diff options
527 files changed, 17609 insertions, 7567 deletions
diff --git a/Android.mk b/Android.mk index d7e8e4eefb15..bd8b16a8ea1f 100644 --- a/Android.mk +++ b/Android.mk @@ -74,6 +74,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/IBackupAgent.aidl \ core/java/android/app/IInstrumentationWatcher.aidl \ core/java/android/app/INotificationManager.aidl \ + core/java/android/app/INotificationManagerCallback.aidl \ core/java/android/app/IProcessObserver.aidl \ core/java/android/app/ISearchManager.aidl \ core/java/android/app/ISearchManagerCallback.aidl \ @@ -112,6 +113,7 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothManagerCallback.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \ core/java/android/bluetooth/IBluetoothMap.aidl \ + core/java/android/bluetooth/IBluetoothSap.aidl \ core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \ core/java/android/bluetooth/IBluetoothHeadsetClient.aidl \ core/java/android/bluetooth/IBluetoothGatt.aidl \ @@ -207,6 +209,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IUserManager.aidl \ core/java/android/os/IVibratorService.aidl \ core/java/android/security/IKeystoreService.aidl \ + core/java/android/service/carrier/ICarrierConfigService.aidl \ core/java/android/service/carrier/ICarrierMessagingCallback.aidl \ core/java/android/service/carrier/ICarrierMessagingService.aidl \ core/java/android/service/gatekeeper/IGateKeeperService.aidl \ @@ -388,15 +391,16 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/ims/internal/IImsVideoCallCallback.aidl \ telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl \ telephony/java/com/android/ims/ImsConfigListener.aidl \ + telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl \ + telephony/java/com/android/internal/telephony/IMms.aidl \ + telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ + telephony/java/com/android/internal/telephony/ISms.aidl \ + telephony/java/com/android/internal/telephony/ISub.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ - telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl \ - telephony/java/com/android/internal/telephony/ISms.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ - telephony/java/com/android/internal/telephony/ISub.aidl \ - telephony/java/com/android/internal/telephony/IMms.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ @@ -547,6 +551,7 @@ aidl_files := \ frameworks/base/core/java/android/view/textservice/SpellCheckerInfo.aidl \ frameworks/base/core/java/android/view/textservice/SentenceSuggestionsInfo.aidl \ frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \ + frameworks/base/core/java/android/service/carrier/CarrierIdentifier.aidl \ frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \ frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \ frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \ @@ -555,6 +560,7 @@ aidl_files := \ frameworks/base/core/java/android/app/AssistStructure.aidl \ frameworks/base/core/java/android/app/AssistContent.aidl \ frameworks/base/core/java/android/app/Notification.aidl \ + frameworks/base/core/java/android/app/NotificationManager.aidl \ frameworks/base/core/java/android/app/WallpaperInfo.aidl \ frameworks/base/core/java/android/app/AppOpsManager.aidl \ frameworks/base/core/java/android/app/ActivityManager.aidl \ diff --git a/api/current.txt b/api/current.txt index 818114d14c71..4375e6f4ff0e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20,6 +20,7 @@ package android { field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; + field public static final java.lang.String BIND_CARRIER_CONFIG_SERVICE = "android.permission.BIND_CARRIER_CONFIG_SERVICE"; field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE"; field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"; @@ -793,6 +794,7 @@ package android { field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad + field public static final int leftIndents = 16844016; // 0x10104f0 field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -815,6 +817,7 @@ package android { field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 field public static final int listViewWhiteStyle = 16842869; // 0x1010075 + field public static final int lockTaskMode = 16844015; // 0x10104ef field public static final int logo = 16843454; // 0x10102be field public static final int longClickable = 16842982; // 0x10100e6 field public static final int loopViews = 16843527; // 0x1010307 @@ -1020,6 +1023,7 @@ package android { field public static final int reversible = 16843851; // 0x101044b field public static final int revisionCode = 16843989; // 0x10104d5 field public static final int right = 16843183; // 0x10101af + field public static final int rightIndents = 16844017; // 0x10104f1 field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 field public static final int rotation = 16843558; // 0x1010326 @@ -1095,7 +1099,8 @@ package android { field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 - field public static final int showOnLockScreen = 16843721; // 0x10103c9 + field public static final int showForAllUsers = 16844018; // 0x10104f2 + field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9 field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad field public static final deprecated int showWeekNumber = 16843582; // 0x101033e @@ -1975,6 +1980,7 @@ package android { field public static final int TextAppearance_Material_Widget_ActionMode_Title = 16974355; // 0x1030213 field public static final int TextAppearance_Material_Widget_ActionMode_Title_Inverse = 16974356; // 0x1030214 field public static final int TextAppearance_Material_Widget_Button = 16974357; // 0x1030215 + field public static final int TextAppearance_Material_Widget_Button_Inverse = 16974565; // 0x10302e5 field public static final int TextAppearance_Material_Widget_DropDownHint = 16974358; // 0x1030216 field public static final int TextAppearance_Material_Widget_DropDownItem = 16974359; // 0x1030217 field public static final int TextAppearance_Material_Widget_EditText = 16974360; // 0x1030218 @@ -3630,12 +3636,15 @@ package android.app { method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RecentTaskInfo> CREATOR; field public int affiliatedTaskId; + field public android.content.ComponentName baseActivity; field public android.content.Intent baseIntent; field public java.lang.CharSequence description; field public int id; + field public int numActivities; field public android.content.ComponentName origActivity; field public int persistentId; field public android.app.ActivityManager.TaskDescription taskDescription; + field public android.content.ComponentName topActivity; } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -5107,8 +5116,44 @@ package android.app { method public void cancel(int); method public void cancel(java.lang.String, int); method public void cancelAll(); + method public android.app.NotificationManager.Policy getNotificationPolicy(android.app.NotificationManager.Policy.Token); + method public boolean isNotificationPolicyTokenValid(android.app.NotificationManager.Policy.Token); method public void notify(int, android.app.Notification); method public void notify(java.lang.String, int, android.app.Notification); + method public void requestNotificationPolicyToken(android.app.NotificationManager.Policy.Token.RequestCallback, android.os.Handler); + method public void setNotificationPolicy(android.app.NotificationManager.Policy.Token, android.app.NotificationManager.Policy); + field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + } + + public static class NotificationManager.Policy implements android.os.Parcelable { + ctor public NotificationManager.Policy(int, int); + method public int describeContents(); + method public static java.lang.String priorityCategoriesToString(int); + method public static java.lang.String prioritySendersToString(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR; + field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8 + field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2 + field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4 + field public static final int PRIORITY_CATEGORY_REMINDERS = 1; // 0x1 + field public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 16; // 0x10 + field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0 + field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1 + field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2 + field public final int priorityCategories; + field public final int prioritySenders; + } + + public static class NotificationManager.Policy.Token implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy.Token> CREATOR; + } + + public static abstract class NotificationManager.Policy.Token.RequestCallback { + ctor public NotificationManager.Policy.Token.RequestCallback(); + method public abstract void onTokenDenied(); + method public abstract void onTokenGranted(android.app.NotificationManager.Policy.Token); } public final class PendingIntent implements android.os.Parcelable { @@ -6912,6 +6957,7 @@ package android.bluetooth { field public static final int GATT_SERVER = 8; // 0x8 field public static final int HEADSET = 1; // 0x1 field public static final int HEALTH = 3; // 0x3 + field public static final int SAP = 10; // 0xa field public static final int STATE_CONNECTED = 2; // 0x2 field public static final int STATE_CONNECTING = 1; // 0x1 field public static final int STATE_DISCONNECTED = 0; // 0x0 @@ -6923,6 +6969,25 @@ package android.bluetooth { method public abstract void onServiceDisconnected(int); } + public final class BluetoothSap implements android.bluetooth.BluetoothProfile { + method public synchronized void close(); + method public boolean connect(android.bluetooth.BluetoothDevice); + method public boolean disconnect(android.bluetooth.BluetoothDevice); + method public android.bluetooth.BluetoothDevice getClient(); + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + method public int getPriority(android.bluetooth.BluetoothDevice); + method public int getState(); + method public boolean isConnected(android.bluetooth.BluetoothDevice); + method public boolean setPriority(android.bluetooth.BluetoothDevice, int); + field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; + field public static final int RESULT_CANCELED = 2; // 0x2 + field public static final int RESULT_FAILURE = 0; // 0x0 + field public static final int RESULT_SUCCESS = 1; // 0x1 + field public static final int STATE_ERROR = -1; // 0xffffffff + } + public final class BluetoothServerSocket implements java.io.Closeable { method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException; method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException; @@ -6932,10 +6997,16 @@ package android.bluetooth { public final class BluetoothSocket implements java.io.Closeable { method public void close() throws java.io.IOException; method public void connect() throws java.io.IOException; + method public int getConnectionType(); method public java.io.InputStream getInputStream() throws java.io.IOException; + method public int getMaxReceivePacketSize(); + method public int getMaxTransmitPacketSize(); method public java.io.OutputStream getOutputStream() throws java.io.IOException; method public android.bluetooth.BluetoothDevice getRemoteDevice(); method public boolean isConnected(); + field public static final int TYPE_L2CAP = 3; // 0x3 + field public static final int TYPE_RFCOMM = 1; // 0x1 + field public static final int TYPE_SCO = 2; // 0x2 } } @@ -7661,6 +7732,7 @@ package android.content { field public static final java.lang.String ALARM_SERVICE = "alarm"; field public static final java.lang.String APPWIDGET_SERVICE = "appwidget"; field public static final java.lang.String APP_OPS_SERVICE = "appops"; + field public static final java.lang.String AUDIO_DEVICES_SERVICE = "audio_devices_manager"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final java.lang.String BATTERY_SERVICE = "batterymanager"; field public static final int BIND_ABOVE_CLIENT = 8; // 0x8 @@ -7674,6 +7746,7 @@ package android.content { field public static final java.lang.String BLUETOOTH_SERVICE = "bluetooth"; field public static final java.lang.String CAMERA_SERVICE = "camera"; field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; + field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; @@ -12608,6 +12681,7 @@ package android.hardware { method public final void unlock(); field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; field public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; + field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2 field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64 field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1 } @@ -13768,6 +13842,8 @@ package android.hardware.fingerprint { public class FingerprintManager { method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, int); + method public boolean hasEnrolledFingerprints(); + method public boolean isHardwareDetected(); field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -13798,6 +13874,8 @@ package android.hardware.fingerprint { } public static class FingerprintManager.CryptoObject { + ctor public FingerprintManager.CryptoObject(java.security.Signature); + ctor public FingerprintManager.CryptoObject(javax.crypto.Cipher); method public javax.crypto.Cipher getCipher(); method public java.security.Signature getSignature(); } @@ -14566,6 +14644,47 @@ package android.media { method public android.media.AudioAttributes.Builder setUsage(int); } + public class AudioDeviceInfo { + method public java.lang.String getAddress(); + method public int[] getChannelCounts(); + method public int[] getChannelMasks(); + method public int[] getFormats(); + method public java.lang.String getName(); + method public int[] getSampleRates(); + method public int getType(); + method public boolean isSink(); + method public boolean isSource(); + field public static final int TYPE_AUX_LINE = 19; // 0x13 + field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8 + field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7 + field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1 + field public static final int TYPE_BUILTIN_MIC = 15; // 0xf + field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 + field public static final int TYPE_DOCK = 13; // 0xd + field public static final int TYPE_FM = 14; // 0xe + field public static final int TYPE_FM_TUNER = 16; // 0x10 + field public static final int TYPE_HDMI = 9; // 0x9 + field public static final int TYPE_HDMI_ARC = 10; // 0xa + field public static final int TYPE_LINE_ANALOG = 5; // 0x5 + field public static final int TYPE_LINE_DIGITAL = 6; // 0x6 + field public static final int TYPE_TELEPHONY = 18; // 0x12 + field public static final int TYPE_TV_TUNER = 17; // 0x11 + field public static final int TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_USB_ACCESSORY = 12; // 0xc + field public static final int TYPE_USB_DEVICE = 11; // 0xb + field public static final int TYPE_WIRED_HEADPHONES = 4; // 0x4 + field public static final int TYPE_WIRED_HEADSET = 3; // 0x3 + } + + public class AudioDevicesManager { + method public void addOnAudioDeviceConnectionListener(android.media.OnAudioDeviceConnectionListener, android.os.Handler); + method public android.media.AudioDeviceInfo[] listDevices(int); + method public void removeOnAudioDeviceConnectionListener(android.media.OnAudioDeviceConnectionListener); + field public static final int LIST_DEVICES_ALL = 3; // 0x3 + field public static final int LIST_DEVICES_INPUTS = 1; // 0x1 + field public static final int LIST_DEVICES_OUTPUTS = 2; // 0x2 + } + public class AudioFormat { method public int getChannelCount(); method public int getChannelIndexMask(); @@ -14853,6 +14972,7 @@ package android.media { method public int getPlaybackHeadPosition(); method public int getPlaybackRate(); method public int getPositionNotificationPeriod(); + method public android.media.AudioDeviceInfo getPreferredOutputDevice(); method public int getSampleRate(); method public int getState(); method public int getStreamType(); @@ -14869,6 +14989,7 @@ package android.media { method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler); method public int setPlaybackRate(int); method public int setPositionNotificationPeriod(int); + method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo); method protected deprecated void setState(int); method public deprecated int setStereoVolume(float, float); method public int setVolume(float); @@ -15452,6 +15573,11 @@ package android.media { ctor public MediaCryptoException(java.lang.String); } + public abstract interface MediaDataSource implements java.io.Closeable { + method public abstract long getSize(); + method public abstract int readAt(long, byte[], int); + } + public class MediaDescription implements android.os.Parcelable { method public int describeContents(); method public java.lang.CharSequence getDescription(); @@ -15587,6 +15713,7 @@ package android.media { method public final void release(); method public void seekTo(long, int); method public void selectTrack(int); + method public final void setDataSource(android.media.MediaDataSource) throws java.io.IOException, java.lang.IllegalArgumentException; method public final void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException; method public final void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException; method public final void setDataSource(java.lang.String) throws java.io.IOException; @@ -15766,6 +15893,7 @@ package android.media { method public void setDataSource(java.io.FileDescriptor, long, long) throws java.lang.IllegalArgumentException; method public void setDataSource(java.io.FileDescriptor) throws java.lang.IllegalArgumentException; method public void setDataSource(android.content.Context, android.net.Uri) throws java.lang.IllegalArgumentException, java.lang.SecurityException; + method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException; field public static final int METADATA_KEY_ALBUM = 1; // 0x1 field public static final int METADATA_KEY_ALBUMARTIST = 13; // 0xd field public static final int METADATA_KEY_ARTIST = 2; // 0x2 @@ -15850,6 +15978,7 @@ package android.media { method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; + method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setDisplay(android.view.SurfaceHolder); method public void setLooping(boolean); method public void setNextMediaPlayer(android.media.MediaPlayer); @@ -16172,7 +16301,7 @@ package android.media { public final class MediaSync { ctor public MediaSync(); - method public void configureAudioTrack(android.media.AudioTrack, int); + method public void configureAudioTrack(android.media.AudioTrack); method public void configureSurface(android.view.Surface); method public final android.view.Surface createInputSurface(); method public boolean getTimestamp(android.media.MediaTimestamp); @@ -16208,6 +16337,10 @@ package android.media { ctor public NotProvisionedException(java.lang.String); } + public abstract interface OnAudioDeviceConnectionListener { + method public abstract void onAudioDeviceConnection(); + } + public final class Rating implements android.os.Parcelable { method public int describeContents(); method public float getPercentRating(); @@ -18018,6 +18151,7 @@ package android.net { public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(android.net.Network); + method public android.net.Network getActiveNetwork(); method public android.net.NetworkInfo getActiveNetworkInfo(); method public android.net.NetworkInfo[] getAllNetworkInfo(); method public android.net.Network[] getAllNetworks(); @@ -18036,7 +18170,8 @@ package android.net { method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void releaseNetworkRequest(android.app.PendingIntent); method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); - method public void reportBadNetwork(android.net.Network); + method public deprecated void reportBadNetwork(android.net.Network); + method public void reportNetworkConnectivity(android.net.Network, boolean); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated boolean requestRouteToHost(int, int); @@ -19552,6 +19687,7 @@ package android.nfc { public final class NfcEvent { field public final android.nfc.NfcAdapter nfcAdapter; + field public final byte peerLlcpVersion; } public final class NfcManager { @@ -22715,6 +22851,8 @@ package android.os { method public static long getNativeHeapFreeSize(); method public static long getNativeHeapSize(); method public static long getPss(); + method public static java.lang.String getRuntimeStat(java.lang.String); + method public static java.util.Map<java.lang.String, java.lang.String> getRuntimeStats(); method public static deprecated int getThreadAllocCount(); method public static deprecated int getThreadAllocSize(); method public static deprecated int getThreadExternalAllocCount(); @@ -26465,6 +26603,7 @@ package android.provider { field public static final java.lang.String DEBUG_APP = "debug_app"; field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; field public static final java.lang.String DEVICE_PROVISIONED = "device_provisioned"; + field public static final java.lang.String HIDE_CARRIER_NETWORK_SETTINGS = "hide_carrier_network_settings"; field public static final java.lang.String HTTP_PROXY = "http_proxy"; field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field public static final java.lang.String MODE_RINGER = "mode_ringer"; @@ -26630,6 +26769,7 @@ package android.provider { field public static final android.net.Uri DEFAULT_RINGTONE_URI; field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned"; field public static final deprecated java.lang.String DIM_SCREEN = "dim_screen"; + field public static final java.lang.String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; field public static final java.lang.String DTMF_TONE_WHEN_DIALING = "dtmf_tone"; field public static final java.lang.String END_BUTTON_BEHAVIOR = "end_button_behavior"; field public static final java.lang.String FONT_SCALE = "font_scale"; @@ -26678,6 +26818,7 @@ package android.provider { field public static final java.lang.String USER_ROTATION = "user_rotation"; field public static final deprecated java.lang.String USE_GOOGLE_MAIL = "use_google_mail"; field public static final java.lang.String VIBRATE_ON = "vibrate_on"; + field public static final java.lang.String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; field public static final deprecated java.lang.String WAIT_FOR_DEBUGGER = "wait_for_debugger"; field public static final deprecated java.lang.String WALLPAPER_ACTIVITY = "wallpaper_activity"; field public static final deprecated java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; @@ -27209,7 +27350,6 @@ package android.renderscript { method public static android.renderscript.AllocationAdapter create1D(android.renderscript.RenderScript, android.renderscript.Allocation); method public static android.renderscript.AllocationAdapter create2D(android.renderscript.RenderScript, android.renderscript.Allocation); method public static android.renderscript.AllocationAdapter createTyped(android.renderscript.RenderScript, android.renderscript.Allocation, android.renderscript.Type); - method public void setArray(int, int); method public void setFace(android.renderscript.Type.CubemapFace); method public void setLOD(int); method public void setX(int); @@ -27870,6 +28010,62 @@ package android.renderscript { method public void setLUT(android.renderscript.Allocation); } + public final class ScriptIntrinsicBLAS extends android.renderscript.ScriptIntrinsic { + method public void BNNM(android.renderscript.Allocation, int, android.renderscript.Allocation, int, android.renderscript.Allocation, int, int); + method public void CGEMM(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CHEMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CHER2K(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CHERK(int, int, float, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CSYMM(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CSYR2K(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CSYRK(int, int, float, float, android.renderscript.Allocation, float, float, android.renderscript.Allocation); + method public void CTRMM(int, int, int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void CTRSM(int, int, int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void DGEMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYR2K(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYRK(int, int, double, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DTRMM(int, int, int, int, double, android.renderscript.Allocation, android.renderscript.Allocation); + method public void DTRSM(int, int, int, int, double, android.renderscript.Allocation, android.renderscript.Allocation); + method public void SGEMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYR2K(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYRK(int, int, float, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void STRMM(int, int, int, int, float, android.renderscript.Allocation, android.renderscript.Allocation); + method public void STRSM(int, int, int, int, float, android.renderscript.Allocation, android.renderscript.Allocation); + method public void ZGEMM(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZHEMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZHER2K(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZHERK(int, int, double, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZSYMM(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZSYR2K(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZSYRK(int, int, double, double, android.renderscript.Allocation, double, double, android.renderscript.Allocation); + method public void ZTRMM(int, int, int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void ZTRSM(int, int, int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation); + method public static android.renderscript.ScriptIntrinsicBLAS create(android.renderscript.RenderScript); + field public static final int CONJ_TRANSPOSE = 113; // 0x71 + field public static final int LEFT = 141; // 0x8d + field public static final int LOWER = 122; // 0x7a + field public static final int NON_UNIT = 131; // 0x83 + field public static final int NO_TRANSPOSE = 111; // 0x6f + field public static final int RIGHT = 142; // 0x8e + field public static final int TRANSPOSE = 112; // 0x70 + field public static final int UNIT = 132; // 0x84 + field public static final int UPPER = 121; // 0x79 + } + + public static abstract class ScriptIntrinsicBLAS.Diag implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Side implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Transpose implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Uplo implements java.lang.annotation.Annotation { + } + public class ScriptIntrinsicBlend extends android.renderscript.ScriptIntrinsic { method public static android.renderscript.ScriptIntrinsicBlend create(android.renderscript.RenderScript, android.renderscript.Element); method public void forEachAdd(android.renderscript.Allocation, android.renderscript.Allocation); @@ -28032,8 +28228,6 @@ package android.renderscript { method public static android.renderscript.Type createX(android.renderscript.RenderScript, android.renderscript.Element, int); method public static android.renderscript.Type createXY(android.renderscript.RenderScript, android.renderscript.Element, int, int); method public static android.renderscript.Type createXYZ(android.renderscript.RenderScript, android.renderscript.Element, int, int, int); - method public int getArray(int); - method public int getArrayCount(); method public int getCount(); method public android.renderscript.Element getElement(); method public int getX(); @@ -28047,7 +28241,6 @@ package android.renderscript { public static class Type.Builder { ctor public Type.Builder(android.renderscript.RenderScript, android.renderscript.Element); method public android.renderscript.Type create(); - method public android.renderscript.Type.Builder setArray(int, int); method public android.renderscript.Type.Builder setFaces(boolean); method public android.renderscript.Type.Builder setMipmaps(boolean); method public android.renderscript.Type.Builder setX(int); @@ -28283,6 +28476,7 @@ package android.security { public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 + field public static final int UNKNOWN = 4; // 0x4 } public static abstract class KeyStoreKeyProperties.OriginEnum implements java.lang.annotation.Annotation { @@ -28381,6 +28575,26 @@ package android.security { package android.service.carrier { + public abstract class CarrierConfigService extends android.app.Service { + ctor public CarrierConfigService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract android.os.Bundle onLoadConfig(android.service.carrier.CarrierIdentifier); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.carrier.CarrierConfigService"; + } + + public class CarrierIdentifier implements android.os.Parcelable { + ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); + method public int describeContents(); + method public java.lang.String getGid1(); + method public java.lang.String getGid2(); + method public java.lang.String getImsi(); + method public java.lang.String getMcc(); + method public java.lang.String getMnc(); + method public java.lang.String getSpn(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR; + } + public abstract class CarrierMessagingService extends android.app.Service { ctor public CarrierMessagingService(); method public android.os.IBinder onBind(android.content.Intent); @@ -28552,6 +28766,7 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final android.app.NotificationManager.Policy.Token getNotificationPolicyToken(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); @@ -29735,6 +29950,382 @@ package android.system { package android.telecom { + public final class AudioState implements android.os.Parcelable { + ctor public AudioState(boolean, int, int); + ctor public AudioState(android.telecom.AudioState); + method public static java.lang.String audioRouteToString(int); + method public int describeContents(); + method public int getRoute(); + method public int getSupportedRouteMask(); + method public boolean isMuted(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.AudioState> CREATOR; + field public static final int ROUTE_BLUETOOTH = 2; // 0x2 + field public static final int ROUTE_EARPIECE = 1; // 0x1 + field public static final int ROUTE_SPEAKER = 8; // 0x8 + field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4 + field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5 + } + + public final class Call { + method public void answer(int); + method public void conference(android.telecom.Call); + method public void disconnect(); + method public java.util.List<java.lang.String> getCannedTextResponses(); + method public java.util.List<android.telecom.Call> getChildren(); + method public java.util.List<android.telecom.Call> getConferenceableCalls(); + method public android.telecom.Call.Details getDetails(); + method public android.telecom.Call getParent(); + method public java.lang.String getRemainingPostDialSequence(); + method public int getState(); + method public android.telecom.InCallService.VideoCall getVideoCall(); + method public void hold(); + method public void mergeConference(); + method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean); + method public void playDtmfTone(char); + method public void postDialContinue(boolean); + method public void registerCallback(android.telecom.Call.Callback); + method public void reject(boolean, java.lang.String); + method public void splitFromConference(); + method public void stopDtmfTone(); + method public void swapConference(); + method public void unhold(); + method public void unregisterCallback(android.telecom.Call.Callback); + field public static final java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; + field public static final int STATE_ACTIVE = 4; // 0x4 + field public static final int STATE_CONNECTING = 9; // 0x9 + field public static final int STATE_DIALING = 1; // 0x1 + field public static final int STATE_DISCONNECTED = 7; // 0x7 + field public static final int STATE_DISCONNECTING = 10; // 0xa + field public static final int STATE_HOLDING = 3; // 0x3 + field public static final int STATE_NEW = 0; // 0x0 + field public static final int STATE_PRE_DIAL_WAIT = 8; // 0x8 + field public static final int STATE_RINGING = 2; // 0x2 + } + + public static abstract class Call.Callback { + ctor public Call.Callback(); + method public void onCallDestroyed(android.telecom.Call); + method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>); + method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>); + method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>); + method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); + method public void onParentChanged(android.telecom.Call, android.telecom.Call); + method public void onPostDialWait(android.telecom.Call, java.lang.String); + method public void onStateChanged(android.telecom.Call, int); + method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall); + } + + public static class Call.Details { + method public static boolean can(int, int); + method public boolean can(int); + method public static java.lang.String capabilitiesToString(int); + method public android.telecom.PhoneAccountHandle getAccountHandle(); + method public int getCallCapabilities(); + method public int getCallProperties(); + method public java.lang.String getCallerDisplayName(); + method public int getCallerDisplayNamePresentation(); + method public final long getConnectTimeMillis(); + method public android.telecom.DisconnectCause getDisconnectCause(); + method public android.os.Bundle getExtras(); + method public android.telecom.GatewayInfo getGatewayInfo(); + method public android.net.Uri getHandle(); + method public int getHandlePresentation(); + method public android.telecom.StatusHints getStatusHints(); + method public int getVideoState(); + field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000 + field public static final int CAPABILITY_GENERIC_CONFERENCE = 16384; // 0x4000 + field public static final int CAPABILITY_HIGH_DEF_AUDIO = 32768; // 0x8000 + field public static final int CAPABILITY_HOLD = 1; // 0x1 + field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80 + field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4 + field public static final int CAPABILITY_MUTE = 64; // 0x40 + field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20 + field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000 + field public static final int CAPABILITY_SHOW_CALLBACK_NUMBER = 131072; // 0x20000 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_RX = 256; // 0x100 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_TX = 512; // 0x200 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800 + field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 + field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int CAPABILITY_WIFI = 65536; // 0x10000 + } + + public class CallProperties { + ctor public CallProperties(); + field public static final int CONFERENCE = 1; // 0x1 + } + + public final class CallState { + method public static java.lang.String toString(int); + field public static final int ABORTED = 8; // 0x8 + field public static final int ACTIVE = 5; // 0x5 + field public static final int CONNECTING = 1; // 0x1 + field public static final int DIALING = 3; // 0x3 + field public static final int DISCONNECTED = 7; // 0x7 + field public static final int DISCONNECTING = 9; // 0x9 + field public static final int NEW = 0; // 0x0 + field public static final int ON_HOLD = 6; // 0x6 + field public static final int PRE_DIAL_WAIT = 2; // 0x2 + field public static final int RINGING = 4; // 0x4 + } + + public final class CameraCapabilities implements android.os.Parcelable { + ctor public CameraCapabilities(int, int); + method public int describeContents(); + method public int getHeight(); + method public int getWidth(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.CameraCapabilities> CREATOR; + } + + public abstract class Conference implements android.telecom.IConferenceable { + ctor public Conference(android.telecom.PhoneAccountHandle); + method public final boolean addConnection(android.telecom.Connection); + method public final void destroy(); + method public final android.telecom.AudioState getAudioState(); + method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); + method public final long getConnectTimeMillis(); + method public final int getConnectionCapabilities(); + method public final java.util.List<android.telecom.Connection> getConnections(); + method public final android.telecom.DisconnectCause getDisconnectCause(); + method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle(); + method public android.telecom.Connection getPrimaryConnection(); + method public final int getState(); + method public void onAudioStateChanged(android.telecom.AudioState); + method public void onConnectionAdded(android.telecom.Connection); + method public void onDisconnect(); + method public void onHold(); + method public void onMerge(android.telecom.Connection); + method public void onMerge(); + method public void onPlayDtmfTone(char); + method public void onSeparate(android.telecom.Connection); + method public void onStopDtmfTone(); + method public void onSwap(); + method public void onUnhold(); + method public final void removeConnection(android.telecom.Connection); + method public final void setActive(); + method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>); + method public void setConnectTimeMillis(long); + method public final void setConnectionCapabilities(int); + method public final void setDisconnected(android.telecom.DisconnectCause); + method public final void setOnHold(); + field public static final long CONNECT_TIME_NOT_SPECIFIED = 0L; // 0x0L + } + + public abstract class Connection implements android.telecom.IConferenceable { + ctor public Connection(); + method public static java.lang.String capabilitiesToString(int); + method public static android.telecom.Connection createCanceledConnection(); + method public static android.telecom.Connection createFailedConnection(android.telecom.DisconnectCause); + method public final void destroy(); + method public final android.net.Uri getAddress(); + method public final int getAddressPresentation(); + method public final boolean getAudioModeIsVoip(); + method public final android.telecom.AudioState getAudioState(); + method public final java.lang.String getCallerDisplayName(); + method public final int getCallerDisplayNamePresentation(); + method public final android.telecom.Conference getConference(); + method public final java.util.List<android.telecom.IConferenceable> getConferenceables(); + method public final int getConnectionCapabilities(); + method public final android.telecom.DisconnectCause getDisconnectCause(); + method public final int getState(); + method public final android.telecom.StatusHints getStatusHints(); + method public final android.telecom.Connection.VideoProvider getVideoProvider(); + method public final boolean isRingbackRequested(); + method public void onAbort(); + method public void onAnswer(); + method public void onAudioStateChanged(android.telecom.AudioState); + method public void onDisconnect(); + method public void onHold(); + method public void onPlayDtmfTone(char); + method public void onPostDialContinue(boolean); + method public void onReject(); + method public void onSeparate(); + method public void onStateChanged(int); + method public void onStopDtmfTone(); + method public void onUnhold(); + method public final void setActive(); + method public final void setAddress(android.net.Uri, int); + method public final void setAudioModeIsVoip(boolean); + method public final void setCallerDisplayName(java.lang.String, int); + method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>); + method public final void setConferenceables(java.util.List<android.telecom.IConferenceable>); + method public final void setConnectionCapabilities(int); + method public final void setConnectionService(android.telecom.ConnectionService); + method public final void setDialing(); + method public final void setDisconnected(android.telecom.DisconnectCause); + method public final void setInitialized(); + method public final void setInitializing(); + method public final void setNextPostDialChar(char); + method public final void setOnHold(); + method public final void setPostDialWait(java.lang.String); + method public final void setRingbackRequested(boolean); + method public final void setRinging(); + method public final void setStatusHints(android.telecom.StatusHints); + method public static java.lang.String stateToString(int); + field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000 + field public static final int CAPABILITY_HOLD = 1; // 0x1 + field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80 + field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4 + field public static final int CAPABILITY_MUTE = 64; // 0x40 + field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20 + field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000 + field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 + field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int STATE_ACTIVE = 4; // 0x4 + field public static final int STATE_DIALING = 3; // 0x3 + field public static final int STATE_DISCONNECTED = 6; // 0x6 + field public static final int STATE_HOLDING = 5; // 0x5 + field public static final int STATE_INITIALIZING = 0; // 0x0 + field public static final int STATE_NEW = 1; // 0x1 + field public static final int STATE_RINGING = 2; // 0x2 + } + + public static abstract class Connection.VideoProvider { + ctor public Connection.VideoProvider(); + method public void changeCallDataUsage(long); + method public void changeCameraCapabilities(android.telecom.CameraCapabilities); + method public void changePeerDimensions(int, int); + method public void changeVideoQuality(int); + method public void handleCallSessionEvent(int); + method public abstract void onRequestCameraCapabilities(); + method public abstract void onRequestConnectionDataUsage(); + method public abstract void onSendSessionModifyRequest(android.telecom.VideoProfile); + method public abstract void onSendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void onSetCamera(java.lang.String); + method public abstract void onSetDeviceOrientation(int); + method public abstract void onSetDisplaySurface(android.view.Surface); + method public abstract void onSetPauseImage(java.lang.String); + method public abstract void onSetPreviewSurface(android.view.Surface); + method public abstract void onSetZoom(float); + method public void receiveSessionModifyRequest(android.telecom.VideoProfile); + method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5 + field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6 + field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1 + field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2 + field public static final int SESSION_EVENT_TX_START = 3; // 0x3 + field public static final int SESSION_EVENT_TX_STOP = 4; // 0x4 + field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2 + field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3 + field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5 + field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1 + field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4 + } + + public final class ConnectionRequest implements android.os.Parcelable { + ctor public ConnectionRequest(android.telecom.PhoneAccountHandle, android.net.Uri, android.os.Bundle); + method public int describeContents(); + method public android.telecom.PhoneAccountHandle getAccountHandle(); + method public android.net.Uri getAddress(); + method public android.os.Bundle getExtras(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR; + } + + public abstract class ConnectionService extends android.app.Service { + ctor public ConnectionService(); + method public final void addConference(android.telecom.Conference); + method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection); + method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection); + method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method public final java.util.Collection<android.telecom.Connection> getAllConnections(); + method public final android.os.IBinder onBind(android.content.Intent); + method public void onConference(android.telecom.Connection, android.telecom.Connection); + method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method public void onRemoteConferenceAdded(android.telecom.RemoteConference); + method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection); + field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService"; + } + + public final class DisconnectCause implements android.os.Parcelable { + ctor public DisconnectCause(int); + ctor public DisconnectCause(int, java.lang.String); + ctor public DisconnectCause(int, java.lang.CharSequence, java.lang.CharSequence, java.lang.String); + ctor public DisconnectCause(int, java.lang.CharSequence, java.lang.CharSequence, java.lang.String, int); + method public int describeContents(); + method public int getCode(); + method public java.lang.CharSequence getDescription(); + method public java.lang.CharSequence getLabel(); + method public java.lang.String getReason(); + method public int getTone(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int BUSY = 7; // 0x7 + field public static final int CANCELED = 4; // 0x4 + field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa + field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR; + field public static final int ERROR = 1; // 0x1 + field public static final int LOCAL = 2; // 0x2 + field public static final int MISSED = 5; // 0x5 + field public static final int OTHER = 9; // 0x9 + field public static final int REJECTED = 6; // 0x6 + field public static final int REMOTE = 3; // 0x3 + field public static final int RESTRICTED = 8; // 0x8 + field public static final int UNKNOWN = 0; // 0x0 + } + + public class GatewayInfo implements android.os.Parcelable { + ctor public GatewayInfo(java.lang.String, android.net.Uri, android.net.Uri); + method public int describeContents(); + method public android.net.Uri getGatewayAddress(); + method public java.lang.String getGatewayProviderPackageName(); + method public android.net.Uri getOriginalAddress(); + method public boolean isEmpty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.GatewayInfo> CREATOR; + } + + public abstract interface IConferenceable { + } + + public abstract class InCallService extends android.app.Service { + ctor public InCallService(); + method public final boolean canAddCall(); + method public final android.telecom.AudioState getAudioState(); + method public final java.util.List<android.telecom.Call> getCalls(); + method public void onAudioStateChanged(android.telecom.AudioState); + method public android.os.IBinder onBind(android.content.Intent); + method public void onBringToForeground(boolean); + method public void onCallAdded(android.telecom.Call); + method public void onCallRemoved(android.telecom.Call); + method public void onCanAddCallChanged(boolean); + method public final void setAudioRoute(int); + method public final void setMuted(boolean); + field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService"; + } + + public static abstract class InCallService.VideoCall { + ctor public InCallService.VideoCall(); + method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback); + method public abstract void requestCallDataUsage(); + method public abstract void requestCameraCapabilities(); + method public abstract void sendSessionModifyRequest(android.telecom.VideoProfile); + method public abstract void sendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void setCamera(java.lang.String); + method public abstract void setDeviceOrientation(int); + method public abstract void setDisplaySurface(android.view.Surface); + method public abstract void setPauseImage(java.lang.String); + method public abstract void setPreviewSurface(android.view.Surface); + method public abstract void setZoom(float); + } + + public static abstract class InCallService.VideoCall.Callback { + ctor public InCallService.VideoCall.Callback(); + method public abstract void onCallDataUsageChanged(long); + method public abstract void onCallSessionEvent(int); + method public abstract void onCameraCapabilitiesChanged(android.telecom.CameraCapabilities); + method public abstract void onPeerDimensionsChanged(int, int); + method public abstract void onSessionModifyRequestReceived(android.telecom.VideoProfile); + method public abstract void onSessionModifyResponseReceived(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + method public abstract void onVideoQualityChanged(int); + } + public class PhoneAccount implements android.os.Parcelable { method public static android.telecom.PhoneAccount.Builder builder(android.telecom.PhoneAccountHandle, java.lang.CharSequence); method public android.graphics.drawable.Drawable createIconDrawable(android.content.Context); @@ -29753,7 +30344,10 @@ package android.telecom { method public java.util.List<java.lang.String> getSupportedUriSchemes(); method public boolean hasCapabilities(int); method public boolean supportsUriScheme(java.lang.String); + method public android.telecom.PhoneAccount.Builder toBuilder(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2 + field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1 field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10 field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4 field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8 @@ -29769,6 +30363,7 @@ package android.telecom { public static class PhoneAccount.Builder { ctor public PhoneAccount.Builder(android.telecom.PhoneAccountHandle, java.lang.CharSequence); ctor public PhoneAccount.Builder(android.telecom.PhoneAccount); + method public android.telecom.PhoneAccount.Builder addSupportedUriScheme(java.lang.String); method public android.telecom.PhoneAccount build(); method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri); method public android.telecom.PhoneAccount.Builder setCapabilities(int); @@ -29785,28 +30380,130 @@ package android.telecom { public class PhoneAccountHandle implements android.os.Parcelable { ctor public PhoneAccountHandle(android.content.ComponentName, java.lang.String); + ctor public PhoneAccountHandle(android.content.ComponentName, java.lang.String, android.os.UserHandle); method public int describeContents(); method public android.content.ComponentName getComponentName(); method public java.lang.String getId(); + method public android.os.UserHandle getUserHandle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountHandle> CREATOR; } + public final class RemoteConference { + method public void disconnect(); + method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections(); + method public final int getConnectionCapabilities(); + method public final java.util.List<android.telecom.RemoteConnection> getConnections(); + method public android.telecom.DisconnectCause getDisconnectCause(); + method public final int getState(); + method public void hold(); + method public void merge(); + method public void playDtmfTone(char); + method public final void registerCallback(android.telecom.RemoteConference.Callback); + method public void separate(android.telecom.RemoteConnection); + method public void setAudioState(android.telecom.AudioState); + method public void stopDtmfTone(); + method public void swap(); + method public void unhold(); + method public final void unregisterCallback(android.telecom.RemoteConference.Callback); + } + + public static abstract class RemoteConference.Callback { + ctor public RemoteConference.Callback(); + method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>); + method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection); + method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int); + method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection); + method public void onDestroyed(android.telecom.RemoteConference); + method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause); + method public void onStateChanged(android.telecom.RemoteConference, int, int); + } + + public final class RemoteConnection { + method public void abort(); + method public void answer(); + method public void disconnect(); + method public android.net.Uri getAddress(); + method public int getAddressPresentation(); + method public java.lang.CharSequence getCallerDisplayName(); + method public int getCallerDisplayNamePresentation(); + method public android.telecom.RemoteConference getConference(); + method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections(); + method public int getConnectionCapabilities(); + method public android.telecom.DisconnectCause getDisconnectCause(); + method public int getState(); + method public android.telecom.StatusHints getStatusHints(); + method public void hold(); + method public boolean isRingbackRequested(); + method public boolean isVoipAudioMode(); + method public void playDtmfTone(char); + method public void postDialContinue(boolean); + method public void registerCallback(android.telecom.RemoteConnection.Callback); + method public void reject(); + method public void setAudioState(android.telecom.AudioState); + method public void stopDtmfTone(); + method public void unhold(); + method public void unregisterCallback(android.telecom.RemoteConnection.Callback); + } + + public static abstract class RemoteConnection.Callback { + ctor public RemoteConnection.Callback(); + method public void onAddressChanged(android.telecom.RemoteConnection, android.net.Uri, int); + method public void onCallerDisplayNameChanged(android.telecom.RemoteConnection, java.lang.String, int); + method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference); + method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>); + method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int); + method public void onDestroyed(android.telecom.RemoteConnection); + method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause); + method public void onPostDialChar(android.telecom.RemoteConnection, char); + method public void onPostDialWait(android.telecom.RemoteConnection, java.lang.String); + method public void onRingbackRequested(android.telecom.RemoteConnection, boolean); + method public void onStateChanged(android.telecom.RemoteConnection, int); + method public void onStatusHintsChanged(android.telecom.RemoteConnection, android.telecom.StatusHints); + method public void onVoipAudioChanged(android.telecom.RemoteConnection, boolean); + } + + public final class StatusHints implements android.os.Parcelable { + ctor public StatusHints(android.content.ComponentName, java.lang.CharSequence, int, android.os.Bundle); + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public android.graphics.drawable.Drawable getIcon(android.content.Context); + method public int getIconResId(); + method public java.lang.CharSequence getLabel(); + method public android.content.ComponentName getPackageName(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR; + } + public class TelecomManager { + method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); method public void cancelMissedCallsNotification(); + method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle); method public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); + method public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(java.lang.String); + method public java.lang.String getLine1Number(android.telecom.PhoneAccountHandle); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); + method public android.telecom.PhoneAccountHandle getSimCallManager(); + method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method public boolean handleMmi(java.lang.String); method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle); method public boolean isInCall(); + method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String); + method public void registerPhoneAccount(android.telecom.PhoneAccount); method public void showInCallScreen(boolean); + method public void silenceRinger(); + method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); + field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; + field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"; field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"; field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ',' field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';' + field public static final java.lang.String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE"; + field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS"; field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS"; field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE"; field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; @@ -29851,6 +30548,18 @@ package android.telecom { package android.telephony { + public class CarrierConfigManager { + method public android.os.Bundle getConfig(); + method public android.os.Bundle getConfigForSubId(int); + method public void reloadCarrierConfigForSubId(int); + field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_AVAILABLE = "bool_carrier_volte_available"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_PROVISIONED = "bool_carrier_volte_provisioned"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_TTY_SUPPORTED = "bool_carrier_volte_tty_supported"; + field public static final java.lang.String BOOL_SHOW_APN_SETTING_CDMA = "bool_show_apn_setting_cdma"; + field public static final java.lang.String INT_VOLTE_REPLACEMENT_RAT = "int_volte_replacement_rat"; + } + public final class CellIdentityCdma implements android.os.Parcelable { method public int describeContents(); method public int getBasestationId(); @@ -30323,6 +31032,7 @@ package android.telephony { method public int getDataActivity(); method public int getDataState(); method public java.lang.String getDeviceId(); + method public java.lang.String getDeviceId(int); method public java.lang.String getDeviceSoftwareVersion(); method public java.lang.String getGroupIdLevel1(); method public java.lang.String getLine1Number(); @@ -30333,6 +31043,7 @@ package android.telephony { method public java.lang.String getNetworkOperator(); method public java.lang.String getNetworkOperatorName(); method public int getNetworkType(); + method public int getPhoneCount(); method public int getPhoneType(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); @@ -30356,6 +31067,7 @@ package android.telephony { method public boolean isVoiceCapable(); method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); + method public void notifyCarrierNetworkChange(boolean); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String); method public boolean setOperatorBrandOverride(java.lang.String); @@ -33853,6 +34565,7 @@ package android.view { method public boolean onTouchEvent(android.view.MotionEvent); method public void setIsLongpressEnabled(boolean); method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener); + method public void setOnStylusButtonPressListener(android.view.GestureDetector.OnStylusButtonPressListener); } public static abstract interface GestureDetector.OnDoubleTapListener { @@ -33870,7 +34583,11 @@ package android.view { method public abstract boolean onSingleTapUp(android.view.MotionEvent); } - public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener { + public static abstract interface GestureDetector.OnStylusButtonPressListener { + method public abstract boolean onStylusButtonPress(android.view.MotionEvent); + } + + public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener android.view.GestureDetector.OnStylusButtonPressListener { ctor public GestureDetector.SimpleOnGestureListener(); method public boolean onDoubleTap(android.view.MotionEvent); method public boolean onDoubleTapEvent(android.view.MotionEvent); @@ -33881,6 +34598,7 @@ package android.view { method public void onShowPress(android.view.MotionEvent); method public boolean onSingleTapConfirmed(android.view.MotionEvent); method public boolean onSingleTapUp(android.view.MotionEvent); + method public boolean onStylusButtonPress(android.view.MotionEvent); } public class Gravity { @@ -35842,6 +36560,7 @@ package android.view { ctor public ViewGroup(android.content.Context, android.util.AttributeSet, int); ctor public ViewGroup(android.content.Context, android.util.AttributeSet, int, int); method public boolean addStatesFromChildren(); + method public void addTransientView(android.view.View, int); method public void addView(android.view.View); method public void addView(android.view.View, int); method public void addView(android.view.View, int, int); @@ -35893,6 +36612,9 @@ package android.view { method public int getNestedScrollAxes(); method public int getPersistentDrawingCache(); method public boolean getTouchscreenBlocksFocus(); + method public android.view.View getTransientView(int); + method public int getTransientViewCount(); + method public int getTransientViewIndex(int); method public int indexOfChild(android.view.View); method public final void invalidateChild(android.view.View, android.graphics.Rect); method public android.view.ViewParent invalidateChildInParent(int[], android.graphics.Rect); @@ -35926,6 +36648,7 @@ package android.view { method public void removeAllViews(); method public void removeAllViewsInLayout(); method protected void removeDetachedView(android.view.View, boolean); + method public void removeTransientView(android.view.View); method public void removeView(android.view.View); method public void removeViewAt(int); method public void removeViewInLayout(android.view.View); @@ -36528,6 +37251,7 @@ package android.view { field public static final int TITLE_CHANGED = 64; // 0x40 field public static final int TYPE_ACCESSIBILITY_OVERLAY = 2032; // 0x7f0 field public static final int TYPE_APPLICATION = 2; // 0x2 + field public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = 1005; // 0x3ed field public static final int TYPE_APPLICATION_ATTACHED_DIALOG = 1003; // 0x3eb field public static final int TYPE_APPLICATION_MEDIA = 1001; // 0x3e9 field public static final int TYPE_APPLICATION_PANEL = 1000; // 0x3e8 @@ -40640,6 +41364,7 @@ package android.widget { method public int getInputType(); method public final android.text.method.KeyListener getKeyListener(); method public final android.text.Layout getLayout(); + method public int[] getLeftIndents(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); method public int getLineCount(); @@ -40662,6 +41387,7 @@ package android.widget { method public android.text.TextPaint getPaint(); method public int getPaintFlags(); method public java.lang.String getPrivateImeOptions(); + method public int[] getRightIndents(); method public int getSelectionEnd(); method public int getSelectionStart(); method public int getShadowColor(); @@ -40739,6 +41465,7 @@ package android.widget { method public void setImeActionLabel(java.lang.CharSequence, int); method public void setImeOptions(int); method public void setIncludeFontPadding(boolean); + method public void setIndents(int[], int[]); method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public void setInputType(int); method public void setKeyListener(android.text.method.KeyListener); @@ -57347,3 +58074,4 @@ package org.xmlpull.v1.sax2 { } } + diff --git a/api/system-current.txt b/api/system-current.txt index 099ed3a75375..363967373791 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -28,6 +28,7 @@ package android { field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; + field public static final java.lang.String BIND_CARRIER_CONFIG_SERVICE = "android.permission.BIND_CARRIER_CONFIG_SERVICE"; field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE"; field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; field public static final java.lang.String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE"; @@ -866,6 +867,7 @@ package android { field public static final int layout_x = 16843135; // 0x101017f field public static final int layout_y = 16843136; // 0x1010180 field public static final int left = 16843181; // 0x10101ad + field public static final int leftIndents = 16844016; // 0x10104f0 field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -888,6 +890,7 @@ package android { field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 field public static final int listViewWhiteStyle = 16842869; // 0x1010075 + field public static final int lockTaskMode = 16844015; // 0x10104ef field public static final int logo = 16843454; // 0x10102be field public static final int longClickable = 16842982; // 0x10100e6 field public static final int loopViews = 16843527; // 0x1010307 @@ -1093,6 +1096,7 @@ package android { field public static final int reversible = 16843851; // 0x101044b field public static final int revisionCode = 16843989; // 0x10104d5 field public static final int right = 16843183; // 0x10101af + field public static final int rightIndents = 16844017; // 0x10104f1 field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093 field public static final int ringtoneType = 16843257; // 0x10101f9 field public static final int rotation = 16843558; // 0x1010326 @@ -1172,7 +1176,8 @@ package android { field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 - field public static final int showOnLockScreen = 16843721; // 0x10103c9 + field public static final int showForAllUsers = 16844018; // 0x10104f2 + field public static final deprecated int showOnLockScreen = 16843721; // 0x10103c9 field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad field public static final deprecated int showWeekNumber = 16843582; // 0x101033e @@ -2054,6 +2059,7 @@ package android { field public static final int TextAppearance_Material_Widget_ActionMode_Title = 16974355; // 0x1030213 field public static final int TextAppearance_Material_Widget_ActionMode_Title_Inverse = 16974356; // 0x1030214 field public static final int TextAppearance_Material_Widget_Button = 16974357; // 0x1030215 + field public static final int TextAppearance_Material_Widget_Button_Inverse = 16974565; // 0x10302e5 field public static final int TextAppearance_Material_Widget_DropDownHint = 16974358; // 0x1030216 field public static final int TextAppearance_Material_Widget_DropDownItem = 16974359; // 0x1030217 field public static final int TextAppearance_Material_Widget_EditText = 16974360; // 0x1030218 @@ -3719,12 +3725,15 @@ package android.app { method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RecentTaskInfo> CREATOR; field public int affiliatedTaskId; + field public android.content.ComponentName baseActivity; field public android.content.Intent baseIntent; field public java.lang.CharSequence description; field public int id; + field public int numActivities; field public android.content.ComponentName origActivity; field public int persistentId; field public android.app.ActivityManager.TaskDescription taskDescription; + field public android.content.ComponentName topActivity; } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -5198,8 +5207,44 @@ package android.app { method public void cancel(int); method public void cancel(java.lang.String, int); method public void cancelAll(); + method public android.app.NotificationManager.Policy getNotificationPolicy(android.app.NotificationManager.Policy.Token); + method public boolean isNotificationPolicyTokenValid(android.app.NotificationManager.Policy.Token); method public void notify(int, android.app.Notification); method public void notify(java.lang.String, int, android.app.Notification); + method public void requestNotificationPolicyToken(android.app.NotificationManager.Policy.Token.RequestCallback, android.os.Handler); + method public void setNotificationPolicy(android.app.NotificationManager.Policy.Token, android.app.NotificationManager.Policy); + field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + } + + public static class NotificationManager.Policy implements android.os.Parcelable { + ctor public NotificationManager.Policy(int, int); + method public int describeContents(); + method public static java.lang.String priorityCategoriesToString(int); + method public static java.lang.String prioritySendersToString(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR; + field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8 + field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2 + field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4 + field public static final int PRIORITY_CATEGORY_REMINDERS = 1; // 0x1 + field public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 16; // 0x10 + field public static final int PRIORITY_SENDERS_ANY = 0; // 0x0 + field public static final int PRIORITY_SENDERS_CONTACTS = 1; // 0x1 + field public static final int PRIORITY_SENDERS_STARRED = 2; // 0x2 + field public final int priorityCategories; + field public final int prioritySenders; + } + + public static class NotificationManager.Policy.Token implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy.Token> CREATOR; + } + + public static abstract class NotificationManager.Policy.Token.RequestCallback { + ctor public NotificationManager.Policy.Token.RequestCallback(); + method public abstract void onTokenDenied(); + method public abstract void onTokenGranted(android.app.NotificationManager.Policy.Token); } public final class PendingIntent implements android.os.Parcelable { @@ -7103,6 +7148,7 @@ package android.bluetooth { field public static final int GATT_SERVER = 8; // 0x8 field public static final int HEADSET = 1; // 0x1 field public static final int HEALTH = 3; // 0x3 + field public static final int SAP = 10; // 0xa field public static final int STATE_CONNECTED = 2; // 0x2 field public static final int STATE_CONNECTING = 1; // 0x1 field public static final int STATE_DISCONNECTED = 0; // 0x0 @@ -7114,6 +7160,25 @@ package android.bluetooth { method public abstract void onServiceDisconnected(int); } + public final class BluetoothSap implements android.bluetooth.BluetoothProfile { + method public synchronized void close(); + method public boolean connect(android.bluetooth.BluetoothDevice); + method public boolean disconnect(android.bluetooth.BluetoothDevice); + method public android.bluetooth.BluetoothDevice getClient(); + method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method public int getConnectionState(android.bluetooth.BluetoothDevice); + method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); + method public int getPriority(android.bluetooth.BluetoothDevice); + method public int getState(); + method public boolean isConnected(android.bluetooth.BluetoothDevice); + method public boolean setPriority(android.bluetooth.BluetoothDevice, int); + field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; + field public static final int RESULT_CANCELED = 2; // 0x2 + field public static final int RESULT_FAILURE = 0; // 0x0 + field public static final int RESULT_SUCCESS = 1; // 0x1 + field public static final int STATE_ERROR = -1; // 0xffffffff + } + public final class BluetoothServerSocket implements java.io.Closeable { method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException; method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException; @@ -7123,10 +7188,16 @@ package android.bluetooth { public final class BluetoothSocket implements java.io.Closeable { method public void close() throws java.io.IOException; method public void connect() throws java.io.IOException; + method public int getConnectionType(); method public java.io.InputStream getInputStream() throws java.io.IOException; + method public int getMaxReceivePacketSize(); + method public int getMaxTransmitPacketSize(); method public java.io.OutputStream getOutputStream() throws java.io.IOException; method public android.bluetooth.BluetoothDevice getRemoteDevice(); method public boolean isConnected(); + field public static final int TYPE_L2CAP = 3; // 0x3 + field public static final int TYPE_RFCOMM = 1; // 0x1 + field public static final int TYPE_SCO = 2; // 0x2 } } @@ -7876,6 +7947,7 @@ package android.content { field public static final java.lang.String ALARM_SERVICE = "alarm"; field public static final java.lang.String APPWIDGET_SERVICE = "appwidget"; field public static final java.lang.String APP_OPS_SERVICE = "appops"; + field public static final java.lang.String AUDIO_DEVICES_SERVICE = "audio_devices_manager"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final java.lang.String BACKUP_SERVICE = "backup"; field public static final java.lang.String BATTERY_SERVICE = "batterymanager"; @@ -7890,6 +7962,7 @@ package android.content { field public static final java.lang.String BLUETOOTH_SERVICE = "bluetooth"; field public static final java.lang.String CAMERA_SERVICE = "camera"; field public static final java.lang.String CAPTIONING_SERVICE = "captioning"; + field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard"; field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity"; field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir"; @@ -12902,6 +12975,7 @@ package android.hardware { method public final void unlock(); field public static final java.lang.String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; field public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; + field public static final int CAMERA_ERROR_EVICTED = 2; // 0x2 field public static final int CAMERA_ERROR_SERVER_DIED = 100; // 0x64 field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1 } @@ -14064,6 +14138,8 @@ package android.hardware.fingerprint { public class FingerprintManager { method public void authenticate(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.CancellationSignal, android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, int); + method public boolean hasEnrolledFingerprints(); + method public boolean isHardwareDetected(); field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 @@ -14094,6 +14170,8 @@ package android.hardware.fingerprint { } public static class FingerprintManager.CryptoObject { + ctor public FingerprintManager.CryptoObject(java.security.Signature); + ctor public FingerprintManager.CryptoObject(javax.crypto.Cipher); method public javax.crypto.Cipher getCipher(); method public java.security.Signature getSignature(); } @@ -15754,6 +15832,47 @@ package android.media { method public android.media.AudioAttributes.Builder setUsage(int); } + public class AudioDeviceInfo { + method public java.lang.String getAddress(); + method public int[] getChannelCounts(); + method public int[] getChannelMasks(); + method public int[] getFormats(); + method public java.lang.String getName(); + method public int[] getSampleRates(); + method public int getType(); + method public boolean isSink(); + method public boolean isSource(); + field public static final int TYPE_AUX_LINE = 19; // 0x13 + field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8 + field public static final int TYPE_BLUETOOTH_SCO = 7; // 0x7 + field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1 + field public static final int TYPE_BUILTIN_MIC = 15; // 0xf + field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2 + field public static final int TYPE_DOCK = 13; // 0xd + field public static final int TYPE_FM = 14; // 0xe + field public static final int TYPE_FM_TUNER = 16; // 0x10 + field public static final int TYPE_HDMI = 9; // 0x9 + field public static final int TYPE_HDMI_ARC = 10; // 0xa + field public static final int TYPE_LINE_ANALOG = 5; // 0x5 + field public static final int TYPE_LINE_DIGITAL = 6; // 0x6 + field public static final int TYPE_TELEPHONY = 18; // 0x12 + field public static final int TYPE_TV_TUNER = 17; // 0x11 + field public static final int TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_USB_ACCESSORY = 12; // 0xc + field public static final int TYPE_USB_DEVICE = 11; // 0xb + field public static final int TYPE_WIRED_HEADPHONES = 4; // 0x4 + field public static final int TYPE_WIRED_HEADSET = 3; // 0x3 + } + + public class AudioDevicesManager { + method public void addOnAudioDeviceConnectionListener(android.media.OnAudioDeviceConnectionListener, android.os.Handler); + method public android.media.AudioDeviceInfo[] listDevices(int); + method public void removeOnAudioDeviceConnectionListener(android.media.OnAudioDeviceConnectionListener); + field public static final int LIST_DEVICES_ALL = 3; // 0x3 + field public static final int LIST_DEVICES_INPUTS = 1; // 0x1 + field public static final int LIST_DEVICES_OUTPUTS = 2; // 0x2 + } + public final class AudioFocusInfo implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioAttributes getAttributes(); @@ -16065,6 +16184,7 @@ package android.media { method public int getPlaybackHeadPosition(); method public int getPlaybackRate(); method public int getPositionNotificationPeriod(); + method public android.media.AudioDeviceInfo getPreferredOutputDevice(); method public int getSampleRate(); method public int getState(); method public int getStreamType(); @@ -16081,6 +16201,7 @@ package android.media { method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener, android.os.Handler); method public int setPlaybackRate(int); method public int setPositionNotificationPeriod(int); + method public boolean setPreferredOutputDevice(android.media.AudioDeviceInfo); method protected deprecated void setState(int); method public deprecated int setStereoVolume(float, float); method public int setVolume(float); @@ -16664,6 +16785,11 @@ package android.media { ctor public MediaCryptoException(java.lang.String); } + public abstract interface MediaDataSource implements java.io.Closeable { + method public abstract long getSize(); + method public abstract int readAt(long, byte[], int); + } + public class MediaDescription implements android.os.Parcelable { method public int describeContents(); method public java.lang.CharSequence getDescription(); @@ -16800,6 +16926,7 @@ package android.media { method public final void release(); method public void seekTo(long, int); method public void selectTrack(int); + method public final void setDataSource(android.media.MediaDataSource) throws java.io.IOException, java.lang.IllegalArgumentException; method public final void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException; method public final void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException; method public final void setDataSource(java.lang.String) throws java.io.IOException; @@ -16979,6 +17106,7 @@ package android.media { method public void setDataSource(java.io.FileDescriptor, long, long) throws java.lang.IllegalArgumentException; method public void setDataSource(java.io.FileDescriptor) throws java.lang.IllegalArgumentException; method public void setDataSource(android.content.Context, android.net.Uri) throws java.lang.IllegalArgumentException, java.lang.SecurityException; + method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException; field public static final int METADATA_KEY_ALBUM = 1; // 0x1 field public static final int METADATA_KEY_ALBUMARTIST = 13; // 0xd field public static final int METADATA_KEY_ARTIST = 2; // 0x2 @@ -17063,6 +17191,7 @@ package android.media { method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException; method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; + method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException; method public void setDisplay(android.view.SurfaceHolder); method public void setLooping(boolean); method public void setNextMediaPlayer(android.media.MediaPlayer); @@ -17387,7 +17516,7 @@ package android.media { public final class MediaSync { ctor public MediaSync(); - method public void configureAudioTrack(android.media.AudioTrack, int); + method public void configureAudioTrack(android.media.AudioTrack); method public void configureSurface(android.view.Surface); method public final android.view.Surface createInputSurface(); method public boolean getTimestamp(android.media.MediaTimestamp); @@ -17423,6 +17552,10 @@ package android.media { ctor public NotProvisionedException(java.lang.String); } + public abstract interface OnAudioDeviceConnectionListener { + method public abstract void onAudioDeviceConnection(); + } + public final class Rating implements android.os.Parcelable { method public int describeContents(); method public float getPercentRating(); @@ -19476,6 +19609,7 @@ package android.net { public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(android.net.Network); + method public android.net.Network getActiveNetwork(); method public android.net.NetworkInfo getActiveNetworkInfo(); method public android.net.NetworkInfo[] getAllNetworkInfo(); method public android.net.Network[] getAllNetworks(); @@ -19494,7 +19628,8 @@ package android.net { method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void releaseNetworkRequest(android.app.PendingIntent); method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); - method public void reportBadNetwork(android.net.Network); + method public deprecated void reportBadNetwork(android.net.Network); + method public void reportNetworkConnectivity(android.net.Network, boolean); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated boolean requestRouteToHost(int, int); @@ -21440,6 +21575,7 @@ package android.nfc { public final class NfcEvent { field public final android.nfc.NfcAdapter nfcAdapter; + field public final byte peerLlcpVersion; } public final class NfcManager { @@ -24603,6 +24739,8 @@ package android.os { method public static long getNativeHeapFreeSize(); method public static long getNativeHeapSize(); method public static long getPss(); + method public static java.lang.String getRuntimeStat(java.lang.String); + method public static java.util.Map<java.lang.String, java.lang.String> getRuntimeStats(); method public static deprecated int getThreadAllocCount(); method public static deprecated int getThreadAllocSize(); method public static deprecated int getThreadExternalAllocCount(); @@ -28467,6 +28605,7 @@ package android.provider { field public static final java.lang.String DEBUG_APP = "debug_app"; field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled"; field public static final java.lang.String DEVICE_PROVISIONED = "device_provisioned"; + field public static final java.lang.String HIDE_CARRIER_NETWORK_SETTINGS = "hide_carrier_network_settings"; field public static final java.lang.String HTTP_PROXY = "http_proxy"; field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; field public static final java.lang.String MODE_RINGER = "mode_ringer"; @@ -28633,6 +28772,7 @@ package android.provider { field public static final android.net.Uri DEFAULT_RINGTONE_URI; field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned"; field public static final deprecated java.lang.String DIM_SCREEN = "dim_screen"; + field public static final java.lang.String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; field public static final java.lang.String DTMF_TONE_WHEN_DIALING = "dtmf_tone"; field public static final java.lang.String END_BUTTON_BEHAVIOR = "end_button_behavior"; field public static final java.lang.String FONT_SCALE = "font_scale"; @@ -28681,6 +28821,7 @@ package android.provider { field public static final java.lang.String USER_ROTATION = "user_rotation"; field public static final deprecated java.lang.String USE_GOOGLE_MAIL = "use_google_mail"; field public static final java.lang.String VIBRATE_ON = "vibrate_on"; + field public static final java.lang.String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; field public static final deprecated java.lang.String WAIT_FOR_DEBUGGER = "wait_for_debugger"; field public static final deprecated java.lang.String WALLPAPER_ACTIVITY = "wallpaper_activity"; field public static final deprecated java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; @@ -29212,7 +29353,6 @@ package android.renderscript { method public static android.renderscript.AllocationAdapter create1D(android.renderscript.RenderScript, android.renderscript.Allocation); method public static android.renderscript.AllocationAdapter create2D(android.renderscript.RenderScript, android.renderscript.Allocation); method public static android.renderscript.AllocationAdapter createTyped(android.renderscript.RenderScript, android.renderscript.Allocation, android.renderscript.Type); - method public void setArray(int, int); method public void setFace(android.renderscript.Type.CubemapFace); method public void setLOD(int); method public void setX(int); @@ -29873,6 +30013,62 @@ package android.renderscript { method public void setLUT(android.renderscript.Allocation); } + public final class ScriptIntrinsicBLAS extends android.renderscript.ScriptIntrinsic { + method public void BNNM(android.renderscript.Allocation, int, android.renderscript.Allocation, int, android.renderscript.Allocation, int, int); + method public void CGEMM(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CHEMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CHER2K(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CHERK(int, int, float, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void CSYMM(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CSYR2K(int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Float2, android.renderscript.Allocation); + method public void CSYRK(int, int, float, float, android.renderscript.Allocation, float, float, android.renderscript.Allocation); + method public void CTRMM(int, int, int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void CTRSM(int, int, int, int, android.renderscript.Float2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void DGEMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYR2K(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DSYRK(int, int, double, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void DTRMM(int, int, int, int, double, android.renderscript.Allocation, android.renderscript.Allocation); + method public void DTRSM(int, int, int, int, double, android.renderscript.Allocation, android.renderscript.Allocation); + method public void SGEMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYMM(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYR2K(int, int, float, android.renderscript.Allocation, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void SSYRK(int, int, float, android.renderscript.Allocation, float, android.renderscript.Allocation); + method public void STRMM(int, int, int, int, float, android.renderscript.Allocation, android.renderscript.Allocation); + method public void STRSM(int, int, int, int, float, android.renderscript.Allocation, android.renderscript.Allocation); + method public void ZGEMM(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZHEMM(int, int, double, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZHER2K(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZHERK(int, int, double, android.renderscript.Allocation, double, android.renderscript.Allocation); + method public void ZSYMM(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZSYR2K(int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation, android.renderscript.Double2, android.renderscript.Allocation); + method public void ZSYRK(int, int, double, double, android.renderscript.Allocation, double, double, android.renderscript.Allocation); + method public void ZTRMM(int, int, int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation); + method public void ZTRSM(int, int, int, int, android.renderscript.Double2, android.renderscript.Allocation, android.renderscript.Allocation); + method public static android.renderscript.ScriptIntrinsicBLAS create(android.renderscript.RenderScript); + field public static final int CONJ_TRANSPOSE = 113; // 0x71 + field public static final int LEFT = 141; // 0x8d + field public static final int LOWER = 122; // 0x7a + field public static final int NON_UNIT = 131; // 0x83 + field public static final int NO_TRANSPOSE = 111; // 0x6f + field public static final int RIGHT = 142; // 0x8e + field public static final int TRANSPOSE = 112; // 0x70 + field public static final int UNIT = 132; // 0x84 + field public static final int UPPER = 121; // 0x79 + } + + public static abstract class ScriptIntrinsicBLAS.Diag implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Side implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Transpose implements java.lang.annotation.Annotation { + } + + public static abstract class ScriptIntrinsicBLAS.Uplo implements java.lang.annotation.Annotation { + } + public class ScriptIntrinsicBlend extends android.renderscript.ScriptIntrinsic { method public static android.renderscript.ScriptIntrinsicBlend create(android.renderscript.RenderScript, android.renderscript.Element); method public void forEachAdd(android.renderscript.Allocation, android.renderscript.Allocation); @@ -30035,8 +30231,6 @@ package android.renderscript { method public static android.renderscript.Type createX(android.renderscript.RenderScript, android.renderscript.Element, int); method public static android.renderscript.Type createXY(android.renderscript.RenderScript, android.renderscript.Element, int, int); method public static android.renderscript.Type createXYZ(android.renderscript.RenderScript, android.renderscript.Element, int, int, int); - method public int getArray(int); - method public int getArrayCount(); method public int getCount(); method public android.renderscript.Element getElement(); method public int getX(); @@ -30050,7 +30244,6 @@ package android.renderscript { public static class Type.Builder { ctor public Type.Builder(android.renderscript.RenderScript, android.renderscript.Element); method public android.renderscript.Type create(); - method public android.renderscript.Type.Builder setArray(int, int); method public android.renderscript.Type.Builder setFaces(boolean); method public android.renderscript.Type.Builder setMipmaps(boolean); method public android.renderscript.Type.Builder setX(int); @@ -30286,6 +30479,7 @@ package android.security { public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 + field public static final int UNKNOWN = 4; // 0x4 } public static abstract class KeyStoreKeyProperties.OriginEnum implements java.lang.annotation.Annotation { @@ -30384,6 +30578,26 @@ package android.security { package android.service.carrier { + public abstract class CarrierConfigService extends android.app.Service { + ctor public CarrierConfigService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract android.os.Bundle onLoadConfig(android.service.carrier.CarrierIdentifier); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.carrier.CarrierConfigService"; + } + + public class CarrierIdentifier implements android.os.Parcelable { + ctor public CarrierIdentifier(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); + method public int describeContents(); + method public java.lang.String getGid1(); + method public java.lang.String getGid2(); + method public java.lang.String getImsi(); + method public java.lang.String getMcc(); + method public java.lang.String getMnc(); + method public java.lang.String getSpn(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR; + } + public abstract class CarrierMessagingService extends android.app.Service { ctor public CarrierMessagingService(); method public android.os.IBinder onBind(android.content.Intent); @@ -30595,6 +30809,7 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final android.app.NotificationManager.Policy.Token getNotificationPolicyToken(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); @@ -30728,7 +30943,8 @@ package android.service.trust { public class TrustAgentService extends android.app.Service { ctor public TrustAgentService(); - method public final void grantTrust(java.lang.CharSequence, long, boolean); + method public final deprecated void grantTrust(java.lang.CharSequence, long, boolean); + method public final void grantTrust(java.lang.CharSequence, long, int); method public final android.os.IBinder onBind(android.content.Intent); method public boolean onConfigure(java.util.List<android.os.PersistableBundle>); method public void onDeviceLocked(); @@ -30737,6 +30953,8 @@ package android.service.trust { method public void onUnlockAttempt(boolean); method public final void revokeTrust(); method public final void setManagingTrust(boolean); + field public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 2; // 0x2 + field public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1; // 0x1 field public static final java.lang.String SERVICE_INTERFACE = "android.service.trust.TrustAgentService"; field public static final java.lang.String TRUST_AGENT_META_DATA = "android.service.trust.trustagent"; } @@ -31838,6 +32056,7 @@ package android.telecom { public final class AudioState implements android.os.Parcelable { ctor public AudioState(boolean, int, int); ctor public AudioState(android.telecom.AudioState); + method public static java.lang.String audioRouteToString(int); method public int describeContents(); method public int getRoute(); method public int getSupportedRouteMask(); @@ -31852,7 +32071,7 @@ package android.telecom { } public final class Call { - method public void addListener(android.telecom.Call.Listener); + method public deprecated void addListener(android.telecom.Call.Listener); method public void answer(int); method public void conference(android.telecom.Call); method public void disconnect(); @@ -31863,17 +32082,21 @@ package android.telecom { method public android.telecom.Call getParent(); method public java.lang.String getRemainingPostDialSequence(); method public int getState(); + method public android.telecom.InCallService.VideoCall getVideoCall(); method public void hold(); method public void mergeConference(); method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean); method public void playDtmfTone(char); method public void postDialContinue(boolean); + method public void registerCallback(android.telecom.Call.Callback); method public void reject(boolean, java.lang.String); - method public void removeListener(android.telecom.Call.Listener); + method public deprecated void removeListener(android.telecom.Call.Listener); method public void splitFromConference(); method public void stopDtmfTone(); method public void swapConference(); method public void unhold(); + method public void unregisterCallback(android.telecom.Call.Callback); + field public static final java.lang.String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; field public static final int STATE_ACTIVE = 4; // 0x4 field public static final int STATE_CONNECTING = 9; // 0x9 field public static final int STATE_DIALING = 1; // 0x1 @@ -31885,7 +32108,22 @@ package android.telecom { field public static final int STATE_RINGING = 2; // 0x2 } + public static abstract class Call.Callback { + ctor public Call.Callback(); + method public void onCallDestroyed(android.telecom.Call); + method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>); + method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>); + method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>); + method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); + method public void onParentChanged(android.telecom.Call, android.telecom.Call); + method public void onPostDialWait(android.telecom.Call, java.lang.String); + method public void onStateChanged(android.telecom.Call, int); + method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall); + } + public static class Call.Details { + method public static boolean can(int, int); + method public boolean can(int); method public static java.lang.String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); @@ -31901,26 +32139,33 @@ package android.telecom { method public android.telecom.StatusHints getStatusHints(); method public int getVideoState(); field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000 + field public static final int CAPABILITY_GENERIC_CONFERENCE = 16384; // 0x4000 + field public static final int CAPABILITY_HIGH_DEF_AUDIO = 32768; // 0x8000 field public static final int CAPABILITY_HOLD = 1; // 0x1 field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80 field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4 field public static final int CAPABILITY_MUTE = 64; // 0x40 field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20 field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000 + field public static final int CAPABILITY_SHOW_CALLBACK_NUMBER = 131072; // 0x20000 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_RX = 256; // 0x100 + field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_TX = 512; // 0x200 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = 3072; // 0xc00 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 1024; // 0x400 + field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int CAPABILITY_WIFI = 65536; // 0x10000 } - public static abstract class Call.Listener { + public static abstract deprecated class Call.Listener extends android.telecom.Call.Callback { ctor public Call.Listener(); - method public void onCallDestroyed(android.telecom.Call); - method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>); - method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>); - method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>); - method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); - method public void onParentChanged(android.telecom.Call, android.telecom.Call); - method public void onPostDialWait(android.telecom.Call, java.lang.String); - method public void onStateChanged(android.telecom.Call, int); + } + + public class CallProperties { + ctor public CallProperties(); + field public static final int CONFERENCE = 1; // 0x1 } public final class CallState { @@ -31937,13 +32182,22 @@ package android.telecom { field public static final int RINGING = 4; // 0x4 } + public final class CameraCapabilities implements android.os.Parcelable { + ctor public CameraCapabilities(int, int); + method public int describeContents(); + method public int getHeight(); + method public int getWidth(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.CameraCapabilities> CREATOR; + } + public abstract class Conference implements android.telecom.IConferenceable { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); method public final void destroy(); method public final android.telecom.AudioState getAudioState(); method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); - method public long getConnectTimeMillis(); + method public final long getConnectTimeMillis(); method public final int getConnectionCapabilities(); method public final java.util.List<android.telecom.Connection> getConnections(); method public final android.telecom.DisconnectCause getDisconnectCause(); @@ -31989,8 +32243,8 @@ package android.telecom { method public final android.telecom.DisconnectCause getDisconnectCause(); method public final int getState(); method public final android.telecom.StatusHints getStatusHints(); + method public final android.telecom.Connection.VideoProvider getVideoProvider(); method public final boolean isRingbackRequested(); - method protected void notifyConferenceStarted(); method public void onAbort(); method public void onAnswer(); method public void onAudioStateChanged(android.telecom.AudioState); @@ -32040,6 +32294,38 @@ package android.telecom { field public static final int STATE_RINGING = 2; // 0x2 } + public static abstract class Connection.VideoProvider { + ctor public Connection.VideoProvider(); + method public void changeCallDataUsage(long); + method public void changeCameraCapabilities(android.telecom.CameraCapabilities); + method public void changePeerDimensions(int, int); + method public void changeVideoQuality(int); + method public void handleCallSessionEvent(int); + method public abstract void onRequestCameraCapabilities(); + method public abstract void onRequestConnectionDataUsage(); + method public abstract void onSendSessionModifyRequest(android.telecom.VideoProfile); + method public abstract void onSendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void onSetCamera(java.lang.String); + method public abstract void onSetDeviceOrientation(int); + method public abstract void onSetDisplaySurface(android.view.Surface); + method public abstract void onSetPauseImage(java.lang.String); + method public abstract void onSetPreviewSurface(android.view.Surface); + method public abstract void onSetZoom(float); + method public void receiveSessionModifyRequest(android.telecom.VideoProfile); + method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5 + field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6 + field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1 + field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2 + field public static final int SESSION_EVENT_TX_START = 3; // 0x3 + field public static final int SESSION_EVENT_TX_STOP = 4; // 0x4 + field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2 + field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3 + field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5 + field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1 + field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4 + } + public final class ConnectionRequest implements android.os.Parcelable { ctor public ConnectionRequest(android.telecom.PhoneAccountHandle, android.net.Uri, android.os.Bundle); method public int describeContents(); @@ -32109,14 +32395,50 @@ package android.telecom { public abstract class InCallService extends android.app.Service { ctor public InCallService(); - method public final android.telecom.Phone getPhone(); + method public final boolean canAddCall(); + method public final android.telecom.AudioState getAudioState(); + method public final java.util.List<android.telecom.Call> getCalls(); + method public deprecated android.telecom.Phone getPhone(); + method public void onAudioStateChanged(android.telecom.AudioState); method public android.os.IBinder onBind(android.content.Intent); - method public void onPhoneCreated(android.telecom.Phone); - method public void onPhoneDestroyed(android.telecom.Phone); + method public void onBringToForeground(boolean); + method public void onCallAdded(android.telecom.Call); + method public void onCallRemoved(android.telecom.Call); + method public void onCanAddCallChanged(boolean); + method public deprecated void onPhoneCreated(android.telecom.Phone); + method public deprecated void onPhoneDestroyed(android.telecom.Phone); + method public final void setAudioRoute(int); + method public final void setMuted(boolean); field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService"; } - public final class Phone { + public static abstract class InCallService.VideoCall { + ctor public InCallService.VideoCall(); + method public abstract void registerCallback(android.telecom.InCallService.VideoCall.Callback); + method public abstract void requestCallDataUsage(); + method public abstract void requestCameraCapabilities(); + method public abstract void sendSessionModifyRequest(android.telecom.VideoProfile); + method public abstract void sendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void setCamera(java.lang.String); + method public abstract void setDeviceOrientation(int); + method public abstract void setDisplaySurface(android.view.Surface); + method public abstract void setPauseImage(java.lang.String); + method public abstract void setPreviewSurface(android.view.Surface); + method public abstract void setZoom(float); + } + + public static abstract class InCallService.VideoCall.Callback { + ctor public InCallService.VideoCall.Callback(); + method public abstract void onCallDataUsageChanged(long); + method public abstract void onCallSessionEvent(int); + method public abstract void onCameraCapabilitiesChanged(android.telecom.CameraCapabilities); + method public abstract void onPeerDimensionsChanged(int, int); + method public abstract void onSessionModifyRequestReceived(android.telecom.VideoProfile); + method public abstract void onSessionModifyResponseReceived(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + method public abstract void onVideoQualityChanged(int); + } + + public final deprecated class Phone { method public final void addListener(android.telecom.Phone.Listener); method public final boolean canAddCall(); method public final android.telecom.AudioState getAudioState(); @@ -32175,7 +32497,6 @@ package android.telecom { ctor public PhoneAccount.Builder(android.telecom.PhoneAccount); method public android.telecom.PhoneAccount.Builder addSupportedUriScheme(java.lang.String); method public android.telecom.PhoneAccount build(); - method public android.telecom.PhoneAccount.Builder setAccountHandle(android.telecom.PhoneAccountHandle); method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri); method public android.telecom.PhoneAccount.Builder setCapabilities(int); method public android.telecom.PhoneAccount.Builder setHighlightColor(int); @@ -32308,9 +32629,10 @@ package android.telecom { method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage(); method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String); method public java.util.List<android.telecom.PhoneAccountHandle> getRegisteredConnectionManagers(); + method public android.telecom.PhoneAccountHandle getSimCallManager(); + method public java.lang.String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method public boolean handleMmi(java.lang.String); method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle); - method public boolean hasVoiceMailNumber(android.telecom.PhoneAccountHandle); method public boolean isInCall(); method public boolean isRinging(); method public boolean isTtySupported(); @@ -32321,6 +32643,7 @@ package android.telecom { method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; + field public static final java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED"; field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS"; field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"; @@ -32376,6 +32699,24 @@ package android.telecom { package android.telephony { + public class CarrierConfigManager { + method public android.os.Bundle getConfig(); + method public android.os.Bundle getConfigForSubId(int); + method public static android.os.Bundle getDefaultConfig(); + method public void reloadCarrierConfigForSubId(int); + method public void updateConfigForPhoneId(int, java.lang.String); + field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_AVAILABLE = "bool_carrier_volte_available"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_PROVISIONED = "bool_carrier_volte_provisioned"; + field public static final java.lang.String BOOL_CARRIER_VOLTE_TTY_SUPPORTED = "bool_carrier_volte_tty_supported"; + field public static final java.lang.String BOOL_SHOW_APN_SETTING_CDMA = "bool_show_apn_setting_cdma"; + field public static final java.lang.String INT_VOLTE_REPLACEMENT_RAT = "int_volte_replacement_rat"; + field public static final java.lang.String SHORT_VVM_PORT_NUMBER = "string_vvm_port_number"; + field public static final java.lang.String STRING_VVM_DESTINATION_NUMBER = "string_vvm_destination_number"; + field public static final java.lang.String STRING_VVM_TYPE = "string_vvm_type"; + field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp"; + } + public final class CellIdentityCdma implements android.os.Parcelable { method public int describeContents(); method public int getBasestationId(); @@ -32865,6 +33206,7 @@ package android.telephony { method public boolean getDataEnabled(int); method public int getDataState(); method public java.lang.String getDeviceId(); + method public java.lang.String getDeviceId(int); method public java.lang.String getDeviceSoftwareVersion(); method public java.lang.String getGroupIdLevel1(); method public java.lang.String getLine1Number(); @@ -32875,6 +33217,7 @@ package android.telephony { method public java.lang.String getNetworkOperator(); method public java.lang.String getNetworkOperatorName(); method public int getNetworkType(); + method public int getPhoneCount(); method public int getPhoneType(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); @@ -32908,6 +33251,7 @@ package android.telephony { method public boolean isWorldPhone(); method public void listen(android.telephony.PhoneStateListener, int); method public boolean needsOtaServiceProvisioning(); + method public void notifyCarrierNetworkChange(boolean); method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void setDataEnabled(boolean); method public void setDataEnabled(int, boolean); @@ -36423,6 +36767,7 @@ package android.view { method public boolean onTouchEvent(android.view.MotionEvent); method public void setIsLongpressEnabled(boolean); method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener); + method public void setOnStylusButtonPressListener(android.view.GestureDetector.OnStylusButtonPressListener); } public static abstract interface GestureDetector.OnDoubleTapListener { @@ -36440,7 +36785,11 @@ package android.view { method public abstract boolean onSingleTapUp(android.view.MotionEvent); } - public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener { + public static abstract interface GestureDetector.OnStylusButtonPressListener { + method public abstract boolean onStylusButtonPress(android.view.MotionEvent); + } + + public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener android.view.GestureDetector.OnStylusButtonPressListener { ctor public GestureDetector.SimpleOnGestureListener(); method public boolean onDoubleTap(android.view.MotionEvent); method public boolean onDoubleTapEvent(android.view.MotionEvent); @@ -36451,6 +36800,7 @@ package android.view { method public void onShowPress(android.view.MotionEvent); method public boolean onSingleTapConfirmed(android.view.MotionEvent); method public boolean onSingleTapUp(android.view.MotionEvent); + method public boolean onStylusButtonPress(android.view.MotionEvent); } public class Gravity { @@ -38412,6 +38762,7 @@ package android.view { ctor public ViewGroup(android.content.Context, android.util.AttributeSet, int); ctor public ViewGroup(android.content.Context, android.util.AttributeSet, int, int); method public boolean addStatesFromChildren(); + method public void addTransientView(android.view.View, int); method public void addView(android.view.View); method public void addView(android.view.View, int); method public void addView(android.view.View, int, int); @@ -38463,6 +38814,9 @@ package android.view { method public int getNestedScrollAxes(); method public int getPersistentDrawingCache(); method public boolean getTouchscreenBlocksFocus(); + method public android.view.View getTransientView(int); + method public int getTransientViewCount(); + method public int getTransientViewIndex(int); method public int indexOfChild(android.view.View); method public final void invalidateChild(android.view.View, android.graphics.Rect); method public android.view.ViewParent invalidateChildInParent(int[], android.graphics.Rect); @@ -38496,6 +38850,7 @@ package android.view { method public void removeAllViews(); method public void removeAllViewsInLayout(); method protected void removeDetachedView(android.view.View, boolean); + method public void removeTransientView(android.view.View); method public void removeView(android.view.View); method public void removeViewAt(int); method public void removeViewInLayout(android.view.View); @@ -39101,6 +39456,7 @@ package android.view { field public static final int TITLE_CHANGED = 64; // 0x40 field public static final int TYPE_ACCESSIBILITY_OVERLAY = 2032; // 0x7f0 field public static final int TYPE_APPLICATION = 2; // 0x2 + field public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = 1005; // 0x3ed field public static final int TYPE_APPLICATION_ATTACHED_DIALOG = 1003; // 0x3eb field public static final int TYPE_APPLICATION_MEDIA = 1001; // 0x3e9 field public static final int TYPE_APPLICATION_PANEL = 1000; // 0x3e8 @@ -43512,6 +43868,7 @@ package android.widget { method public int getInputType(); method public final android.text.method.KeyListener getKeyListener(); method public final android.text.Layout getLayout(); + method public int[] getLeftIndents(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); method public int getLineCount(); @@ -43534,6 +43891,7 @@ package android.widget { method public android.text.TextPaint getPaint(); method public int getPaintFlags(); method public java.lang.String getPrivateImeOptions(); + method public int[] getRightIndents(); method public int getSelectionEnd(); method public int getSelectionStart(); method public int getShadowColor(); @@ -43611,6 +43969,7 @@ package android.widget { method public void setImeActionLabel(java.lang.CharSequence, int); method public void setImeOptions(int); method public void setIncludeFontPadding(boolean); + method public void setIndents(int[], int[]); method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public void setInputType(int); method public void setKeyListener(android.text.method.KeyListener); @@ -60219,3 +60578,4 @@ package org.xmlpull.v1.sax2 { } } + diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index fa28143a0210..8ba2a5ac6b55 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -857,6 +857,11 @@ public class Am extends BaseCommand { "Error: Activity not started, voice control not allowed for: " + intent); break; + case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY: + out.println( + "Error: Not allowed to start background user activity" + + " that shouldn't be displayed for all users."); + break; default: out.println( "Error: Activity not started, unknown error code " + res); diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java index 815a0ac999eb..64f023f16abf 100644 --- a/cmds/wm/src/com/android/commands/wm/Wm.java +++ b/cmds/wm/src/com/android/commands/wm/Wm.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.os.ServiceManager; import android.util.AndroidException; +import android.util.DisplayMetrics; import android.view.Display; import android.view.IWindowManager; import com.android.internal.os.BaseCommand; @@ -45,21 +46,27 @@ public class Wm extends BaseCommand { (new Wm()).run(args); } + @Override public void onShowUsage(PrintStream out) { out.println( "usage: wm [subcommand] [options]\n" + - " wm size [reset|WxH]\n" + + " wm size [reset|WxH|WdpxHdp]\n" + " wm density [reset|DENSITY]\n" + " wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]\n" + + " wm scaling [off|auto]\n" + "\n" + "wm size: return or override display size.\n" + + " width and height in pixels unless suffixed with 'dp'.\n" + "\n" + "wm density: override display density.\n" + "\n" + - "wm overscan: set overscan area for display.\n" + "wm overscan: set overscan area for display.\n" + + "\n" + + "wm scaling: set display scaling mode.\n" ); } + @Override public void onRun() throws Exception { mWm = IWindowManager.Stub.asInterface(ServiceManager.checkService( Context.WINDOW_SERVICE)); @@ -76,6 +83,8 @@ public class Wm extends BaseCommand { runDisplayDensity(); } else if (op.equals("overscan")) { runDisplayOverscan(); + } else if (op.equals("scaling")) { + runDisplayScaling(); } else { showError("Error: unknown command '" + op + "'"); return; @@ -85,6 +94,7 @@ public class Wm extends BaseCommand { private void runDisplaySize() throws Exception { String size = nextArg(); int w, h; + boolean scale = true; if (size == null) { Point initialSize = new Point(); Point baseSize = new Point(); @@ -109,8 +119,8 @@ public class Wm extends BaseCommand { String wstr = size.substring(0, div); String hstr = size.substring(div+1); try { - w = Integer.parseInt(wstr); - h = Integer.parseInt(hstr); + w = parseDimension(wstr); + h = parseDimension(hstr); } catch (NumberFormatException e) { System.err.println("Error: bad number " + e); return; @@ -193,4 +203,32 @@ public class Wm extends BaseCommand { } catch (RemoteException e) { } } + + private void runDisplayScaling() throws Exception { + String scalingStr = nextArgRequired(); + if ("auto".equals(scalingStr)) { + mWm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 0); + } else if ("off".equals(scalingStr)) { + mWm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1); + } else { + System.err.println("Error: scaling must be 'auto' or 'off'"); + } + } + + private int parseDimension(String s) throws NumberFormatException { + if (s.endsWith("px")) { + return Integer.parseInt(s.substring(0, s.length() - 2)); + } + if (s.endsWith("dp")) { + int density; + try { + density = mWm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY); + } catch (RemoteException e) { + density = DisplayMetrics.DENSITY_DEFAULT; + } + return Integer.parseInt(s.substring(0, s.length() - 2)) * density / + DisplayMetrics.DENSITY_DEFAULT; + } + return Integer.parseInt(s); + } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6cf648174570..4ccde1cb9bf4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6434,18 +6434,22 @@ public class Activity extends ContextThemeWrapper * Request to put this Activity in a mode where the user is locked to the * current task. * - * This will prevent the user from launching other apps, going to settings, - * or reaching the home screen. + * This will prevent the user from launching other apps, going to settings, or reaching the + * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode} + * values permit launching while locked. * - * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true - * for this component then the app will go directly into Lock Task mode. The user - * will not be able to exit this mode until {@link Activity#stopLockTask()} is called. + * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or + * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into + * Lock Task mode. The user will not be able to exit this mode until + * {@link Activity#stopLockTask()} is called. * * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false * then the system will prompt the user with a dialog requesting permission to enter * this mode. When entered through this method the user can exit at any time through * an action described by the request dialog. Calling stopLockTask will also exit the * mode. + * + * @see android.R.attr#lockTaskMode */ public void startLockTask() { try { @@ -6462,6 +6466,14 @@ public class Activity extends ContextThemeWrapper * startLockTask previously. * * This will allow the user to exit this app and move onto other activities. + * <p>Note: This method should only be called when the activity is user-facing. That is, + * between onResume() and onPause(). + * <p>Note: If there are other tasks below this one that are also locked then calling this + * method will immediately finish this task and resume the previous locked one, remaining in + * lockTask mode. + * + * @see android.R.attr#lockTaskMode + * @see ActivityManager#getLockTaskModeState() */ public void stopLockTask() { try { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 51ececcba7e9..576a046e081e 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -86,6 +86,13 @@ public class ActivityManager { public static final String META_HOME_ALTERNATE = "android.app.home.alternate"; /** + * Result for IActivityManager.startActivity: trying to start a background user + * activity that shouldn't be displayed for all users. + * @hide + */ + public static final int START_NOT_CURRENT_USER_ACTIVITY = -8; + + /** * Result for IActivityManager.startActivity: trying to start an activity under voice * control when that activity does not support the VOICE category. * @hide @@ -862,6 +869,23 @@ public class ActivityManager { */ public int affiliatedTaskColor; + /** + * The component launched as the first activity in the task. + * This can be considered the "application" of this task. + */ + public ComponentName baseActivity; + + /** + * The activity component at the top of the history stack of the task. + * This is what the user is currently doing. + */ + public ComponentName topActivity; + + /** + * Number of activities in this task. + */ + public int numActivities; + public RecentTaskInfo() { } @@ -895,6 +919,9 @@ public class ActivityManager { dest.writeLong(lastActiveTime); dest.writeInt(affiliatedTaskId); dest.writeInt(affiliatedTaskColor); + ComponentName.writeToParcel(baseActivity, dest); + ComponentName.writeToParcel(topActivity, dest); + dest.writeInt(numActivities); } public void readFromParcel(Parcel source) { @@ -911,6 +938,9 @@ public class ActivityManager { lastActiveTime = source.readLong(); affiliatedTaskId = source.readInt(); affiliatedTaskColor = source.readInt(); + baseActivity = ComponentName.readFromParcel(source); + topActivity = ComponentName.readFromParcel(source); + numActivities = source.readInt(); } public static final Creator<RecentTaskInfo> CREATOR diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index fe6e4f36d99d..b9ddff0b06d1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -67,6 +67,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.util.ArrayMap; import android.util.Log; import android.view.Display; @@ -1427,6 +1429,61 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + if (app.isInternal()) { + return Preconditions.checkNotNull( + storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)); + } else if (app.isExternalAsec()) { + final List<VolumeInfo> vols = storage.getVolumes(); + for (VolumeInfo vol : vols) { + if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) { + return vol; + } + } + throw new IllegalStateException("Failed to find primary public volume"); + } else { + return Preconditions.checkNotNull(storage.findVolumeByUuid(app.volumeUuid)); + } + } + + @Override + public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final List<VolumeInfo> vols = storage.getVolumes(); + final List<VolumeInfo> candidates = new ArrayList<>(); + for (VolumeInfo vol : vols) { + if (isCandidateVolume(app, vol)) { + candidates.add(vol); + } + } + return candidates; + } + + private static boolean isCandidateVolume(ApplicationInfo app, VolumeInfo vol) { + // Private internal is always an option + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) { + return true; + } + + // System apps and apps demanding internal storage can't be moved + // anywhere else + if (app.isSystemApp() + || app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { + return false; + } + + // Moving into an ASEC on public primary is only an option when app is + // internal, or already in ASEC + if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) { + return app.isInternal() || app.isExternalAsec(); + } + + // Otherwise we can move to any private volume + return (vol.getType() == VolumeInfo.TYPE_PRIVATE); + } + + @Override public String getInstallerPackageName(String packageName) { try { return mPM.getInstallerPackageName(packageName); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e2230da8c5a1..913159ae8b1f 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -17,8 +17,10 @@ package android.app; +import android.app.INotificationManagerCallback; import android.app.ITransientNotification; import android.app.Notification; +import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -71,6 +73,7 @@ interface INotificationManager void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter); int getInterruptionFilterFromListener(in INotificationListener token); void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); + NotificationManager.Policy.Token getPolicyTokenFromListener(in INotificationListener listener); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); @@ -82,4 +85,8 @@ interface INotificationManager oneway void setZenMode(int mode, in Uri conditionId, String reason); oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions); oneway void requestZenModeConditions(in IConditionListener callback, int relevance); + oneway void requestNotificationPolicyToken(String pkg, in INotificationManagerCallback callback); + boolean isNotificationPolicyTokenValid(String pkg, in NotificationManager.Policy.Token token); + NotificationManager.Policy getNotificationPolicy(in NotificationManager.Policy.Token token); + void setNotificationPolicy(in NotificationManager.Policy.Token token, in NotificationManager.Policy policy); } diff --git a/packages/SystemUI/res/drawable/ic_audio_phone.xml b/core/java/android/app/INotificationManagerCallback.aidl index 64147f2b8361..b9414caa4b6b 100644 --- a/packages/SystemUI/res/drawable/ic_audio_phone.xml +++ b/core/java/android/app/INotificationManagerCallback.aidl @@ -1,7 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* - * Copyright 2014, The Android Open Source Project +/** + * Copyright (c) 2015, 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. @@ -15,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@*android:drawable/ic_audio_phone_am_alpha" - android:autoMirrored="true" - android:tint="#ffffffff" />
\ No newline at end of file +package android.app; + +import android.app.NotificationManager; + +/** @hide */ +oneway interface INotificationManagerCallback { + void onPolicyToken(in NotificationManager.Policy.Token token); +} diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index fc9646486b1f..b77dec537b6c 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1797,6 +1797,10 @@ public class Instrumentation { case ActivityManager.START_NOT_VOICE_COMPATIBLE: throw new SecurityException( "Starting under voice control not allowed for: " + intent); + case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY: + throw new SecurityException( + "Not allowed to start background user activity that shouldn't be" + + " displayed for all users."); default: throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent); diff --git a/core/res/res/drawable/ic_audio_phone.xml b/core/java/android/app/NotificationManager.aidl index 1bab863b426e..8380b8df5e29 100644 --- a/core/res/res/drawable/ic_audio_phone.xml +++ b/core/java/android/app/NotificationManager.aidl @@ -1,7 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* - * Copyright 2013, The Android Open Source Project +/** + * Copyright (c) 2015, 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. @@ -15,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_phone_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> +package android.app; + +parcelable NotificationManager.Policy; +parcelable NotificationManager.Policy.Token;
\ No newline at end of file diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index fa61e184900f..7133dce9cd90 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -16,14 +16,19 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.app.Notification.Builder; +import android.app.NotificationManager.Policy.Token; import android.content.ComponentName; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -33,6 +38,8 @@ import android.service.notification.IConditionListener; import android.service.notification.ZenModeConfig; import android.util.Log; +import java.util.Objects; + /** * Class to notify the user of events that happen. This is how you tell * the user that something has happened in the background. {@more} @@ -89,6 +96,14 @@ public class NotificationManager public static final String ACTION_EFFECTS_SUPPRESSOR_CHANGED = "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED"; + /** + * Intent that is broadcast when the state of getNotificationPolicy() changes. + * This broadcast is only sent to registered receivers. + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_POLICY_CHANGED + = "android.app.action.NOTIFICATION_POLICY_CHANGED"; + private static INotificationManager sService; /** @hide */ @@ -338,5 +353,293 @@ public class NotificationManager return null; } + /** + * Requests a notification policy token for the calling package. + * + * @param callback required, used to receive the granted token or the deny signal. + * @param handler The handler used when receiving the result. + * If null, the current thread is used. + */ + public void requestNotificationPolicyToken(@NonNull final Policy.Token.RequestCallback callback, + @Nullable Handler handler) { + checkRequired("callback", callback); + final Handler h = handler != null ? handler : new Handler(); + INotificationManager service = getService(); + try { + service.requestNotificationPolicyToken(mContext.getOpPackageName(), + new INotificationManagerCallback.Stub() { + @Override + public void onPolicyToken(final Token token) throws RemoteException { + h.post(new Runnable() { + @Override + public void run() { + if (token != null) { + callback.onTokenGranted(token); + } else { + callback.onTokenDenied(); + } + } + }); + } + }); + } catch (RemoteException e) { + } + } + + /** + * Checks a given notification policy token. + * + * Returns true if the token is still valid for managing policy. + */ + public boolean isNotificationPolicyTokenValid(@NonNull Policy.Token token) { + if (token == null) return false; + INotificationManager service = getService(); + try { + return service.isNotificationPolicyTokenValid(mContext.getOpPackageName(), token); + } catch (RemoteException e) { + } + return false; + } + + /** + * Gets the current notification policy. + * + * @param token A valid notification policy token is required to access the current policy. + */ + public Policy getNotificationPolicy(@NonNull Policy.Token token) { + checkRequired("token", token); + INotificationManager service = getService(); + try { + return service.getNotificationPolicy(token); + } catch (RemoteException e) { + } + return null; + } + + /** + * Sets the current notification policy. + * + * @param token A valid notification policy token is required to modify the current policy. + * @param policy The new desired policy. + */ + public void setNotificationPolicy(@NonNull Policy.Token token, @NonNull Policy policy) { + checkRequired("token", token); + checkRequired("policy", policy); + INotificationManager service = getService(); + try { + service.setNotificationPolicy(token, policy); + } catch (RemoteException e) { + } + } + private Context mContext; + + private static void checkRequired(String name, Object value) { + if (value == null) { + throw new IllegalArgumentException(name + " is required"); + } + } + + /** + * Notification policy configuration. Represents user-preferences for notification + * filtering and prioritization. + */ + public static class Policy implements android.os.Parcelable { + /** Reminder notifications are prioritized. */ + public static final int PRIORITY_CATEGORY_REMINDERS = 1 << 0; + /** Event notifications are prioritized. */ + public static final int PRIORITY_CATEGORY_EVENTS = 1 << 1; + /** Message notifications are prioritized. */ + public static final int PRIORITY_CATEGORY_MESSAGES = 1 << 2; + /** Calls are prioritized. */ + public static final int PRIORITY_CATEGORY_CALLS = 1 << 3; + /** Calls from repeat callers are prioritized. */ + public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4; + + private static final int[] ALL_PRIORITY_CATEGORIES = { + PRIORITY_CATEGORY_REMINDERS, + PRIORITY_CATEGORY_EVENTS, + PRIORITY_CATEGORY_MESSAGES, + PRIORITY_CATEGORY_CALLS, + PRIORITY_CATEGORY_REPEAT_CALLERS, + }; + + /** Any sender is prioritized. */ + public static final int PRIORITY_SENDERS_ANY = 0; + /** Saved contacts are prioritized. */ + public static final int PRIORITY_SENDERS_CONTACTS = 1; + /** Only starred contacts are prioritized. */ + public static final int PRIORITY_SENDERS_STARRED = 2; + + /** Notification categories to prioritize. Bitmask of PRIORITY_CATEGORY_* constants. */ + public final int priorityCategories; + + /** Notification senders to prioritize. One of: + * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */ + public final int prioritySenders; + + public Policy(int priorityCategories, int prioritySenders) { + this.priorityCategories = priorityCategories; + this.prioritySenders = prioritySenders; + } + + /** @hide */ + public Policy(Parcel source) { + this(source.readInt(), source.readInt()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(priorityCategories); + dest.writeInt(prioritySenders); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + return Objects.hash(priorityCategories, prioritySenders); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Policy)) return false; + if (o == this) return true; + final Policy other = (Policy) o; + return other.priorityCategories == priorityCategories + && other.prioritySenders == prioritySenders; + } + + @Override + public String toString() { + return "NotificationManager.Policy[" + + "priorityCategories=" + priorityCategoriesToString(priorityCategories) + + ",prioritySenders=" + prioritySendersToString(prioritySenders) + + "]"; + } + + public static String priorityCategoriesToString(int priorityCategories) { + if (priorityCategories == 0) return ""; + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ALL_PRIORITY_CATEGORIES.length; i++) { + final int priorityCategory = ALL_PRIORITY_CATEGORIES[i]; + if ((priorityCategories & priorityCategory) != 0) { + if (sb.length() > 0) sb.append(','); + sb.append(priorityCategoryToString(priorityCategory)); + } + priorityCategories &= ~priorityCategory; + } + if (priorityCategories != 0) { + if (sb.length() > 0) sb.append(','); + sb.append("PRIORITY_CATEGORY_UNKNOWN_").append(priorityCategories); + } + return sb.toString(); + } + + private static String priorityCategoryToString(int priorityCategory) { + switch (priorityCategory) { + case PRIORITY_CATEGORY_REMINDERS: return "PRIORITY_CATEGORY_REMINDERS"; + case PRIORITY_CATEGORY_EVENTS: return "PRIORITY_CATEGORY_EVENTS"; + case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES"; + case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS"; + case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS"; + default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory; + } + } + + public static String prioritySendersToString(int prioritySenders) { + switch (prioritySenders) { + case PRIORITY_SENDERS_ANY: return "PRIORITY_SENDERS_ANY"; + case PRIORITY_SENDERS_CONTACTS: return "PRIORITY_SENDERS_CONTACTS"; + case PRIORITY_SENDERS_STARRED: return "PRIORITY_SENDERS_STARRED"; + default: return "PRIORITY_SENDERS_UNKNOWN_" + prioritySenders; + } + } + + public static final Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() { + @Override + public Policy createFromParcel(Parcel in) { + return new Policy(in); + } + + @Override + public Policy[] newArray(int size) { + return new Policy[size]; + } + }; + + /** + * Represents a client-specific token required to manage notification policy. + */ + public static class Token implements Parcelable { + private final IBinder mBinder; + + /** @hide */ + public Token(IBinder binder) { + if (binder == null) throw new IllegalArgumentException("Binder required for token"); + mBinder = binder; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + return Objects.hash(mBinder); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Token)) return false; + if (o == this) return true; + final Token other = (Token) o; + return Objects.equals(other.mBinder, mBinder); + } + + @Override + public String toString() { + return String.format("NotificationManager.Token[0x%08x]", + System.identityHashCode(mBinder)); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mBinder); + } + + public static final Parcelable.Creator<Token> CREATOR + = new Parcelable.Creator<Token>() { + @Override + public Token createFromParcel(Parcel in) { + return new Token(in.readStrongBinder()); + } + + @Override + public Token[] newArray(int size) { + return new Token[size]; + } + }; + + /** Callback for receiving the result of a token request. */ + public static abstract class RequestCallback { + /** + * Received if the request was granted for this package. + * + * @param token can be used to manage notification policy. + */ + public abstract void onTokenGranted(Policy.Token token); + + /** + * Received if the request was denied for this package. + */ + public abstract void onTokenDenied(); + } + } + } + } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index b3aa6bef5097..e446700317ab 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -55,6 +55,7 @@ import android.location.CountryDetector; import android.location.ICountryDetector; import android.location.ILocationManager; import android.location.LocationManager; +import android.media.AudioDevicesManager; import android.media.AudioManager; import android.media.MediaRouter; import android.media.midi.IMidiManager; @@ -104,6 +105,7 @@ import android.hardware.fingerprint.IFingerprintService; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.telecom.TelecomManager; +import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; @@ -419,6 +421,13 @@ final class SystemServiceRegistry { return new SubscriptionManager(ctx.getOuterContext()); }}); + registerService(Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, + new CachedServiceFetcher<CarrierConfigManager>() { + @Override + public CarrierConfigManager createService(ContextImpl ctx) { + return new CarrierConfigManager(); + }}); + registerService(Context.TELECOM_SERVICE, TelecomManager.class, new CachedServiceFetcher<TelecomManager>() { @Override @@ -693,6 +702,13 @@ final class SystemServiceRegistry { public RadioManager createService(ContextImpl ctx) { return new RadioManager(ctx); }}); + + registerService(Context.AUDIO_DEVICES_SERVICE, AudioDevicesManager.class, + new CachedServiceFetcher<AudioDevicesManager>() { + @Override + public AudioDevicesManager createService(ContextImpl ctx) { + return new AudioDevicesManager(ctx); + }}); } /** diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 2bf267a7a80c..d8556a254aea 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -283,6 +283,7 @@ public abstract class BackupAgent extends ContextWrapper { // all of the ones we will be traversing String rootDir = new File(appInfo.dataDir).getCanonicalPath(); String filesDir = getFilesDir().getCanonicalPath(); + String nobackupDir = getNoBackupFilesDir().getCanonicalPath(); String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); String cacheDir = getCacheDir().getCanonicalPath(); @@ -304,6 +305,7 @@ public abstract class BackupAgent extends ContextWrapper { filterSet.add(databaseDir); filterSet.add(sharedPrefsDir); filterSet.add(filesDir); + filterSet.add(nobackupDir); fullBackupFileTree(packageName, FullBackup.ROOT_TREE_TOKEN, rootDir, filterSet, data); // Now do the same for the files dir, db dir, and shared prefs dir diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl index d80f58cd1072..506dd12a36a0 100644 --- a/core/java/android/app/trust/ITrustListener.aidl +++ b/core/java/android/app/trust/ITrustListener.aidl @@ -22,6 +22,6 @@ package android.app.trust; * {@hide} */ oneway interface ITrustListener { - void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser); + void onTrustChanged(boolean enabled, int userId, int flags); void onTrustManagedChanged(boolean managed, int userId); }
\ No newline at end of file diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 705a144d8c03..b5c5317aeb06 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -34,7 +34,7 @@ public class TrustManager { private static final int MSG_TRUST_MANAGED_CHANGED = 2; private static final String TAG = "TrustManager"; - private static final String DATA_INITIATED_BY_USER = "initiatedByUser"; + private static final String DATA_FLAGS = "initiatedByUser"; private final ITrustManager mService; private final ArrayMap<TrustListener, ITrustListener> mTrustListeners; @@ -109,11 +109,11 @@ public class TrustManager { try { ITrustListener.Stub iTrustListener = new ITrustListener.Stub() { @Override - public void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser) { + public void onTrustChanged(boolean enabled, int userId, int flags) { Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId, trustListener); - if (initiatedByUser) { - m.getData().putBoolean(DATA_INITIATED_BY_USER, initiatedByUser); + if (flags != 0) { + m.getData().putInt(DATA_FLAGS, flags); } m.sendToTarget(); } @@ -156,11 +156,8 @@ public class TrustManager { public void handleMessage(Message msg) { switch(msg.what) { case MSG_TRUST_CHANGED: - boolean initiatedByUser = msg.peekData() != null && - msg.peekData().getBoolean(DATA_INITIATED_BY_USER); - ((TrustListener)msg.obj).onTrustChanged( - msg.arg1 != 0, msg.arg2, initiatedByUser); - + int flags = msg.peekData() != null ? msg.peekData().getInt(DATA_FLAGS) : 0; + ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags); break; case MSG_TRUST_MANAGED_CHANGED: ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2); @@ -174,10 +171,11 @@ public class TrustManager { * Reports that the trust state has changed. * @param enabled if true, the system believes the environment to be trusted. * @param userId the user, for which the trust changed. - * @param initiatedByUser indicates that the user has explicitly initiated an action that - * proves the user is about to use the device. + * @param flags flags specified by the trust agent when granting trust. See + * {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int) + * TrustAgentService.grantTrust(CharSequence, long, int)}. */ - void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser); + void onTrustChanged(boolean enabled, int userId, int flags); /** * Reports that whether trust is managed has changed diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2b3cf340bc54..2418e8242593 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2009-2014 The Android Open Source Project + * Copyright (C) 2009-2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +32,9 @@ import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; +import android.app.ActivityThread; +import android.os.SystemProperties; +import android.os.Binder; import android.util.Log; import android.util.Pair; @@ -152,6 +156,24 @@ public final class BluetoothAdapter { public static final int STATE_TURNING_OFF = 13; /** + * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on. + * @hide + */ + public static final int STATE_BLE_TURNING_ON = 14; + + /** + * Indicates the local Bluetooth adapter is in LE only mode. + * @hide + */ + public static final int STATE_BLE_ON = 15; + + /** + * Indicates the local Bluetooth adapter is turning off LE only mode. + * @hide + */ + public static final int STATE_BLE_TURNING_OFF = 16; + + /** * Activity Action: Show a system activity that requests discoverable mode. * This activity will also request the user to turn on Bluetooth if it * is not currently enabled. @@ -362,6 +384,39 @@ public final class BluetoothAdapter { public static final String EXTRA_PREVIOUS_CONNECTION_STATE = "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"; + /** + * Broadcast Action: The Bluetooth adapter state has changed in LE only mode. + * @hide + */ + public static final String ACTION_BLE_STATE_CHANGED = + "anrdoid.bluetooth.adapter.action.BLE_STATE_CHANGED"; + + /** + * Broadcast Action: The notifys Bluetooth ACL connected event. This will be + * by BLE Always on enabled application to know the ACL_CONNECTED event + * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection + * as Bluetooth LE is the only feature available in STATE_BLE_ON + * + * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which + * works in Bluetooth state STATE_ON + * @hide + */ + public static final String ACTION_BLE_ACL_CONNECTED = + "android.bluetooth.adapter.action.BLE_ACL_CONNECTED"; + + /** + * Broadcast Action: The notifys Bluetooth ACL connected event. This will be + * by BLE Always on enabled application to know the ACL_DISCONNECTED event + * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth + * LE is the only feature available in STATE_BLE_ON + * + * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which + * works in Bluetooth state STATE_ON + * @hide + */ + public static final String ACTION_BLE_ACL_DISCONNECTED = + "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED"; + /** The profile is in disconnected state */ public static final int STATE_DISCONNECTED = 0; /** The profile is in connecting state */ @@ -373,6 +428,19 @@ public final class BluetoothAdapter { /** @hide */ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; + private final IBinder mToken; + + + /** When creating a ServerSocket using listenUsingRfcommOn() or + * listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create + * a ServerSocket that auto assigns a channel number to the first + * bluetooth socket. + * The channel number assigned to this first Bluetooth Socket will + * be stored in the ServerSocket, and reused for subsequent Bluetooth + * sockets. + * @hide */ + public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2; + private static final int ADDRESS_LENGTH = 17; @@ -431,6 +499,7 @@ public final class BluetoothAdapter { } catch (RemoteException e) {Log.e(TAG, "", e);} mManagerService = managerService; mLeScanClients = new HashMap<LeScanCallback, ScanCallback>(); + mToken = new Binder(); } /** @@ -477,11 +546,9 @@ public final class BluetoothAdapter { * on this device before calling this method. */ public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { - if (getState() != STATE_ON) { - return null; - } + if (!getLeAccess()) return null; if (!isMultipleAdvertisementSupported() && !isPeripheralModeSupported()) { - Log.e(TAG, "bluetooth le advertising not supported"); + Log.e(TAG, "Bluetooth LE advertising not supported"); return null; } synchronized(mLock) { @@ -496,9 +563,7 @@ public final class BluetoothAdapter { * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations. */ public BluetoothLeScanner getBluetoothLeScanner() { - if (getState() != STATE_ON) { - return null; - } + if (!getLeAccess()) return null; synchronized(mLock) { if (sBluetoothLeScanner == null) { sBluetoothLeScanner = new BluetoothLeScanner(mManagerService); @@ -516,7 +581,6 @@ public final class BluetoothAdapter { * @return true if the local adapter is turned on */ public boolean isEnabled() { - try { synchronized(mManagerCallback) { if (mService != null) return mService.isEnabled(); @@ -526,6 +590,178 @@ public final class BluetoothAdapter { } /** + * Return true if Bluetooth LE(Always BLE On feature) is currently + * enabled and ready for use + * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON + * + * @return true if the local Bluetooth LE adapter is turned on + * @hide + */ + public boolean isLeEnabled() { + final int state = getLeState(); + if (state == BluetoothAdapter.STATE_ON) { + if (DBG) Log.d (TAG, "STATE_ON"); + } else if (state == BluetoothAdapter.STATE_BLE_ON) { + if (DBG) Log.d (TAG, "STATE_BLE_ON"); + } else { + if (DBG) Log.d (TAG, "STATE_OFF"); + return false; + } + return true; + } + + /** + * Performs action based on user action to turn BT ON + * or OFF if BT is in BLE_ON state + */ + private void notifyUserAction(boolean enable) { + if (mService == null) { + Log.e(TAG, "mService is null"); + return; + } + + try { + if (enable) { + mService.onLeServiceUp(); //NA:TODO implementation pending + } else { + mService.onBrEdrDown(); //NA:TODO implementation pending + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Returns true if LE only mode is enabled, that is apps + * have authorization to turn only BT ON and the calling + * app has privilage to do so + */ + private boolean isLEAlwaysOnEnabled() { + boolean ret = false; + if (SystemProperties.getBoolean("ro.bluetooth.blealwayson", true) == true) { + Log.v(TAG, "LE always on mode is enabled"); + // TODO: System API authorization check + ret = true; + } else { + Log.v(TAG, "LE always on mode is disabled"); + ret = false; + } + return ret; + } + + /** + * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE(). + * + * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition + * to STATE_OFF and completely shut-down Bluetooth + * + * <p> If the Adapter state is STATE_ON, This would unregister the existance of + * special Bluetooth LE application and hence the further turning off of Bluetooth + * from UI would ensure the complete turn-off of Bluetooth rather than staying back + * BLE only state + * + * <p>This is an asynchronous call: it will return immediately, and + * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} + * to be notified of subsequent adapter state changes If this call returns + * true, then the adapter state will immediately transition from {@link + * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time + * later transition to either {@link #STATE_BLE_ON} or {@link + * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications + * If this call returns false then there was an + * immediate problem that will prevent the QAdapter from being turned off - + * such as the QAadapter already being turned off. + * + * @return true to indicate success, or false on + * immediate error + * @hide + */ + public boolean disableBLE() { + if (isLEAlwaysOnEnabled() != true) return false; + + int state = getLeState(); + if (state == BluetoothAdapter.STATE_ON) { + if (DBG) Log.d (TAG, "STATE_ON: shouldn't disable"); + try { + mManagerService.updateBleAppCount(mToken, false); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return true; + + } else if (state == BluetoothAdapter.STATE_BLE_ON) { + if (DBG) Log.d (TAG, "STATE_BLE_ON"); + int bleAppCnt = 0; + try { + bleAppCnt = mManagerService.updateBleAppCount(mToken, false); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + if (bleAppCnt == 0) { + // Disable only if there are no other clients + notifyUserAction(false); + } + return true; + } + + if (DBG) Log.d (TAG, "STATE_OFF: Already disabled"); + return false; + } + + /** + * Special Applications who want to only turn on Bluetooth Low Energy (BLE) would + * EnableBLE, EnableBLE brings-up Bluetooth so that application can access + * only LE related feature (Bluetooth GATT layers interfaces using the respective class) + * EnableBLE in turn registers the existance of a special App which wants to + * turn on Bluetooth Low enrgy part without making it visible at the settings UI + * as Bluetooth ON. + * <p>Invoking EnableBLE when Bluetooth is already in ON state, would just registers + * the existance of special Application and doesn't do anything to current BT state. + * when user turn OFF Bluetooth from UI, if there is an existance of special app, Bluetooth + * would stay in BLE_ON state so that LE features are still acessible to the special + * Applications. + * + * <p>This is an asynchronous call: it will return immediately, and + * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} + * to be notified of subsequent adapter state changes. If this call returns + * true, then the adapter state will immediately transition from {@link + * #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, and some time + * later transition to either {@link #STATE_OFF} or {@link + * #STATE_BLE_ON}. If this call returns false then there was an + * immediate problem that will prevent the adapter from being turned on - + * such as Airplane mode, or the adapter is already turned on. + * (@link #ACTION_BLE_STATE_CHANGED) returns the Bluetooth Adapter's various + * states, It includes all the classic Bluetooth Adapter states along with + * internal BLE only states + * + * @return true to indicate Bluetooth LE start-up has begun, or false on + * immediate error + * @hide + */ + public boolean enableBLE() { + if (isLEAlwaysOnEnabled() != true) return false; + + if (isLeEnabled() == true) { + if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!"); + try { + mManagerService.updateBleAppCount(mToken, true); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return true; + } + + try { + if (DBG) Log.d(TAG, "Calling enableBLE"); + mManagerService.updateBleAppCount(mToken, true); + return mManagerService.enable(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + + return false; + } + + /** * Get the current state of the local Bluetooth adapter. * <p>Possible return values are * {@link #STATE_OFF}, @@ -543,6 +779,13 @@ public final class BluetoothAdapter { { int state= mService.getState(); if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state); + //consider all internal states as OFF + if (state == BluetoothAdapter.STATE_BLE_ON + || state == BluetoothAdapter.STATE_BLE_TURNING_ON + || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { + if (VDBG) Log.d(TAG, "Consider internal state as OFF"); + state = BluetoothAdapter.STATE_OFF; + } return state; } // TODO(BT) there might be a small gap during STATE_TURNING_ON that @@ -554,6 +797,49 @@ public final class BluetoothAdapter { } /** + * Get the current state of the local Bluetooth adapter + * <p>This returns current internal state of Adapter including LE ON/OFF + * + * <p>Possible return values are + * {@link #STATE_OFF}, + * {@link #STATE_BLE_TURNING_ON}, + * {@link #STATE_BLE_ON}, + * {@link #STATE_TURNING_ON}, + * {@link #STATE_ON}, + * {@link #STATE_TURNING_OFF}, + * {@link #STATE_BLE_TURNING_OFF}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return current state of Bluetooth adapter + * @hide + */ + public int getLeState() { + try { + synchronized(mManagerCallback) { + if (mService != null) + { + int state= mService.getState(); + if (VDBG) Log.d(TAG,"getLeState() returning " + state); + return state; + } + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return BluetoothAdapter.STATE_OFF; + } + + boolean getLeAccess() { + if(getLeState() == STATE_ON) + return true; + + else if (getLeState() == STATE_BLE_ON) + return true; // TODO: FILTER SYSTEM APPS HERE <-- + + return false; + } + + /** * Turn on the local Bluetooth adapter—do not use without explicit * user action to turn on Bluetooth. * <p>This powers on the underlying Bluetooth hardware, and starts all @@ -581,10 +867,23 @@ public final class BluetoothAdapter { * immediate error */ public boolean enable() { + int state = STATE_OFF; if (isEnabled() == true){ if (DBG) Log.d(TAG, "enable(): BT is already enabled..!"); return true; } + //Use service interface to get the exact state + if (mService != null) { + try { + state = mService.getState(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + } + + if (state == BluetoothAdapter.STATE_BLE_ON) { + Log.e(TAG, "BT is in BLE_ON State"); + notifyUserAction(true); + return true; + } try { return mManagerService.enable(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -1141,6 +1440,9 @@ public final class BluetoothAdapter { BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothSocket.TYPE_RFCOMM, true, true, channel); int errno = socket.mSocket.bindListen(); + if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + socket.setChannel(socket.mSocket.getPort()); + } if (errno != 0) { //TODO(BT): Throw the same exception error code // that the previous code was using. @@ -1275,6 +1577,9 @@ public final class BluetoothAdapter { BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothSocket.TYPE_RFCOMM, false, false, port); int errno = socket.mSocket.bindListen(); + if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + socket.setChannel(socket.mSocket.getPort()); + } if (errno != 0) { //TODO(BT): Throw the same exception error code // that the previous code was using. @@ -1297,6 +1602,9 @@ public final class BluetoothAdapter { BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothSocket.TYPE_RFCOMM, false, true, port); int errno = socket.mSocket.bindListen(); + if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + socket.setChannel(socket.mSocket.getPort()); + } if (errno < 0) { //TODO(BT): Throw the same exception error code // that the previous code was using. @@ -1327,6 +1635,30 @@ public final class BluetoothAdapter { } /** + * Construct an encrypted, authenticated, L2CAP server socket. + * Call #accept to retrieve connections to this socket. + * @return An L2CAP BluetoothServerSocket + * @throws IOException On error, for example Bluetooth not available, or + * insufficient permissions. + * @hide + */ + public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException { + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_L2CAP, true, true, port); + int errno = socket.mSocket.bindListen(); + if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + socket.setChannel(socket.mSocket.getPort()); + } + if (errno != 0) { + //TODO(BT): Throw the same exception error code + // that the previous code was using. + //socket.mSocket.throwErrnoNative(errno); + throw new IOException("Error: " + errno); + } + return socket; + } + + /** * Read the local Out of Band Pairing Data * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * @@ -1405,6 +1737,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.HEADSET_CLIENT) { BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener); return true; + } else if (profile == BluetoothProfile.SAP) { + BluetoothSap sap = new BluetoothSap(context, listener); + return true; } else { return false; } @@ -1469,6 +1804,10 @@ public final class BluetoothAdapter { BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient)proxy; headsetClient.close(); break; + case BluetoothProfile.SAP: + BluetoothSap sap = (BluetoothSap)proxy; + sap.close(); + break; } } @@ -1512,6 +1851,10 @@ public final class BluetoothAdapter { } } } + + public void onBrEdrDown() { + if (VDBG) Log.i(TAG, "on QBrEdrDown: "); + } }; /** diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index bb0d0a39f56a..bfc374fb913c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -302,6 +302,12 @@ public final class BluetoothDevice implements Parcelable { */ public static final int DEVICE_TYPE_DUAL = 3; + + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SDP_RECORD = + "android.bluetooth.device.action.SDP_RECORD"; + /** * Broadcast Action: This intent is used to broadcast the {@link UUID} * wrapped as a {@link android.os.ParcelUuid} of the remote device after it @@ -376,6 +382,9 @@ public final class BluetoothDevice implements Parcelable { /**@hide*/ public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; + /**@hide*/ + public static final int REQUEST_TYPE_SIM_ACCESS = 4; + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, * Contains package name to return reply intent to. @@ -526,6 +535,13 @@ public final class BluetoothDevice implements Parcelable { */ public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + /** @hide */ + public static final String EXTRA_SDP_RECORD = + "android.bluetooth.device.extra.SDP_RECORD"; + + /** @hide */ + public static final String EXTRA_SDP_SEARCH_STATUS = + "android.bluetooth.device.extra.SDP_SEARCH_STATUS"; /** * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. @@ -593,7 +609,9 @@ public final class BluetoothDevice implements Parcelable { public void onBluetoothServiceUp(IBluetooth bluetoothService) throws RemoteException { synchronized (BluetoothDevice.class) { - sService = bluetoothService; + if (sService == null) { + sService = bluetoothService; + } } } @@ -603,6 +621,11 @@ public final class BluetoothDevice implements Parcelable { sService = null; } } + + public void onBrEdrDown() + { + if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state"); + } }; /** * Create a new BluetoothDevice @@ -1017,7 +1040,7 @@ public final class BluetoothDevice implements Parcelable { * or null on error */ public ParcelUuid[] getUuids() { - if (sService == null) { + if (sService == null || isBluetoothEnabled() == false) { Log.e(TAG, "BT not enabled. Cannot get remote device Uuids"); return null; } @@ -1044,7 +1067,7 @@ public final class BluetoothDevice implements Parcelable { */ public boolean fetchUuidsWithSdp() { IBluetooth service = sService; - if (service == null) { + if (service == null || isBluetoothEnabled() == false) { Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp"); return false; } @@ -1054,28 +1077,38 @@ public final class BluetoothDevice implements Parcelable { return false; } + /** + * Perform a service discovery on the remote device to get the SDP records associated + * with the specified UUID. + * + * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent, + * with the SDP records found on the remote end. If there is an error + * in getting the SDP records or if the process takes a long time, + * {@link #ACTION_SDP_RECORD} intent is sent with an status value in + * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0. + * Detailed status error codes can be found by members of the Bluetooth package in + * the AbstractionLayer class. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}. + * The object type will match one of the SdpXxxRecord types, depending on the UUID searched + * for. + * + * @return False if the sanity check fails, True if the process + * of initiating an ACL connection to the remote device + * was started. + */ /** @hide */ - public boolean fetchMasInstances() { + public boolean sdpSearch(ParcelUuid uuid) { if (sService == null) { - Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances"); + Log.e(TAG, "BT not enabled. Cannot query remote device sdp records"); return false; } try { - return sService.fetchRemoteMasInstances(this); + return sService.sdpSearch(this,uuid); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } - /** @hide */ - public int getServiceChannel(ParcelUuid uuid) { - //TODO(BT) - /* - try { - return sService.getRemoteServiceChannel(this, uuid); - } catch (RemoteException e) {Log.e(TAG, "", e);}*/ - return BluetoothDevice.ERROR; - } - /** * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. @@ -1154,6 +1187,15 @@ public final class BluetoothDevice implements Parcelable { return false; } + boolean isBluetoothEnabled() { + boolean ret = false; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null && adapter.isEnabled() == true) { + ret = true; + } + return ret; + } + /** * Requires {@link android.Manifest.permission#BLUETOOTH}. * @return Whether the phonebook access is allowed to this device. Can be @@ -1231,6 +1273,44 @@ public final class BluetoothDevice implements Parcelable { } /** + * Requires {@link android.Manifest.permission#BLUETOOTH}. + * @return Whether the Sim access is allowed to this device. Can be + * {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. + * @hide + */ + public int getSimAccessPermission() { + if (sService == null) { + return ACCESS_UNKNOWN; + } + try { + return sService.getSimAccessPermission(this); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return ACCESS_UNKNOWN; + } + + /** + * Sets whether the Sim access is allowed to this device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or + * {@link #ACCESS_REJECTED}. + * @return Whether the value has been successfully set. + * @hide + */ + public boolean setSimAccessPermission(int value) { + if (sService == null) { + return false; + } + try { + return sService.setSimAccessPermission(this, value); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; + } + + /** * Create an RFCOMM {@link BluetoothSocket} ready to start a secure * outgoing connection to this remote device on given channel. * <p>The remote device will be authenticated and communication on this @@ -1256,11 +1336,45 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public BluetoothSocket createRfcommSocket(int channel) throws IOException { + if (isBluetoothEnabled() == false) { + Log.e(TAG, "Bluetooth is not enabled"); + throw new IOException(); + } return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel, null); } /** + * Create an L2cap {@link BluetoothSocket} ready to start a secure + * outgoing connection to this remote device on given channel. + * <p>The remote device will be authenticated and communication on this + * socket will be encrypted. + * <p> Use this socket only if an authenticated socket link is possible. + * Authentication refers to the authentication of the link key to + * prevent man-in-the-middle type of attacks. + * For example, for Bluetooth 2.1 devices, if any of the devices does not + * have an input and output capability or just has the ability to + * display a numeric key, a secure socket connection is not possible. + * In such a case, use {#link createInsecureRfcommSocket}. + * For more details, refer to the Security Model section 5.2 (vol 3) of + * Bluetooth Core Specification version 2.1 + EDR. + * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing + * connection. + * <p>Valid L2CAP PSM channels are in range 1 to 2^16. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param channel L2cap PSM/channel to connect to + * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions + * @hide + */ + public BluetoothSocket createL2capSocket(int channel) throws IOException { + return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel, + null); + } + + /** * Create an RFCOMM {@link BluetoothSocket} ready to start a secure * outgoing connection to this remote device using SDP lookup of uuid. * <p>This is designed to be used with {@link @@ -1292,6 +1406,11 @@ public final class BluetoothDevice implements Parcelable { * insufficient permissions */ public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { + if (isBluetoothEnabled() == false) { + Log.e(TAG, "Bluetooth is not enabled"); + throw new IOException(); + } + return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1, new ParcelUuid(uuid)); } @@ -1325,6 +1444,10 @@ public final class BluetoothDevice implements Parcelable { * insufficient permissions */ public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException { + if (isBluetoothEnabled() == false) { + Log.e(TAG, "Bluetooth is not enabled"); + throw new IOException(); + } return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1, new ParcelUuid(uuid)); } @@ -1344,6 +1467,11 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { + + if (isBluetoothEnabled() == false) { + Log.e(TAG, "Bluetooth is not enabled"); + throw new IOException(); + } return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port, null); } @@ -1359,6 +1487,11 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public BluetoothSocket createScoSocket() throws IOException { + + if (isBluetoothEnabled() == false) { + Log.e(TAG, "Bluetooth is not enabled"); + throw new IOException(); + } return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null); } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 136740505a4a..eecb07396572 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -103,17 +103,23 @@ public interface BluetoothProfile { */ public static final int MAP = 9; + /* + * SAP Profile + * @hide + */ + public static final int SAP = 10; + /** * A2DP Sink Profile * @hide */ - public static final int A2DP_SINK = 10; + public static final int A2DP_SINK = 11; /** * AVRCP Controller Profile * @hide */ - public static final int AVRCP_CONTROLLER = 11; + public static final int AVRCP_CONTROLLER = 12; /** * Headset Client - HFP HF Role diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java new file mode 100644 index 000000000000..7b4c6f923ffc --- /dev/null +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -0,0 +1,383 @@ +/* + * 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.bluetooth; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + + +public final class BluetoothSap implements BluetoothProfile { + + private static final String TAG = "BluetoothSap"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; + + private IBluetoothSap mService; + private final Context mContext; + private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; + + /** There was an error trying to obtain the state */ + public static final int STATE_ERROR = -1; + + public static final int RESULT_FAILURE = 0; + public static final int RESULT_SUCCESS = 1; + /** Connection canceled before completion. */ + public static final int RESULT_CANCELED = 2; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + doBind(); + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothSap proxy object. + */ + /*package*/ BluetoothSap(Context context, ServiceListener l) { + if (DBG) Log.d(TAG, "Create BluetoothSap proxy object"); + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + doBind(); + } + + boolean doBind() { + Intent intent = new Intent(IBluetoothMap.class.getName()); + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, + android.os.Process.myUserHandle())) { + Log.e(TAG, "Could not bind to Bluetooth SAP Service with " + intent); + return false; + } + return true; + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothSap will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + public synchronized void close() { + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + mServiceListener = null; + } + + /** + * Get the current state of the BluetoothSap service. + * @return One of the STATE_ return codes, or STATE_ERROR if this proxy + * object is currently not connected to the Sap service. + */ + public int getState() { + if (VDBG) log("getState()"); + if (mService != null) { + try { + return mService.getState(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return BluetoothSap.STATE_ERROR; + } + + /** + * Get the currently connected remote Bluetooth device (PCE). + * @return The remote Bluetooth device, or null if not in connected or + * connecting state, or if this proxy object is not connected to + * the Sap service. + */ + public BluetoothDevice getClient() { + if (VDBG) log("getClient()"); + if (mService != null) { + try { + return mService.getClient(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return null; + } + + /** + * Returns true if the specified Bluetooth device is connected. + * Returns false if not connected, or if this proxy object is not + * currently connected to the Sap service. + */ + public boolean isConnected(BluetoothDevice device) { + if (VDBG) log("isConnected(" + device + ")"); + if (mService != null) { + try { + return mService.isConnected(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Initiate connection. Initiation of outgoing connections is not + * supported for SAP server. + */ + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); + return false; + } + + /** + * Initiate disconnect. + * + * @param device Remote Bluetooth Device + * @return false on error, + * true otherwise + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the list of connected devices. Currently at most one. + * + * @return list of connected devices + */ + public List<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Get the list of devices matching specified states. Currently at most one. + * + * @return list of matching devices + */ + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Get connection state of device + * + * @return device connection state + */ + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Set priority of the profile + * + * <p> The device should already be paired. + * + * @param device Paired bluetooth device + * @param priority + * @return true if priority is set, false on error + */ + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } + try { + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the priority of the profile. + * + * @param device Bluetooth device + * @return priority of the device + */ + public int getPriority(BluetoothDevice device) { + if (VDBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return PRIORITY_OFF; + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) log("Proxy object connected"); + mService = IBluetoothSap.Stub.asInterface(service); + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.SAP, BluetoothSap.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) log("Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.SAP); + } + } + }; + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private boolean isEnabled() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) + return true; + log("Bluetooth is Not enabled"); + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) + return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) + return true; + return false; + } + +} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index bc56e556492b..21024a6021b0 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.os.Handler; import android.os.ParcelUuid; +import android.util.Log; import java.io.Closeable; import java.io.IOException; @@ -66,10 +67,11 @@ import java.io.IOException; */ public final class BluetoothServerSocket implements Closeable { + private static final String TAG = "BluetoothServerSocket"; /*package*/ final BluetoothSocket mSocket; private Handler mHandler; private int mMessage; - private final int mChannel; + private int mChannel; /** * Construct a socket for incoming connections. @@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable { throws IOException { mChannel = port; mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null); + if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { + mSocket.setExcludeSdp(true); + } } /** @@ -98,6 +103,7 @@ public final class BluetoothServerSocket implements Closeable { /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) throws IOException { mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); + // TODO: This is the same as mChannel = -1 - is this intentional? mChannel = mSocket.getPort(); } @@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable { /*package*/ void setServiceName(String ServiceName) { mSocket.setServiceName(ServiceName); } + /** * Returns the channel on which this socket is bound. * @hide @@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable { public int getChannel() { return mChannel; } + + /** + * Sets the channel on which future sockets are bound. + * Currently used only when a channel is auto generated. + */ + /*package*/ void setChannel(int newChannel) { + /* TODO: From a design/architecture perspective this is wrong. + * The bind operation should be conducted through this class + * and the resulting port should be kept in mChannel, and + * not set from BluetoothAdapter. */ + if(mSocket != null) { + if(mSocket.getPort() != newChannel) { + Log.w(TAG,"The port set is different that the underlying port. mSocket.getPort(): " + + mSocket.getPort() + " requested newChannel: " + newChannel); + } + } + mChannel = newChannel; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ServerSocket: Type: "); + switch(mSocket.getConnectionType()) { + case BluetoothSocket.TYPE_RFCOMM: + { + sb.append("TYPE_RFCOMM"); + break; + } + case BluetoothSocket.TYPE_L2CAP: + { + sb.append("TYPE_L2CAP"); + break; + } + case BluetoothSocket.TYPE_SCO: + { + sb.append("TYPE_SCO"); + break; + } + } + sb.append(" Channel: ").append(mChannel); + return sb.toString(); + } } diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 36997e544841..5702d117b66c 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; +import java.io.BufferedInputStream; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; @@ -29,6 +30,8 @@ import java.io.OutputStream; import java.util.Locale; import java.util.UUID; import android.net.LocalSocket; + +import java.nio.Buffer; import java.nio.ByteOrder; import java.nio.ByteBuffer; /** @@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable { /** @hide */ public static final int MAX_RFCOMM_CHANNEL = 30; + /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF; /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */ - /*package*/ static final int TYPE_RFCOMM = 1; - /*package*/ static final int TYPE_SCO = 2; - /*package*/ static final int TYPE_L2CAP = 3; + public static final int TYPE_RFCOMM = 1; + public static final int TYPE_SCO = 2; + public static final int TYPE_L2CAP = 3; /*package*/ static final int EBADFD = 77; /*package*/ static final int EADDRINUSE = 98; /*package*/ static final int SEC_FLAG_ENCRYPT = 1; /*package*/ static final int SEC_FLAG_AUTH = 1 << 1; + /*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2; private final int mType; /* one of TYPE_RFCOMM etc */ private BluetoothDevice mDevice; /* remote device */ @@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable { private final BluetoothInputStream mInputStream; private final BluetoothOutputStream mOutputStream; private final ParcelUuid mUuid; + private boolean mExcludeSdp = false; private ParcelFileDescriptor mPfd; private LocalSocket mSocket; private InputStream mSocketIS; @@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable { private String mServiceName; private static int PROXY_CONNECTION_TIMEOUT = 5000; - private static int SOCK_SIGNAL_SIZE = 16; + private static int SOCK_SIGNAL_SIZE = 20; + + private ByteBuffer mL2capBuffer = null; + private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer. + private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received. private enum SocketState { INIT, @@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable { */ /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { - if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) { + if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type); + if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1 + && port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { if (port < 1 || port > MAX_RFCOMM_CHANNEL) { throw new IOException("Invalid RFCOMM channel: " + port); } } - if(uuid != null) + if (uuid != null) mUuid = uuid; else mUuid = new ParcelUuid(new UUID(0, 0)); mType = type; @@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable { mOutputStream = new BluetoothOutputStream(this); } private BluetoothSocket(BluetoothSocket s) { + if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType); mUuid = s.mUuid; mType = s.mType; mAuth = s.mAuth; @@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable { mPort = s.mPort; mInputStream = new BluetoothInputStream(this); mOutputStream = new BluetoothOutputStream(this); + mMaxRxPacketSize = s.mMaxRxPacketSize; + mMaxTxPacketSize = s.mMaxTxPacketSize; + mServiceName = s.mServiceName; + mExcludeSdp = s.mExcludeSdp; } private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException { BluetoothSocket as = new BluetoothSocket(this); @@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable { flags |= SEC_FLAG_AUTH; if(mEncrypt) flags |= SEC_FLAG_ENCRYPT; + if(mExcludeSdp) + flags |= BTSOCK_FLAG_NO_SDP; return flags; } @@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable { try { if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + IBluetooth bluetoothProxy = + BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); if (bluetoothProxy == null) throw new IOException("Bluetooth is off"); mPfd = bluetoothProxy.connectSocket(mDevice, mType, mUuid, mPort, getSecurityFlags()); @@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable { mSocketState = SocketState.LISTENING; } if (DBG) Log.d(TAG, "channel: " + channel); - if (mPort == -1) { + if (mPort <= -1) { mPort = channel; } // else ASSERT(mPort == channel) ret = 0; @@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable { /*package*/ BluetoothSocket accept(int timeout) throws IOException { BluetoothSocket acceptedSocket; - if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state"); + if (mSocketState != SocketState.LISTENING) + throw new IOException("bt socket is not in listen state"); if(timeout > 0) { Log.d(TAG, "accept() set timeout (ms):" + timeout); mSocket.setSoTimeout(timeout); @@ -427,27 +448,80 @@ public final class BluetoothSocket implements Closeable { } /*package*/ int read(byte[] b, int offset, int length) throws IOException { - if (mSocketIS == null) throw new IOException("read is called on null InputStream"); + int ret = 0; if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length); - int ret = mSocketIS.read(b, offset, length); - if(ret < 0) + if(mType == TYPE_L2CAP) + { + int bytesToRead = length; + if (VDBG) Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length + + "mL2capBuffer= " + mL2capBuffer); + if (mL2capBuffer == null) { + createL2capRxBuffer(); + } + if (mL2capBuffer.remaining() == 0) { + if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling..."); + if (fillL2capRxBuffer() == -1) { + return -1; + } + } + if (bytesToRead > mL2capBuffer.remaining()) { + bytesToRead = mL2capBuffer.remaining(); + } + if(VDBG) Log.v(TAG, "get(): offset: " + offset + + " bytesToRead: " + bytesToRead); + mL2capBuffer.get(b, offset, bytesToRead); + ret = bytesToRead; + }else { + if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length); + ret = mSocketIS.read(b, offset, length); + } + if (ret < 0) throw new IOException("bt socket closed, read return: " + ret); if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); return ret; } /*package*/ int write(byte[] b, int offset, int length) throws IOException { - if (mSocketOS == null) throw new IOException("write is called on null OutputStream"); - if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); - mSocketOS.write(b, offset, length); - // There is no good way to confirm since the entire process is asynchronous anyway - if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); - return length; + + //TODO: Since bindings can exist between the SDU size and the + // protocol, we might need to throw an exception instead of just + // splitting the write into multiple smaller writes. + // Rfcomm uses dynamic allocation, and should not have any bindings + // to the actual message length. + if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); + if (mType == TYPE_L2CAP) { + if(length <= mMaxTxPacketSize) { + mSocketOS.write(b, offset, length); + } else { + int tmpOffset = offset; + int tmpLength = mMaxTxPacketSize; + int endIndex = offset + length; + boolean done = false; + if(DBG) Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n" + + "Packet will be divided into SDU packets of size " + + mMaxTxPacketSize); + do{ + mSocketOS.write(b, tmpOffset, tmpLength); + tmpOffset += mMaxTxPacketSize; + if((tmpOffset + mMaxTxPacketSize) > endIndex) { + tmpLength = endIndex - tmpOffset; + done = true; + } + } while(!done); + + } + } else { + mSocketOS.write(b, offset, length); + } + // There is no good way to confirm since the entire process is asynchronous anyway + if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); + return length; } @Override public void close() throws IOException { - if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState); + if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + + mSocketState); if(mSocketState == SocketState.CLOSED) return; else @@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable { if(mSocketState == SocketState.CLOSED) return; mSocketState = SocketState.CLOSED; - if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS + - ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket); + if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + + ", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS + + "mSocket: " + mSocket); if(mSocket != null) { if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket); mSocket.shutdownInput(); @@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable { /*package */ int getPort() { return mPort; } + + /** + * Get the maximum supported Transmit packet size for the underlying transport. + * Use this to optimize the writes done to the output socket, to avoid sending + * half full packets. + * @return the maximum supported Transmit packet size for the underlying transport. + */ + public int getMaxTransmitPacketSize(){ + return mMaxTxPacketSize; + } + + /** + * Get the maximum supported Receive packet size for the underlying transport. + * Use this to optimize the reads done on the input stream, as any call to read + * will return a maximum of this amount of bytes - or for some transports a + * multiple of this value. + * @return the maximum supported Receive packet size for the underlying transport. + */ + public int getMaxReceivePacketSize(){ + return mMaxRxPacketSize; + } + + /** + * Get the type of the underlying connection + * @return one of TYPE_ + */ + public int getConnectionType() { + return mType; + } + + /** + * Change if a SDP entry should be automatically created. + * Must be called before calling .bind, for the call to have any effect. + * @param mExcludeSdp <li>TRUE - do not auto generate SDP record. + * <li>FALSE - default - auto generate SPP SDP record. + * @hide + */ + public void setExcludeSdp(boolean excludeSdp) { + this.mExcludeSdp = excludeSdp; + } + private String convertAddr(final byte[] addr) { return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]); @@ -487,8 +603,10 @@ public final class BluetoothSocket implements Closeable { private String waitSocketSignal(InputStream is) throws IOException { byte [] sig = new byte[SOCK_SIGNAL_SIZE]; int ret = readAll(is, sig); - if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret); + if (VDBG) Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE + + " bytes signal ret: " + ret); ByteBuffer bb = ByteBuffer.wrap(sig); + /* the struct in native is decorated with __attribute__((packed)), hence this is possible */ bb.order(ByteOrder.nativeOrder()); int size = bb.getShort(); if(size != SOCK_SIGNAL_SIZE) @@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable { bb.get(addr); int channel = bb.getInt(); int status = bb.getInt(); + mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value + mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value String RemoteAddr = convertAddr(addr); if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: " - + RemoteAddr + ", channel: " + channel + ", status: " + status); + + RemoteAddr + ", channel: " + channel + ", status: " + status + + " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize); if(status != 0) throw new IOException("Connection failure, status: " + status); return RemoteAddr; } + + private void createL2capRxBuffer(){ + if(mType == TYPE_L2CAP) { + // Allocate the buffer to use for reads. + if(VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize); + mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]); + if(VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining()); + mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request + if(VDBG) Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" + + mL2capBuffer.remaining()); + } + } + private int readAll(InputStream is, byte[] b) throws IOException { int left = b.length; while(left > 0) { int ret = is.read(b, b.length - left, left); if(ret <= 0) - throw new IOException("read failed, socket might closed or timeout, read ret: " + ret); + throw new IOException("read failed, socket might closed or timeout, read ret: " + + ret); left -= ret; if(left != 0) Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) + @@ -526,4 +661,18 @@ public final class BluetoothSocket implements Closeable { bb.order(ByteOrder.nativeOrder()); return bb.getInt(); } + + private int fillL2capRxBuffer() throws IOException { + mL2capBuffer.rewind(); + int ret = mSocketIS.read(mL2capBuffer.array()); + if(ret == -1) { + // reached end of stream - return -1 + mL2capBuffer.limit(0); + return -1; + } + mL2capBuffer.limit(ret); + return ret; + } + + } diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 194a53e0f6cb..2ded4c8fea32 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -76,7 +76,9 @@ public final class BluetoothUuid { ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); public static final ParcelUuid MAS = ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); - + public static final ParcelUuid SAP = + ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid BASE_UUID = ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); @@ -89,7 +91,7 @@ public final class BluetoothUuid { public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, - ObexObjectPush, PANU, NAP, MAP, MNS, MAS}; + ObexObjectPush, PANU, NAP, MAP, MNS, MAS, SAP}; public static boolean isAudioSource(ParcelUuid uuid) { return uuid.equals(AudioSource); @@ -143,6 +145,9 @@ public final class BluetoothUuid { public static boolean isMas(ParcelUuid uuid) { return uuid.equals(MAS); } + public static boolean isSap(ParcelUuid uuid) { + return uuid.equals(SAP); + } /** * Returns true if ParcelUuid is present in uuidArray diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 299f4c8e327a..f6001bfffedd 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -68,7 +68,7 @@ interface IBluetooth int getRemoteClass(in BluetoothDevice device); ParcelUuid[] getRemoteUuids(in BluetoothDevice device); boolean fetchRemoteUuids(in BluetoothDevice device); - boolean fetchRemoteMasInstances(in BluetoothDevice device); + boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid); boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode); boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[] @@ -79,6 +79,8 @@ interface IBluetooth boolean setPhonebookAccessPermission(in BluetoothDevice device, int value); int getMessageAccessPermission(in BluetoothDevice device); boolean setMessageAccessPermission(in BluetoothDevice device, int value); + int getSimAccessPermission(in BluetoothDevice device); + boolean setSimAccessPermission(in BluetoothDevice device, int value); void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState); @@ -102,4 +104,6 @@ interface IBluetooth // for dumpsys support String dump(); + void onLeServiceUp(); + void onBrEdrDown(); } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 7070baebcf79..4ca57f8eccab 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -101,4 +101,6 @@ interface IBluetoothGatt { in int srvcInstanceId, in ParcelUuid srvcId, in int charInstanceId, in ParcelUuid charId, in boolean confirm, in byte[] value); + void disconnectAll(); + void unregAll(); } diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl index 7411d3f27b54..8d1ce990a562 100644 --- a/core/java/android/bluetooth/IBluetoothManager.aidl +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -44,4 +44,6 @@ interface IBluetoothManager String getAddress(); String getName(); + int updateBleAppCount(IBinder b, boolean enable); + boolean isBleAppPresent(); } diff --git a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl index 9551086a1de7..1385dafca2fb 100644 --- a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl @@ -26,4 +26,5 @@ import android.bluetooth.IBluetooth; interface IBluetoothManagerCallback { void onBluetoothServiceUp(in IBluetooth bluetoothService); void onBluetoothServiceDown(); -}
\ No newline at end of file + void onBrEdrDown(); +} diff --git a/core/java/android/bluetooth/IBluetoothSap.aidl b/core/java/android/bluetooth/IBluetoothSap.aidl new file mode 100644 index 000000000000..8970639467c0 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothSap.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You 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.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * System private API for Bluetooth SAP service + * + * {@hide} + */ +interface IBluetoothSap { + int getState(); + BluetoothDevice getClient(); + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + boolean isConnected(in BluetoothDevice device); + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); +} diff --git a/core/java/android/bluetooth/SdpMasRecord.java b/core/java/android/bluetooth/SdpMasRecord.java new file mode 100644 index 000000000000..fa164c0fa9d0 --- /dev/null +++ b/core/java/android/bluetooth/SdpMasRecord.java @@ -0,0 +1,147 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class SdpMasRecord implements Parcelable { + private final int mMasInstanceId; + private final int mL2capPsm; + private final int mRfcommChannelNumber; + private final int mProfileVersion; + private final int mSupportedFeatures; + private final int mSupportedMessageTypes; + private final String mServiceName; + public static final class MessageType { + public static final int EMAIL = 0x01; + public static final int SMS_GSM = 0x02; + public static final int SMS_CDMA = 0x04; + public static final int MMS = 0x08; + } + + public SdpMasRecord(int mas_instance_id, + int l2cap_psm, + int rfcomm_channel_number, + int profile_version, + int supported_features, + int supported_message_types, + String service_name){ + this.mMasInstanceId = mas_instance_id; + this.mL2capPsm = l2cap_psm; + this.mRfcommChannelNumber = rfcomm_channel_number; + this.mProfileVersion = profile_version; + this.mSupportedFeatures = supported_features; + this.mSupportedMessageTypes = supported_message_types; + this.mServiceName = service_name; + } + + public SdpMasRecord(Parcel in){ + this.mMasInstanceId = in.readInt(); + this.mL2capPsm = in.readInt(); + this.mRfcommChannelNumber = in.readInt(); + this.mProfileVersion = in.readInt(); + this.mSupportedFeatures = in.readInt(); + this.mSupportedMessageTypes = in.readInt(); + this.mServiceName = in.readString(); + } + @Override + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + + public int getMasInstanceId() { + return mMasInstanceId; + } + + public int getL2capPsm() { + return mL2capPsm; + } + + public int getRfcommCannelNumber() { + return mRfcommChannelNumber; + } + + public int getProfileVersion() { + return mProfileVersion; + } + + public int getSupportedFeatures() { + return mSupportedFeatures; + } + + public int getSupportedMessageTypes() { + return mSupportedMessageTypes; + } + + public boolean msgSupported(int msg) { + return (mSupportedMessageTypes & msg) != 0; + } + + public String getServiceName() { + return mServiceName; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + + dest.writeInt(this.mMasInstanceId); + dest.writeInt(this.mL2capPsm); + dest.writeInt(this.mRfcommChannelNumber); + dest.writeInt(this.mProfileVersion); + dest.writeInt(this.mSupportedFeatures); + dest.writeInt(this.mSupportedMessageTypes); + dest.writeString(this.mServiceName); + + } + @Override + public String toString(){ + String ret = "Bluetooth MAS SDP Record:\n"; + + if(mMasInstanceId != -1){ + ret += "Mas Instance Id: " + mMasInstanceId + "\n"; + } + if(mRfcommChannelNumber != -1){ + ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; + } + if(mL2capPsm != -1){ + ret += "L2CAP PSM: " + mL2capPsm + "\n"; + } + if(mServiceName != null){ + ret += "Service Name: " + mServiceName + "\n"; + } + if(mProfileVersion != -1){ + ret += "Profile version: " + mProfileVersion + "\n"; + } + if(mSupportedMessageTypes != -1){ + ret += "Supported msg types: " + mSupportedMessageTypes + "\n"; + } + if(mSupportedFeatures != -1){ + ret += "Supported features: " + mSupportedFeatures + "\n"; + } + return ret; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpMasRecord createFromParcel(Parcel in) { + return new SdpMasRecord(in); + } + public SdpRecord[] newArray(int size) { + return new SdpRecord[size]; + } + }; +} diff --git a/core/java/android/bluetooth/SdpMnsRecord.java b/core/java/android/bluetooth/SdpMnsRecord.java new file mode 100644 index 000000000000..c02bb5a18417 --- /dev/null +++ b/core/java/android/bluetooth/SdpMnsRecord.java @@ -0,0 +1,112 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class SdpMnsRecord implements Parcelable { + private final int mL2capPsm; + private final int mRfcommChannelNumber; + private final int mSupportedFeatures; + private final int mProfileVersion; + private final String mServiceName; + + public SdpMnsRecord(int l2cap_psm, + int rfcomm_channel_number, + int profile_version, + int supported_features, + String service_name){ + this.mL2capPsm = l2cap_psm; + this.mRfcommChannelNumber = rfcomm_channel_number; + this.mSupportedFeatures = supported_features; + this.mServiceName = service_name; + this.mProfileVersion = profile_version; + } + + public SdpMnsRecord(Parcel in){ + this.mRfcommChannelNumber = in.readInt(); + this.mL2capPsm = in.readInt(); + this.mServiceName = in.readString(); + this.mSupportedFeatures = in.readInt(); + this.mProfileVersion = in.readInt(); + } + @Override + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + + + public int getL2capPsm() { + return mL2capPsm; + } + + public int getRfcommChannelNumber() { + return mRfcommChannelNumber; + } + + public int getSupportedFeatures() { + return mSupportedFeatures; + } + + public String getServiceName() { + return mServiceName; + } + + public int getProfileVersion() { + return mProfileVersion; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRfcommChannelNumber); + dest.writeInt(mL2capPsm); + dest.writeString(mServiceName); + dest.writeInt(mSupportedFeatures); + dest.writeInt(mProfileVersion); + } + + public String toString(){ + String ret = "Bluetooth MNS SDP Record:\n"; + + if(mRfcommChannelNumber != -1){ + ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; + } + if(mL2capPsm != -1){ + ret += "L2CAP PSM: " + mL2capPsm + "\n"; + } + if(mServiceName != null){ + ret += "Service Name: " + mServiceName + "\n"; + } + if(mSupportedFeatures != -1){ + ret += "Supported features: " + mSupportedFeatures + "\n"; + } + if(mProfileVersion != -1){ + ret += "Profile_version: " + mProfileVersion+"\n"; + } + return ret; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpMnsRecord createFromParcel(Parcel in) { + return new SdpMnsRecord(in); + } + public SdpMnsRecord[] newArray(int size) { + return new SdpMnsRecord[size]; + } + }; +} diff --git a/core/java/android/bluetooth/SdpOppOpsRecord.java b/core/java/android/bluetooth/SdpOppOpsRecord.java new file mode 100644 index 000000000000..e0e4007a215c --- /dev/null +++ b/core/java/android/bluetooth/SdpOppOpsRecord.java @@ -0,0 +1,118 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import java.util.Arrays; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data representation of a Object Push Profile Server side SDP record. + */ +/** @hide */ +public class SdpOppOpsRecord implements Parcelable { + + private final String mServiceName; + private final int mRfcommChannel; + private final int mL2capPsm; + private final int mProfileVersion; + private final byte[] mFormatsList; + + public SdpOppOpsRecord(String serviceName, int rfcommChannel, + int l2capPsm, int version, byte[] formatsList) { + super(); + this.mServiceName = serviceName; + this.mRfcommChannel = rfcommChannel; + this.mL2capPsm = l2capPsm; + this.mProfileVersion = version; + this.mFormatsList = formatsList; + } + + public String getServiceName() { + return mServiceName; + } + + public int getRfcommChannel() { + return mRfcommChannel; + } + + public int getL2capPsm() { + return mL2capPsm; + } + + public int getProfileVersion() { + return mProfileVersion; + } + + public byte[] getFormatsList() { + return mFormatsList; + } + + @Override + public int describeContents() { + /* No special objects */ + return 0; + } + + public SdpOppOpsRecord(Parcel in){ + this.mRfcommChannel = in.readInt(); + this.mL2capPsm = in.readInt(); + this.mProfileVersion = in.readInt(); + this.mServiceName = in.readString(); + int arrayLength = in.readInt(); + if(arrayLength > 0) { + byte[] bytes = new byte[arrayLength]; + in.readByteArray(bytes); + this.mFormatsList = bytes; + } else { + this.mFormatsList = null; + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRfcommChannel); + dest.writeInt(mL2capPsm); + dest.writeInt(mProfileVersion); + dest.writeString(mServiceName); + if(mFormatsList!= null && mFormatsList.length > 0) { + dest.writeInt(mFormatsList.length); + dest.writeByteArray(mFormatsList); + } else { + dest.writeInt(0); + } + } + + public String toString(){ + StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n"); + sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel); + sb.append("\n L2CAP PSM: ").append(mL2capPsm); + sb.append("\n Profile version: ").append(mProfileVersion); + sb.append("\n Service Name: ").append(mServiceName); + sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList)); + return sb.toString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpOppOpsRecord createFromParcel(Parcel in) { + return new SdpOppOpsRecord(in); + } + public SdpOppOpsRecord[] newArray(int size) { + return new SdpOppOpsRecord[size]; + } + }; + +} diff --git a/core/java/android/bluetooth/SdpPseRecord.java b/core/java/android/bluetooth/SdpPseRecord.java new file mode 100644 index 000000000000..2c159ccb83c1 --- /dev/null +++ b/core/java/android/bluetooth/SdpPseRecord.java @@ -0,0 +1,125 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class SdpPseRecord implements Parcelable { + private final int mL2capPsm; + private final int mRfcommChannelNumber; + private final int mProfileVersion; + private final int mSupportedFeatures; + private final int mSupportedRepositories; + private final String mServiceName; + + public SdpPseRecord(int l2cap_psm, + int rfcomm_channel_number, + int profile_version, + int supported_features, + int supported_repositories, + String service_name){ + this.mL2capPsm = l2cap_psm; + this.mRfcommChannelNumber = rfcomm_channel_number; + this.mProfileVersion = profile_version; + this.mSupportedFeatures = supported_features; + this.mSupportedRepositories = supported_repositories; + this.mServiceName = service_name; + } + + public SdpPseRecord(Parcel in){ + this.mRfcommChannelNumber = in.readInt(); + this.mL2capPsm = in.readInt(); + this.mProfileVersion = in.readInt(); + this.mSupportedFeatures = in.readInt(); + this.mSupportedRepositories = in.readInt(); + this.mServiceName = in.readString(); + } + @Override + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + + public int getL2capPsm() { + return mL2capPsm; + } + + public int getRfcommChannelNumber() { + return mRfcommChannelNumber; + } + + public int getSupportedFeatures() { + return mSupportedFeatures; + } + + public String getServiceName() { + return mServiceName; + } + + public int getProfileVersion() { + return mProfileVersion; + } + + public int getSupportedRepositories() { + return mSupportedRepositories; + } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRfcommChannelNumber); + dest.writeInt(mL2capPsm); + dest.writeInt(mProfileVersion); + dest.writeInt(mSupportedFeatures); + dest.writeInt(mSupportedRepositories); + dest.writeString(mServiceName); + + } + + public String toString(){ + String ret = "Bluetooth MNS SDP Record:\n"; + + if(mRfcommChannelNumber != -1){ + ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; + } + if(mL2capPsm != -1){ + ret += "L2CAP PSM: " + mL2capPsm + "\n"; + } + if(mProfileVersion != -1){ + ret += "profile version: " + mProfileVersion + "\n"; + } + if(mServiceName != null){ + ret += "Service Name: " + mServiceName + "\n"; + } + if(mSupportedFeatures != -1){ + ret += "Supported features: " + mSupportedFeatures + "\n"; + } + if(mSupportedRepositories != -1){ + ret += "Supported repositories: " + mSupportedRepositories + "\n"; + } + + return ret; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpPseRecord createFromParcel(Parcel in) { + return new SdpPseRecord(in); + } + public SdpPseRecord[] newArray(int size) { + return new SdpPseRecord[size]; + } + }; +} diff --git a/core/java/android/bluetooth/SdpRecord.java b/core/java/android/bluetooth/SdpRecord.java new file mode 100644 index 000000000000..6f1065e38d26 --- /dev/null +++ b/core/java/android/bluetooth/SdpRecord.java @@ -0,0 +1,76 @@ +/* +* Copyright (C) 2015 Samsung System LSI +* 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.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** @hide */ +public class SdpRecord implements Parcelable{ + + private final byte[] mRawData; + private final int mRawSize; + + @Override + public String toString() { + return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData) + + ", rawSize=" + mRawSize + "]"; + } + + public SdpRecord(int size_record, byte[] record){ + this.mRawData = record; + this.mRawSize = size_record; + } + + public SdpRecord(Parcel in){ + this.mRawSize = in.readInt(); + this.mRawData = new byte[mRawSize]; + in.readByteArray(this.mRawData); + + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.mRawSize); + dest.writeByteArray(this.mRawData); + + + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public SdpRecord createFromParcel(Parcel in) { + return new SdpRecord(in); + } + + public SdpRecord[] newArray(int size) { + return new SdpRecord[size]; + } + }; + + public byte[] getRawData() { + return mRawData; + } + + public int getRawSize() { + return mRawSize; + } +} diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java index b6bcfb8d7a76..307895129120 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -409,8 +409,8 @@ public final class BluetoothLeScanner { List <ScanFilter> filterList) { final int callbackType = settings.getCallbackType(); // If onlost/onfound is requested, a non-empty filter is expected - if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH - | ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { + if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH + | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) { if (filterList == null) { return false; } diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java index 4916bd9cebfa..c40256b89069 100644 --- a/core/java/android/bluetooth/le/BluetoothLeUtils.java +++ b/core/java/android/bluetooth/le/BluetoothLeUtils.java @@ -132,7 +132,7 @@ public class BluetoothLeUtils { * {@link BluetoothAdapter#STATE_ON}. */ static void checkAdapterStateOn(BluetoothAdapter adapter) { - if (adapter == null || adapter.getState() != BluetoothAdapter.STATE_ON) { + if (adapter == null || !adapter.isLeEnabled()) {//adapter.getState() != BluetoothAdapter.STATE_ON) { throw new IllegalStateException("BT Adapter is not turned ON"); } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e5e55d68284b..370f61cd7b30 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2193,6 +2193,7 @@ public abstract class Context { MEDIA_ROUTER_SERVICE, TELEPHONY_SERVICE, TELEPHONY_SUBSCRIPTION_SERVICE, + CARRIER_CONFIG_SERVICE, TELECOM_SERVICE, CLIPBOARD_SERVICE, INPUT_METHOD_SERVICE, @@ -2338,6 +2339,8 @@ public abstract class Context { * @see android.telephony.TelephonyManager * @see #TELEPHONY_SUBSCRIPTION_SERVICE * @see android.telephony.SubscriptionManager + * @see #CARRIER_CONFIG_SERVICE + * @see android.telephony.CarrierConfigManager * @see #INPUT_METHOD_SERVICE * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE @@ -2755,6 +2758,16 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.telephony.CarrierConfigManager} for reading carrier configuration values. + * + * @see #getSystemService + * @see android.telephony.CarrierConfigManager + */ + public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.text.ClipboardManager} for accessing and modifying * {@link android.content.ClipboardManager} for accessing and modifying * the contents of the global clipboard. * @@ -3077,6 +3090,15 @@ public abstract class Context { */ public static final String RADIO_SERVICE = "radio"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.media.AudioDevicesManager} for handling device enumeration & notification. + * + * @see #getSystemService + * @see android.media.AudioDevicesManager + */ + public static final String AUDIO_DEVICES_SERVICE = "audio_devices_manager"; + /** * Determine whether the given permission is allowed for a particular diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 4723c0db621c..16f6b1e63329 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -223,11 +223,12 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_HARDWARE_ACCELERATED = 0x0200; /** - * Value for {@link #flags}: true when the application can be displayed over the lockscreen - * and consequently over all users' windows. + * Value for {@link #flags}: true when the application can be displayed for all users + * regardless of if the user of the application is the current user. Set from the + * {@link android.R.attr#showForAllUsers} attribute. * @hide */ - public static final int FLAG_SHOW_ON_LOCK_SCREEN = 0x0400; + public static final int FLAG_SHOW_FOR_ALL_USERS = 0x0400; /** * Bit in {@link #flags} corresponding to an immersive activity * that wishes not to be interrupted by notifications. @@ -609,7 +610,7 @@ public class ActivityInfo extends ComponentInfo * attribute. */ public int configChanges; - + /** * The desired soft input mode for this activity's main window. * Set from the {@link android.R.attr#windowSoftInputMode} attribute @@ -648,6 +649,37 @@ public class ActivityInfo extends ComponentInfo */ public boolean resizeable; + /** @hide */ + public static final int LOCK_TASK_LAUNCH_MODE_DEFAULT = 0; + /** @hide */ + public static final int LOCK_TASK_LAUNCH_MODE_NEVER = 1; + /** @hide */ + public static final int LOCK_TASK_LAUNCH_MODE_ALWAYS = 2; + /** @hide */ + public static final int LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED = 3; + + /** @hide */ + public static final String lockTaskLaunchModeToString(int lockTaskLaunchMode) { + switch (lockTaskLaunchMode) { + case LOCK_TASK_LAUNCH_MODE_DEFAULT: + return "LOCK_TASK_LAUNCH_MODE_DEFAULT"; + case LOCK_TASK_LAUNCH_MODE_NEVER: + return "LOCK_TASK_LAUNCH_MODE_NEVER"; + case LOCK_TASK_LAUNCH_MODE_ALWAYS: + return "LOCK_TASK_LAUNCH_MODE_ALWAYS"; + case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED: + return "LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED"; + default: + return "unknown=" + lockTaskLaunchMode; + } + } + /** + * Value indicating if the activity is to be locked at startup. Takes on the values from + * {@link android.R.attr#lockTaskMode}. + * @hide + */ + public int lockTaskLaunchMode; + public ActivityInfo() { } @@ -665,13 +697,15 @@ public class ActivityInfo extends ComponentInfo uiOptions = orig.uiOptions; parentActivityName = orig.parentActivityName; maxRecents = orig.maxRecents; + resizeable = orig.resizeable; + lockTaskLaunchMode = orig.lockTaskLaunchMode; } - + /** * Return the theme resource identifier to use for this activity. If * the activity defines a theme, that is used; else, the application * theme is used. - * + * * @return The theme associated with this activity. */ public final int getThemeResource() { @@ -709,7 +743,8 @@ public class ActivityInfo extends ComponentInfo if (uiOptions != 0) { pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions)); } - pw.println(prefix + "resizeable=" + resizeable); + pw.println(prefix + "resizeable=" + resizeable + " lockTaskLaunchMode=" + + lockTaskLaunchModeToString(lockTaskLaunchMode)); super.dumpBack(pw, prefix); } @@ -739,6 +774,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(persistableMode); dest.writeInt(maxRecents); dest.writeInt(resizeable ? 1 : 0); + dest.writeInt(lockTaskLaunchMode); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -767,5 +803,6 @@ public class ActivityInfo extends ComponentInfo persistableMode = source.readInt(); maxRecents = source.readInt(); resizeable = (source.readInt() == 1); + lockTaskLaunchMode = source.readInt(); } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 5bdb7bbcf60e..e2701ee8fc12 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.Printer; import com.android.internal.util.ArrayUtils; @@ -338,8 +339,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use * cleartext network traffic, in which case platform components (e.g., HTTP stacks, - * {@code WebView}, {@code MediaPlayer}) will refuse app's requests to use cleartext traffic. - * Third-party libraries are encouraged to honor this flag as well. + * {@code WebView}, {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to + * use cleartext traffic. Third-party libraries are encouraged to honor this flag as well. */ public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27; @@ -937,6 +938,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } + /** @hide */ + public boolean isInternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; + } + + /** @hide */ + public boolean isExternalAsec() { + return TextUtils.isEmpty(volumeUuid) + && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + /** * @hide */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8844ea827315..a12887296cb0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -20,6 +20,7 @@ import android.annotation.CheckResult; import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.StringRes; @@ -4162,6 +4163,12 @@ public abstract class PackageManager { public abstract void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer); + /** {@hide} */ + public abstract @Nullable VolumeInfo getApplicationCurrentVolume(ApplicationInfo app); + + /** {@hide} */ + public abstract @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app); + /** * Returns the device identity that verifiers can use to associate their scheme to a particular * device. This should not be used by anything other than a package verifier. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 7464cab89dc3..fed9261c659d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3099,8 +3099,9 @@ public class PackageParser { a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; } - if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false)) { - a.info.flags |= ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN; + if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false) + || sa.getBoolean(R.styleable.AndroidManifestActivity_showForAllUsers, false)) { + a.info.flags |= ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; } if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) { @@ -3159,6 +3160,9 @@ public class PackageParser { R.styleable.AndroidManifestActivity_screenOrientation, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } + + a.info.lockTaskLaunchMode = + sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0); } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 49f65135c428..d88594dfae8e 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -1803,8 +1803,6 @@ public class Camera { public Point mouth = null; } - // Error codes match the enum in include/ui/Camera.h - /** * Unspecified camera error. * @see Camera.ErrorCallback @@ -1812,6 +1810,12 @@ public class Camera { public static final int CAMERA_ERROR_UNKNOWN = 1; /** + * Camera was disconnected due to use by higher priority user. + * @see Camera.ErrorCallback + */ + public static final int CAMERA_ERROR_EVICTED = 2; + + /** * Media server died. In this case, the application must release the * Camera object and instantiate a new one. * @see Camera.ErrorCallback diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index b757a9acb782..0d7b26101f92 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -199,12 +199,12 @@ public class FingerprintManager { */ public static class CryptoObject { - CryptoObject(Signature signature) { + public CryptoObject(Signature signature) { mSignature = signature; mCipher = null; } - CryptoObject(Cipher cipher) { + public CryptoObject(Cipher cipher) { mCipher = cipher; mSignature = null; } @@ -535,9 +535,9 @@ public class FingerprintManager { * * @hide */ - public List<Fingerprint> getEnrolledFingerprints() { + public List<Fingerprint> getEnrolledFingerprints(int userId) { if (mService != null) try { - return mService.getEnrolledFingerprints(getCurrentUserId()); + return mService.getEnrolledFingerprints(userId); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } @@ -545,11 +545,34 @@ public class FingerprintManager { } /** - * Determine if fingerprint hardware is present and functional. - * @return true if hardware is present and functional, false otherwise. + * Obtain the list of enrolled fingerprints templates. + * @return list of current fingerprint items * * @hide */ + public List<Fingerprint> getEnrolledFingerprints() { + return getEnrolledFingerprints(UserHandle.myUserId()); + } + + /** + * Determine if there is at least one fingerprint enrolled. + * + * @return true if at least one fingerprint is enrolled, false otherwise + */ + public boolean hasEnrolledFingerprints() { + if (mService != null) try { + return mService.hasEnrolledFingerprints(UserHandle.myUserId()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); + } + return false; + } + + /** + * Determine if fingerprint hardware is present and functional. + * + * @return true if hardware is present and functional, false otherwise. + */ public boolean isHardwareDetected() { if (mService != null) { try { diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 6fe72d55a446..51a0e4ce2b9a 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -54,6 +54,9 @@ interface IFingerprintService { // Get a pre-enrollment authentication token long preEnroll(IBinder token); + // Determine if a user has at least one enrolled fingerprint + boolean hasEnrolledFingerprints(int groupId); + // Gets the number of hardware devices // int getHardwareDeviceCount(); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 55e39b1f8d83..b34160061ca2 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -611,6 +611,27 @@ public class ConnectivityManager { } /** + * Returns a {@link Network} object corresponding to the currently active + * default data network. In the event that the current active default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network. + * + * @return a {@link Network} object for the current default network or + * {@code null} if no default network is currently active + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + */ + public Network getActiveNetwork() { + try { + return mService.getActiveNetwork(); + } catch (RemoteException e) { + return null; + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -831,48 +852,6 @@ public class ConnectivityManager { } /** - * Tells each network type to set its radio power state as directed. - * - * @param turnOn a boolean, {@code true} to turn the radios on, - * {@code false} to turn them off. - * @return a boolean, {@code true} indicating success. All network types - * will be tried, even if some fail. - * - * <p>This method requires the caller to hold the permission - * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. - * {@hide} - */ -// TODO - check for any callers and remove -// public boolean setRadios(boolean turnOn) { -// try { -// return mService.setRadios(turnOn); -// } catch (RemoteException e) { -// return false; -// } -// } - - /** - * Tells a given networkType to set its radio power state as directed. - * - * @param networkType the int networkType of interest. - * @param turnOn a boolean, {@code true} to turn the radio on, - * {@code} false to turn it off. - * @return a boolean, {@code true} indicating success. - * - * <p>This method requires the caller to hold the permission - * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}. - * {@hide} - */ -// TODO - check for any callers and remove -// public boolean setRadio(int networkType, boolean turnOn) { -// try { -// return mService.setRadio(networkType, turnOn); -// } catch (RemoteException e) { -// return false; -// } -// } - - /** * Tells the underlying networking system that the caller wants to * begin using the named feature. The interpretation of {@code feature} * is completely up to each networking implementation. @@ -1738,10 +1717,33 @@ public class ConnectivityManager { * * @param network The {@link Network} the application was attempting to use * or {@code null} to indicate the current default network. + * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both + * working and non-working connectivity. */ public void reportBadNetwork(Network network) { try { - mService.reportBadNetwork(network); + // One of these will be ignored because it matches system's current state. + // The other will trigger the necessary reevaluation. + mService.reportNetworkConnectivity(network, true); + mService.reportNetworkConnectivity(network, false); + } catch (RemoteException e) { + } + } + + /** + * Report to the framework whether a network has working connectivity. + * This provides a hint to the system that a particular network is providing + * working connectivity or not. In response the framework may re-evaluate + * the network's connectivity and might take further action thereafter. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @param hasConnectivity {@code true} if the application was able to successfully access the + * Internet using {@code network} or {@code false} if not. + */ + public void reportNetworkConnectivity(Network network, boolean hasConnectivity) { + try { + mService.reportNetworkConnectivity(network, hasConnectivity); } catch (RemoteException e) { } } @@ -1978,12 +1980,18 @@ public class ConnectivityManager { } catch (RemoteException e) { } } - /** {@hide} */ - public void registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return NetID corresponding to NetworkAgent. + */ + public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp, NetworkCapabilities nc, int score, NetworkMisc misc) { try { - mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc); - } catch (RemoteException e) { } + return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc); + } catch (RemoteException e) { + return NETID_UNSET; + } } /** @@ -2444,6 +2452,23 @@ public class ConnectivityManager { } /** + * Request connectivityservice to refresh network capabilities for the given + * {@link network}. This method returns true if the network is still active, false + * otherwise. Notice the method call assumes the caller has registered for + * listening NetworkCapabilities updates. + * + * @param network{@link Network} specifying which network you're interested. + * @hide + */ + public boolean requestBwUpdate(Network network) { + try { + return mService.requestBwUpdate(network); + } catch (RemoteException e) { + return false; + } + } + + /** * Unregisters callbacks about and possibly releases networks originating from * {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the * given {@code NetworkCallback} had previously been used with {@code #requestNetwork}, diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 1aa9c451f8a8..9d9b1bfa3326 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -43,6 +43,7 @@ import com.android.internal.net.VpnProfile; /** {@hide} */ interface IConnectivityManager { + Network getActiveNetwork(); NetworkInfo getActiveNetworkInfo(); NetworkInfo getActiveNetworkInfoForUid(int uid); NetworkInfo getNetworkInfo(int networkType); @@ -95,7 +96,7 @@ interface IConnectivityManager void reportInetCondition(int networkType, int percentage); - void reportBadNetwork(in Network network); + void reportNetworkConnectivity(in Network network, boolean hasConnectivity); ProxyInfo getGlobalProxy(); @@ -121,8 +122,6 @@ interface IConnectivityManager void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal); - int findConnectionTypeForIface(in String iface); - int checkMobileProvisioning(int suggestedTimeOutMs); String getMobileProvisioningUrl(); @@ -135,9 +134,11 @@ interface IConnectivityManager void registerNetworkFactory(in Messenger messenger, in String name); + boolean requestBwUpdate(in Network network); + void unregisterNetworkFactory(in Messenger messenger); - void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, + int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, int score, in NetworkMisc misc); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 95ceb2a06425..9c3a623190b7 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -39,12 +39,18 @@ import java.util.ArrayList; * @hide */ public abstract class NetworkAgent extends Handler { + // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown + // an exception. + public final int netId; + private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; private static final boolean VDBG = false; private final Context mContext; private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); + private volatile long mLastBwRefreshTime = 0; + private static final long BW_REFRESH_MIN_WIN_MS = 500; private static final int BASE = Protocol.BASE_NETWORK_AGENT; @@ -134,6 +140,11 @@ public abstract class NetworkAgent extends Handler { */ public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; + /** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull + * the underlying network connection for updated bandwidth information. + */ + public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10; + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { this(looper, context, logTag, ni, nc, lp, score, null); @@ -151,7 +162,7 @@ public abstract class NetworkAgent extends Handler { if (VDBG) log("Registering NetworkAgent"); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); - cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), + netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), new LinkProperties(lp), new NetworkCapabilities(nc), score, misc); } @@ -195,6 +206,15 @@ public abstract class NetworkAgent extends Handler { log("Unhandled Message " + msg); break; } + case CMD_REQUEST_BANDWIDTH_UPDATE: { + if (VDBG) { + log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); + } + if (System.currentTimeMillis() > (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { + pollLceData(); + } + break; + } case CMD_REPORT_NETWORK_STATUS: { if (VDBG) { log("CMD_REPORT_NETWORK_STATUS(" + @@ -240,6 +260,7 @@ public abstract class NetworkAgent extends Handler { * Called by the bearer code when it has new NetworkCapabilities data. */ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { + mLastBwRefreshTime = System.currentTimeMillis(); queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, new NetworkCapabilities(networkCapabilities)); } @@ -294,6 +315,13 @@ public abstract class NetworkAgent extends Handler { abstract protected void unwanted(); /** + * Called when ConnectivityService request a bandwidth update. The parent factory + * shall try to overwrite this method and produce a bandwidth update if capable. + */ + protected void pollLceData() { + } + + /** * Called when the system determines the usefulness of this network. * * Networks claiming internet connectivity will have their internet diff --git a/core/java/android/nfc/IAppCallback.aidl b/core/java/android/nfc/IAppCallback.aidl index 95993088bb49..c027d54647bd 100644 --- a/core/java/android/nfc/IAppCallback.aidl +++ b/core/java/android/nfc/IAppCallback.aidl @@ -24,7 +24,7 @@ import android.nfc.Tag; */ interface IAppCallback { - BeamShareData createBeamShareData(); - void onNdefPushComplete(); + BeamShareData createBeamShareData(byte peerLlcpVersion); + void onNdefPushComplete(byte peerLlcpVersion); void onTagDiscovered(in Tag tag); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index d00929505d43..76bd0ece384a 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -46,7 +46,6 @@ public final class NfcActivityManager extends IAppCallback.Stub static final Boolean DBG = false; final NfcAdapter mAdapter; - final NfcEvent mDefaultEvent; // cached NfcEvent (its currently always the same) // All objects in the lists are protected by this final List<NfcApplicationState> mApps; // Application(s) that have NFC state. Usually one @@ -200,7 +199,6 @@ public final class NfcActivityManager extends IAppCallback.Stub mAdapter = adapter; mActivities = new LinkedList<NfcActivityState>(); mApps = new ArrayList<NfcApplicationState>(1); // Android VM usually has 1 app - mDefaultEvent = new NfcEvent(mAdapter); } public void enableReaderMode(Activity activity, ReaderCallback callback, int flags, @@ -354,13 +352,14 @@ public final class NfcActivityManager extends IAppCallback.Stub /** Callback from NFC service, usually on binder thread */ @Override - public BeamShareData createBeamShareData() { + public BeamShareData createBeamShareData(byte peerLlcpVersion) { NfcAdapter.CreateNdefMessageCallback ndefCallback; NfcAdapter.CreateBeamUrisCallback urisCallback; NdefMessage message; Activity activity; Uri[] uris; int flags; + NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); synchronized (NfcActivityManager.this) { NfcActivityState state = findResumedActivityState(); if (state == null) return null; @@ -375,10 +374,10 @@ public final class NfcActivityManager extends IAppCallback.Stub // Make callbacks without lock if (ndefCallback != null) { - message = ndefCallback.createNdefMessage(mDefaultEvent); + message = ndefCallback.createNdefMessage(event); } if (urisCallback != null) { - uris = urisCallback.createBeamUris(mDefaultEvent); + uris = urisCallback.createBeamUris(event); if (uris != null) { ArrayList<Uri> validUris = new ArrayList<Uri>(); for (Uri uri : uris) { @@ -412,7 +411,7 @@ public final class NfcActivityManager extends IAppCallback.Stub /** Callback from NFC service, usually on binder thread */ @Override - public void onNdefPushComplete() { + public void onNdefPushComplete(byte peerLlcpVersion) { NfcAdapter.OnNdefPushCompleteCallback callback; synchronized (NfcActivityManager.this) { NfcActivityState state = findResumedActivityState(); @@ -420,10 +419,10 @@ public final class NfcActivityManager extends IAppCallback.Stub callback = state.onNdefPushCompleteCallback; } - + NfcEvent event = new NfcEvent(mAdapter, peerLlcpVersion); // Make callback without lock if (callback != null) { - callback.onNdefPushComplete(mDefaultEvent); + callback.onNdefPushComplete(event); } } diff --git a/core/java/android/nfc/NfcEvent.java b/core/java/android/nfc/NfcEvent.java index 860700a80f02..cf1d71a8b5e8 100644 --- a/core/java/android/nfc/NfcEvent.java +++ b/core/java/android/nfc/NfcEvent.java @@ -38,7 +38,14 @@ public final class NfcEvent { */ public final NfcAdapter nfcAdapter; - NfcEvent(NfcAdapter nfcAdapter) { + /** + * The LLCP version of the peer associated with the NFC event. + * The major version is in the top nibble, the minor version is in the bottom nibble. + */ + public final byte peerLlcpVersion; + + NfcEvent(NfcAdapter nfcAdapter, byte peerLlcpVersion) { this.nfcAdapter = nfcAdapter; + this.peerLlcpVersion = peerLlcpVersion; } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 7c5ddeefa467..4dfe0de831e2 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -152,10 +152,15 @@ public abstract class BatteryStats implements Parcelable { private static final String[] STAT_NAMES = { "l", "c", "u" }; /** - * Bump the version on this if the checkin format changes. + * Current version of checkin data format. + */ + static final String CHECKIN_VERSION = "14"; + + /** + * Old version, we hit 9 and ran out of room, need to remove. */ private static final int BATTERY_STATS_CHECKIN_VERSION = 9; - + private static final long BYTES_PER_KB = 1024; private static final long BYTES_PER_MB = 1048576; // 1024^2 private static final long BYTES_PER_GB = 1073741824; //1024^3 @@ -178,7 +183,9 @@ public abstract class BatteryStats implements Parcelable { private static final String BATTERY_DATA = "bt"; private static final String BATTERY_DISCHARGE_DATA = "dc"; private static final String BATTERY_LEVEL_DATA = "lv"; + private static final String GLOBAL_WIFI_DATA = "gwfl"; private static final String WIFI_DATA = "wfl"; + private static final String GLOBAL_BLUETOOTH_DATA = "gble"; private static final String MISC_DATA = "m"; private static final String GLOBAL_NETWORK_DATA = "gn"; private static final String HISTORY_STRING_POOL = "hsp"; @@ -195,8 +202,6 @@ public abstract class BatteryStats implements Parcelable { private static final String WIFI_SUPPL_STATE_COUNT_DATA = "wssc"; private static final String WIFI_SIGNAL_STRENGTH_TIME_DATA = "wsgt"; private static final String WIFI_SIGNAL_STRENGTH_COUNT_DATA = "wsgc"; - private static final String BLUETOOTH_STATE_TIME_DATA = "bst"; - private static final String BLUETOOTH_STATE_COUNT_DATA = "bsc"; private static final String POWER_USE_SUMMARY_DATA = "pws"; private static final String POWER_USE_ITEM_DATA = "pwi"; private static final String DISCHARGE_STEP_DATA = "dsd"; @@ -1055,22 +1060,23 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE_GPS_ON_FLAG = 1<<29; public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<28; public static final int STATE_WIFI_SCAN_FLAG = 1<<27; - public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<26; + public static final int STATE_WIFI_RADIO_ACTIVE_FLAG = 1<<26; public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25; // These are on the lower bits used for the command; if they change // we need to write another int of data. public static final int STATE_SENSOR_ON_FLAG = 1<<23; public static final int STATE_AUDIO_ON_FLAG = 1<<22; public static final int STATE_PHONE_SCANNING_FLAG = 1<<21; - public static final int STATE_SCREEN_ON_FLAG = 1<<20; - public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; - public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18; - public static final int STATE_CHARGING_FLAG = 1<<17; - public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16; + public static final int STATE_SCREEN_ON_FLAG = 1<<20; // consider moving to states2 + public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; // consider moving to states2 + // empty slot + // empty slot + public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<16; public static final int MOST_INTERESTING_STATES = - STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG - | STATE_PHONE_IN_CALL_FLAG | STATE_BLUETOOTH_ON_FLAG; + STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG; + + public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES; public int states; @@ -1088,9 +1094,15 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE2_WIFI_ON_FLAG = 1<<28; public static final int STATE2_FLASHLIGHT_FLAG = 1<<27; public static final int STATE2_DEVICE_IDLE_FLAG = 1<<26; + public static final int STATE2_CHARGING_FLAG = 1<<25; + public static final int STATE2_PHONE_IN_CALL_FLAG = 1<<24; + public static final int STATE2_BLUETOOTH_ON_FLAG = 1<<23; public static final int MOST_INTERESTING_STATES2 = - STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_FLAG; + STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_FLAG + | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG; + + public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2; public int states2; @@ -1137,8 +1149,10 @@ public abstract class BatteryStats implements Parcelable { public static final int EVENT_PACKAGE_UNINSTALLED = 0x000d; // Event for a package being uninstalled. public static final int EVENT_ALARM = 0x000e; + // Record that we have decided we need to collect new stats data. + public static final int EVENT_COLLECT_EXTERNAL_STATS = 0x000f; // Number of event types. - public static final int EVENT_COUNT = 0x000f; + public static final int EVENT_COUNT = 0x0010; // Mask to extract out only the type part of the event. public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH); @@ -1750,14 +1764,12 @@ public abstract class BatteryStats implements Parcelable { new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock", "Wl"), new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"), new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"), + new BitDescription(HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG, "wifi_radio", "Wr"), new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"), new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"), new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"), new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"), new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"), - new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"), - new BitDescription(HistoryItem.STATE_CHARGING_FLAG, "charging", "ch"), - new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"), new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK, HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn", DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES), @@ -1778,10 +1790,13 @@ public abstract class BatteryStats implements Parcelable { = new BitDescription[] { new BitDescription(HistoryItem.STATE2_POWER_SAVE_FLAG, "power_save", "ps"), new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"), - new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Wr"), + new BitDescription(HistoryItem.STATE2_WIFI_RUNNING_FLAG, "wifi_running", "Ww"), new BitDescription(HistoryItem.STATE2_WIFI_ON_FLAG, "wifi", "W"), new BitDescription(HistoryItem.STATE2_FLASHLIGHT_FLAG, "flashlight", "fl"), new BitDescription(HistoryItem.STATE2_DEVICE_IDLE_FLAG, "device_idle", "di"), + new BitDescription(HistoryItem.STATE2_CHARGING_FLAG, "charging", "ch"), + new BitDescription(HistoryItem.STATE2_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"), + new BitDescription(HistoryItem.STATE2_BLUETOOTH_ON_FLAG, "bluetooth", "b"), new BitDescription(HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK, HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT, "wifi_signal_strength", "Wss", new String[] { "0", "1", "2", "3", "4" }, @@ -1793,12 +1808,12 @@ public abstract class BatteryStats implements Parcelable { public static final String[] HISTORY_EVENT_NAMES = new String[] { "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", - "motion", "active", "pkginst", "pkgunin", "alarm" + "motion", "active", "pkginst", "pkgunin", "alarm", "stats" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf", "Ecn", - "Esm", "Eac", "Epi", "Epu", "Eal" + "Esm", "Eac", "Epi", "Epu", "Eal", "Est" }; /** @@ -1883,43 +1898,6 @@ public abstract class BatteryStats implements Parcelable { public abstract int getWifiSignalStrengthCount(int strengthBin, int which); /** - * Returns the time in microseconds that bluetooth has been on while the device was - * running on battery. - * - * {@hide} - */ - public abstract long getBluetoothOnTime(long elapsedRealtimeUs, int which); - - public abstract int getBluetoothPingCount(); - - public static final int BLUETOOTH_STATE_INACTIVE = 0; - public static final int BLUETOOTH_STATE_LOW = 1; - public static final int BLUETOOTH_STATE_MEDIUM = 2; - public static final int BLUETOOTH_STATE_HIGH = 3; - - static final String[] BLUETOOTH_STATE_NAMES = { - "inactive", "low", "med", "high" - }; - - public static final int NUM_BLUETOOTH_STATES = BLUETOOTH_STATE_HIGH +1; - - /** - * Returns the time in microseconds that Bluetooth has been running in the - * given active state. - * - * {@hide} - */ - public abstract long getBluetoothStateTime(int bluetoothState, - long elapsedRealtimeUs, int which); - - /** - * Returns the number of times that Bluetooth has entered the given active state. - * - * {@hide} - */ - public abstract int getBluetoothStateCount(int bluetoothState, int which); - - /** * Returns the time in microseconds that the flashlight has been on while the device was * running on battery. * @@ -2431,9 +2409,6 @@ public abstract class BatteryStats implements Parcelable { final long deviceIdlingTime = getDeviceIdlingTime(rawRealtime, which); final int connChanges = getNumConnectivityChange(which); final long phoneOnTime = getPhoneOnTime(rawRealtime, which); - final long wifiOnTime = getWifiOnTime(rawRealtime, which); - final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); - final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); final StringBuilder sb = new StringBuilder(128); @@ -2475,7 +2450,8 @@ public abstract class BatteryStats implements Parcelable { } } } - + + // Dump network stats final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); final long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); final long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which); @@ -2484,19 +2460,34 @@ public abstract class BatteryStats implements Parcelable { final long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which); final long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); final long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); - - // Dump network stats dumpLine(pw, 0 /* uid */, category, GLOBAL_NETWORK_DATA, mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes, mobileRxTotalPackets, mobileTxTotalPackets, wifiRxTotalPackets, wifiTxTotalPackets); + // Dump Wifi controller stats + final long wifiOnTime = getWifiOnTime(rawRealtime, which); + final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); + final long wifiIdleTimeMs = getWifiControllerActivity(CONTROLLER_IDLE_TIME, which); + final long wifiRxTimeMs = getWifiControllerActivity(CONTROLLER_RX_TIME, which); + final long wifiTxTimeMs = getWifiControllerActivity(CONTROLLER_TX_TIME, which); + final long wifiPowerMaMs = getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which); + dumpLine(pw, 0 /* uid */, category, GLOBAL_WIFI_DATA, + wifiOnTime / 1000, wifiRunningTime / 1000, + wifiIdleTimeMs, wifiRxTimeMs, wifiTxTimeMs, wifiPowerMaMs / (1000*60*60)); + + // Dump Bluetooth controller stats + final long btIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which); + final long btRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which); + final long btTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which); + final long btPowerMaMs = getBluetoothControllerActivity(CONTROLLER_POWER_DRAIN, which); + dumpLine(pw, 0 /* uid */, category, GLOBAL_BLUETOOTH_DATA, + btIdleTimeMs, btRxTimeMs, btTxTimeMs, btPowerMaMs / (1000*60*60)); + // Dump misc stats dumpLine(pw, 0 /* uid */, category, MISC_DATA, - screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000, - wifiRunningTime / 1000, bluetoothOnTime / 1000, - mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes, + screenOnTime / 1000, phoneOnTime / 1000, fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000, - 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000, + getMobileRadioActiveTime(rawRealtime, which) / 1000, getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000, powerSaveModeEnabledTime / 1000, connChanges, deviceIdleModeEnabledTime / 1000, getDeviceIdleModeEnabledCount(which), deviceIdlingTime / 1000, @@ -2566,17 +2557,6 @@ public abstract class BatteryStats implements Parcelable { } dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); - // Dump bluetooth state stats - args = new Object[NUM_BLUETOOTH_STATES]; - for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { - args[i] = getBluetoothStateTime(i, rawRealtime, which) / 1000; - } - dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_TIME_DATA, args); - for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { - args[i] = getBluetoothStateCount(i, which); - } - dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_COUNT_DATA, args); - if (which == STATS_SINCE_UNPLUGGED) { dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), getDischargeCurrentLevel()); @@ -2681,6 +2661,7 @@ public abstract class BatteryStats implements Parcelable { continue; } final Uid u = uidStats.valueAt(iu); + // Dump Network stats per uid, if any final long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); final long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which); @@ -2692,11 +2673,6 @@ public abstract class BatteryStats implements Parcelable { final int mobileActiveCount = u.getMobileRadioActiveCount(which); final long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which); final long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which); - final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); - final long wifiScanTime = u.getWifiScanTime(rawRealtime, which); - final int wifiScanCount = u.getWifiScanCount(which); - final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); - if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0 || mobilePacketsRx > 0 || mobilePacketsTx > 0 || wifiPacketsRx > 0 || wifiPacketsTx > 0 || mobileActiveTime > 0 || mobileActiveCount > 0) { @@ -2707,10 +2683,19 @@ public abstract class BatteryStats implements Parcelable { mobileActiveTime, mobileActiveCount); } + final long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which); + final long wifiScanTime = u.getWifiScanTime(rawRealtime, which); + final int wifiScanCount = u.getWifiScanCount(which); + final long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which); + final long uidWifiIdleTimeMs = u.getWifiControllerActivity(CONTROLLER_IDLE_TIME, which); + final long uidWifiRxTimeMs = u.getWifiControllerActivity(CONTROLLER_RX_TIME, which); + final long uidWifiTxTimeMs = u.getWifiControllerActivity(CONTROLLER_TX_TIME, which); if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0 - || uidWifiRunningTime != 0) { + || uidWifiRunningTime != 0 || uidWifiIdleTimeMs != 0 || uidWifiRxTimeMs != 0 + || uidWifiTxTimeMs != 0) { dumpLine(pw, uid, category, WIFI_DATA, - fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime, wifiScanCount); + fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime, wifiScanCount, + uidWifiIdleTimeMs, uidWifiRxTimeMs, uidWifiTxTimeMs); } if (u.hasUserActivity()) { @@ -2968,7 +2953,6 @@ public abstract class BatteryStats implements Parcelable { final long phoneOnTime = getPhoneOnTime(rawRealtime, which); final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which); final long wifiOnTime = getWifiOnTime(rawRealtime, which); - final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which); sb.setLength(0); sb.append(prefix); sb.append(" Screen on: "); formatTimeMs(sb, screenOnTime / 1000); @@ -3317,42 +3301,11 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); - sb.append(" WiFi Energy use: ").append(BatteryStatsHelper.makemAh( + sb.append(" WiFi Power drain: ").append(BatteryStatsHelper.makemAh( getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which) / (double)(1000*60*60))); sb.append(" mAh"); pw.println(sb.toString()); - sb.setLength(0); - sb.append(prefix); - sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000); - sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime)); - sb.append(")"); - pw.println(sb.toString()); - - sb.setLength(0); - sb.append(prefix); - sb.append(" Bluetooth states:"); - didOne = false; - for (int i=0; i<NUM_BLUETOOTH_STATES; i++) { - final long time = getBluetoothStateTime(i, rawRealtime, which); - if (time == 0) { - continue; - } - sb.append("\n "); - didOne = true; - sb.append(BLUETOOTH_STATE_NAMES[i]); - sb.append(" "); - formatTimeMs(sb, time/1000); - sb.append("("); - sb.append(formatRatioLocked(time, whichBatteryRealtime)); - sb.append(") "); - sb.append(getPhoneDataConnectionCount(i, which)); - sb.append("x"); - } - - if (!didOne) sb.append(" (no activity)"); - pw.println(sb.toString()); - final long bluetoothIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which); final long bluetoothRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which); @@ -3384,6 +3337,14 @@ public abstract class BatteryStats implements Parcelable { sb.append(")"); pw.println(sb.toString()); + sb.setLength(0); + sb.append(prefix); + sb.append(" Bluetooth Power drain: ").append(BatteryStatsHelper.makemAh( + getBluetoothControllerActivity(CONTROLLER_POWER_DRAIN, which) / + (double)(1000*60*60))); + sb.append(" mAh"); + pw.println(sb.toString()); + pw.println(); if (which == STATS_SINCE_UNPLUGGED) { @@ -4883,7 +4844,8 @@ public abstract class BatteryStats implements Parcelable { prepareForDumpLocked(); dumpLine(pw, 0 /* uid */, "i" /* category */, VERSION_DATA, - "13", getParcelVersion(), getStartPlatformVersion(), getEndPlatformVersion()); + CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(), + getEndPlatformVersion()); long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 75b110117457..4aff7a1835a7 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1173,7 +1173,6 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * the name of the runtime statistic to look up. * @return the value of the specified runtime statistic or {@code null} if the * runtime statistic doesn't exist. - * @hide */ public static String getRuntimeStat(String statName) { return VMDebug.getRuntimeStat(statName); @@ -1184,7 +1183,6 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * that {@link #getRuntimeStat(String)} supports. * * @return a map of the names/values of the supported runtime statistics. - * @hide */ public static Map<String, String> getRuntimeStats() { return VMDebug.getRuntimeStats(); diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index dc96640f82ec..2c60d41acd94 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -26,6 +26,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; +import java.util.Objects; /** * Information about a physical disk which may contain one or more @@ -118,6 +119,20 @@ public class DiskInfo implements Parcelable { } } + @Override + public boolean equals(Object o) { + if (o instanceof DiskInfo) { + return Objects.equals(id, ((DiskInfo) o).id); + } else { + return false; + } + } + + @Override + public int hashCode() { + return id.hashCode(); + } + public static final Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() { @Override public DiskInfo createFromParcel(Parcel in) { diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index f06fc8c30b35..a241728557f5 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -38,6 +38,8 @@ import com.android.internal.util.Preconditions; import java.io.CharArrayWriter; import java.io.File; +import java.util.Comparator; +import java.util.Objects; /** * Information about a storage volume that may be mounted. A volume may be a @@ -77,6 +79,22 @@ public class VolumeInfo implements Parcelable { private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); + private static final Comparator<VolumeInfo> + sDescriptionComparator = new Comparator<VolumeInfo>() { + @Override + public int compare(VolumeInfo lhs, VolumeInfo rhs) { + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) { + return -1; + } else if (lhs.getDescription() == null) { + return 1; + } else if (rhs.getDescription() == null) { + return -1; + } else { + return lhs.getDescription().compareTo(rhs.getDescription()); + } + } + }; + static { sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING); @@ -150,6 +168,10 @@ public class VolumeInfo implements Parcelable { return getBroadcastForEnvironment(getEnvironmentForState(state)); } + public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() { + return sDescriptionComparator; + } + public @NonNull String getId() { return id; } @@ -344,6 +366,20 @@ public class VolumeInfo implements Parcelable { } } + @Override + public boolean equals(Object o) { + if (o instanceof VolumeInfo) { + return Objects.equals(id, ((VolumeInfo) o).id); + } else { + return false; + } + } + + @Override + public int hashCode() { + return id.hashCode(); + } + public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { @Override public VolumeInfo createFromParcel(Parcel in) { diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 30da0e7652a5..4bd085f2c6ba 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -121,13 +121,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba protected void updateSeekBar() { if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { - mSeekBar.setEnabled(true); mSeekBar.setProgress(0); } else if (mMuted) { - mSeekBar.setEnabled(false); mSeekBar.setProgress(0); } else { - mSeekBar.setEnabled(true); mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume); } } @@ -136,6 +133,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_SET_STREAM_VOLUME: + if (mMuted && mLastProgress > 0) { + mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0); + } else if (!mMuted && mLastProgress == 0) { + mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0); + } mAudioManager.setStreamVolume(mStreamType, mLastProgress, AudioManager.FLAG_SHOW_UI_WARNINGS); break; @@ -375,7 +377,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType) : (streamType == mStreamType); if (mSeekBar != null && streamMatch && streamValue != -1) { - final boolean muted = mAudioManager.isStreamMute(mStreamType); + final boolean muted = mAudioManager.isStreamMute(mStreamType) + || streamValue == 0; mUiHandler.postUpdateSlider(streamValue, muted); } } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 109c23be4ff2..a622a212d44e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2766,8 +2766,6 @@ public final class Settings { * It was about AudioManager's setting and thus affected all the applications which * relied on the setting, while this is purely about the vibration setting for incoming * calls. - * - * @hide */ public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; @@ -2788,7 +2786,6 @@ public final class Settings { * DTMF tone type played by the dialer when dialing. * 0 = Normal * 1 = Long - * @hide */ public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type"; @@ -6036,6 +6033,13 @@ public final class Settings { public static final String DISPLAY_SIZE_FORCED = "display_size_forced"; /** + * The saved value for WindowManagerService.setForcedDisplayScalingMode(). + * 0 or unset if scaling is automatic, 1 if scaling is disabled. + * @hide + */ + public static final String DISPLAY_SCALING_FORCE = "display_scaling_force"; + + /** * The maximum size, in bytes, of a download that the download manager will transfer over * a non-wifi connection. * @hide @@ -6963,6 +6967,9 @@ public final class Settings { /** {@hide} */ public static final String BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_"; /** * Get the key that retrieves a bluetooth headset's priority. @@ -6995,6 +7002,15 @@ public final class Settings { public static final String getBluetoothMapPriorityKey(String address) { return BLUETOOTH_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); } + + /** + * Get the key that retrieves a bluetooth map priority. + * @hide + */ + public static final String getBluetoothSapPriorityKey(String address) { + return BLUETOOTH_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); + } + /** * Scaling factor for normal window animations. Setting to 0 will * disable window animations. @@ -7057,7 +7073,6 @@ public final class Settings { /** * Setting to 1 will hide carrier network settings. * Default is 0. - * @hide */ public static final String HIDE_CARRIER_NETWORK_SETTINGS = "hide_carrier_network_settings"; @@ -7724,6 +7739,13 @@ public final class Settings { * @hide */ public static final String NEW_CONTACT_AGGREGATOR = "new_contact_aggregator"; + + /** + * Whether to enable contacts metadata syncing or not + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String CONTACT_METADATA_SYNC = "contact_metadata_sync"; } /** diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java index 0b3bf453fd9c..70cd388a3382 100644 --- a/core/java/android/security/NetworkSecurityPolicy.java +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -46,9 +46,9 @@ public class NetworkSecurityPolicy { * without TLS or STARTTLS) is permitted for this process. * * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP and - * FTP stacks, {@link android.webkit.WebView}, {@link android.media.MediaPlayer}) will refuse - * this process's requests to use cleartext traffic. Third-party libraries are strongly - * encouraged to honor this setting as well. + * FTP stacks, {@link android.webkit.WebView}, {@link android.app.DownloadManager}, + * {@link android.media.MediaPlayer}) will refuse this process's requests to use cleartext + * traffic. Third-party libraries are strongly encouraged to honor this setting as well. * * <p>This flag is honored on a best effort basis because it's impossible to prevent all * cleartext traffic from Android applications given the level of access provided to them. For diff --git a/core/java/android/security/keymaster/KeymasterArgument.java b/core/java/android/security/keymaster/KeymasterArgument.java index 9a1c894fd668..9adde35045cb 100644 --- a/core/java/android/security/keymaster/KeymasterArgument.java +++ b/core/java/android/security/keymaster/KeymasterArgument.java @@ -42,6 +42,7 @@ abstract class KeymasterArgument implements Parcelable { case KeymasterDefs.KM_INT_REP: return new KeymasterIntArgument(tag, in); case KeymasterDefs.KM_LONG: + case KeymasterDefs.KM_LONG_REP: return new KeymasterLongArgument(tag, in); case KeymasterDefs.KM_DATE: return new KeymasterDateArgument(tag, in); diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java index 8ed288c575b7..82f65c72a6f0 100644 --- a/core/java/android/security/keymaster/KeymasterArguments.java +++ b/core/java/android/security/keymaster/KeymasterArguments.java @@ -63,6 +63,12 @@ public class KeymasterArguments implements Parcelable { } } + public void addLongs(int tag, long... values) { + for (long value : values) { + addLong(tag, value); + } + } + public void addBoolean(int tag) { mArguments.add(new KeymasterBooleanArgument(tag)); } @@ -111,8 +117,13 @@ public class KeymasterArguments implements Parcelable { } public long getLong(int tag, long defaultValue) { - if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_LONG) { - throw new IllegalArgumentException("Tag is not a long type: " + tag); + switch (KeymasterDefs.getTagType(tag)) { + case KeymasterDefs.KM_LONG: + break; // Accepted type + case KeymasterDefs.KM_LONG_REP: + throw new IllegalArgumentException("Repeatable tags must use getLongs: " + tag); + default: + throw new IllegalArgumentException("Tag is not a long type: " + tag); } KeymasterArgument arg = getArgumentByTag(tag); if (arg == null) { @@ -175,6 +186,19 @@ public class KeymasterArguments implements Parcelable { return values; } + public List<Long> getLongs(int tag) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_LONG_REP) { + throw new IllegalArgumentException("Tag is not a repeating long: " + tag); + } + List<Long> values = new ArrayList<Long>(); + for (KeymasterArgument arg : mArguments) { + if (arg.tag == tag) { + values.add(((KeymasterLongArgument) arg).value); + } + } + return values; + } + public int size() { return mArguments.size(); } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index ea53c0d66939..40baf9ce1322 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -118,9 +118,9 @@ public final class KeymasterDefs { public static final int KM_DIGEST_SHA_2_512 = 6; // Key origins. - public static final int KM_ORIGIN_HARDWARE = 0; - public static final int KM_ORIGIN_SOFTWARE = 1; + public static final int KM_ORIGIN_GENERATED = 0; public static final int KM_ORIGIN_IMPORTED = 2; + public static final int KM_ORIGIN_UNKNOWN = 3; // Key usability requirements. public static final int KM_BLOB_STANDALONE = 0; @@ -192,6 +192,8 @@ public final class KeymasterDefs { public static final int KM_ERROR_SECURE_HW_BUSY = -48; public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49; public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50; + public static final int KM_ERROR_MISSING_NONCE = -51; + public static final int KM_ERROR_INVALID_NONCE = -52; public static final int KM_ERROR_UNIMPLEMENTED = -100; public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; @@ -231,6 +233,8 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag"); sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed"); sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); + sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing"); + sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java index 9d2be098cdbc..eb17b7e3461f 100644 --- a/core/java/android/security/keymaster/KeymasterLongArgument.java +++ b/core/java/android/security/keymaster/KeymasterLongArgument.java @@ -28,6 +28,7 @@ class KeymasterLongArgument extends KeymasterArgument { super(tag); switch (KeymasterDefs.getTagType(tag)) { case KeymasterDefs.KM_LONG: + case KeymasterDefs.KM_LONG_REP: break; // OK. default: throw new IllegalArgumentException("Bad long tag " + tag); diff --git a/core/java/android/service/carrier/CarrierConfigService.java b/core/java/android/service/carrier/CarrierConfigService.java new file mode 100644 index 000000000000..1880d16fb15e --- /dev/null +++ b/core/java/android/service/carrier/CarrierConfigService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 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.service.carrier; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/** + * A service that sets carrier configuration for telephony services. + * <p> + * To extend this class, you must declare the service in your manifest file to require the + * {@link android.Manifest.permission#BIND_CARRIER_CONFIG_SERVICE} permission and include an intent + * filter with the {@link #SERVICE_INTERFACE} action. For example: + * </p> + * + * <pre>{@code + * <service android:name=".MyCarrierConfigService" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_CARRIER_CONFIG_SERVICE"> + * <intent-filter> + * <action android:name="android.service.carrier.CarrierConfigService" /> + * </intent-filter> + * </service> + * }</pre> + */ +public abstract class CarrierConfigService extends Service { + + public static final String SERVICE_INTERFACE = "android.service.carrier.CarrierConfigService"; + + private final ICarrierConfigService.Stub mStubWrapper; + + public CarrierConfigService() { + mStubWrapper = new ICarrierConfigServiceWrapper(); + } + + /** + * Override this method to set carrier configuration. + * <p> + * This method will be called by telephony services to get carrier-specific configuration + * values. The returned config will be saved by the system until, + * <ol> + * <li>The carrier app package is updated, or</li> + * <li>The carrier app requests a reload with + * {@link android.telephony.CarrierConfigManager#reloadCarrierConfigForSubId + * reloadCarrierConfigForSubId}.</li> + * </ol> + * This method can be called after a SIM card loads, which may be before or after boot. + * </p> + * <p> + * This method should not block for a long time. If expensive operations (e.g. network access) + * are required, this method can schedule the work and return null. Then, use + * {@link android.telephony.CarrierConfigManager#reloadCarrierConfigForSubId + * reloadCarrierConfigForSubId} to trigger a reload when the config is ready. + * </p> + * <p> + * Implementations should use the keys defined in {@link android.telephony.CarrierConfigManager + * CarrierConfigManager}. Any configuration values not set in the returned {@link Bundle} may be + * overridden by the system's default configuration service. + * </p> + * + * @param id contains details about the current carrier that can be used do decide what + * configuration values to return. + * @return a {@link Bundle} object containing the configuration or null if default values should + * be used. + */ + public abstract Bundle onLoadConfig(CarrierIdentifier id); + + /** @hide */ + @Override + public final IBinder onBind(Intent intent) { + if (!SERVICE_INTERFACE.equals(intent.getAction())) { + return null; + } + return mStubWrapper; + } + + /** + * A wrapper around ICarrierConfigService that forwards calls to implementations of + * {@link CarrierConfigService}. + * + * @hide + */ + private class ICarrierConfigServiceWrapper extends ICarrierConfigService.Stub { + + @Override + public Bundle getCarrierConfig(CarrierIdentifier id) { + return CarrierConfigService.this.onLoadConfig(id); + } + } +} diff --git a/core/java/android/service/carrier/CarrierIdentifier.aidl b/core/java/android/service/carrier/CarrierIdentifier.aidl new file mode 100644 index 000000000000..48b13983050d --- /dev/null +++ b/core/java/android/service/carrier/CarrierIdentifier.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015, 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.service.carrier; + +parcelable CarrierIdentifier; diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java new file mode 100644 index 000000000000..495fea657b4e --- /dev/null +++ b/core/java/android/service/carrier/CarrierIdentifier.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2015, 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.service.carrier; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Used to pass info to CarrierConfigService implementations so they can decide what values to + * return. + */ +public class CarrierIdentifier implements Parcelable { + + /** Used to create a {@link CarrierIdentifier} from a {@link Parcel}. */ + public static final Creator<CarrierIdentifier> CREATOR = new Creator<CarrierIdentifier>() { + @Override + public CarrierIdentifier createFromParcel(Parcel parcel) { + return new CarrierIdentifier(parcel); + } + + @Override + public CarrierIdentifier[] newArray(int i) { + return new CarrierIdentifier[i]; + } + }; + + private String mMcc; + private String mMnc; + private String mSpn; + private String mImsi; + private String mGid1; + private String mGid2; + + public CarrierIdentifier(String mcc, String mnc, String spn, String imsi, String gid1, + String gid2) { + mMcc = mcc; + mMnc = mnc; + mSpn = spn; + mImsi = imsi; + mGid1 = gid1; + mGid2 = gid2; + } + + /** @hide */ + public CarrierIdentifier(Parcel parcel) { + readFromParcel(parcel); + } + + /** Get the mobile country code. */ + public String getMcc() { + return mMcc; + } + + /** Get the mobile network code. */ + public String getMnc() { + return mMnc; + } + + /** Get the service provider name. */ + public String getSpn() { + return mSpn; + } + + /** Get the international mobile subscriber identity. */ + public String getImsi() { + return mImsi; + } + + /** Get the group identifier level 1. */ + public String getGid1() { + return mGid1; + } + + /** Get the group identifier level 2. */ + public String getGid2() { + return mGid2; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mMcc); + out.writeString(mMnc); + out.writeString(mSpn); + out.writeString(mImsi); + out.writeString(mGid1); + out.writeString(mGid2); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mMcc = in.readString(); + mMnc = in.readString(); + mSpn = in.readString(); + mImsi = in.readString(); + mGid1 = in.readString(); + mGid2 = in.readString(); + } +} diff --git a/core/java/android/service/carrier/ICarrierConfigService.aidl b/core/java/android/service/carrier/ICarrierConfigService.aidl new file mode 100644 index 000000000000..d8390b66a04b --- /dev/null +++ b/core/java/android/service/carrier/ICarrierConfigService.aidl @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015, 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.service.carrier; + +import android.os.Bundle; +import android.service.carrier.CarrierIdentifier; + +/** + * Service used to get carrier config from carrier apps. + * + * @see android.service.carrier.CarrierConfigService + * @hide + */ +interface ICarrierConfigService { + + /** @see android.service.carrier.CarrierConfigService#onLoadConfig */ + Bundle getCarrierConfig(in CarrierIdentifier id); +}
\ No newline at end of file diff --git a/core/java/android/service/gatekeeper/IGateKeeperService.aidl b/core/java/android/service/gatekeeper/IGateKeeperService.aidl index 2f3e296f483f..4f4670116389 100644 --- a/core/java/android/service/gatekeeper/IGateKeeperService.aidl +++ b/core/java/android/service/gatekeeper/IGateKeeperService.aidl @@ -53,13 +53,27 @@ interface IGateKeeperService { * Verifies an enrolled handle against a provided, plaintext blob. * @param uid The Android user ID associated to this enrollment * @param challenge a challenge to authenticate agaisnt the device credential. If successful - * authentication occurs, this value will be written to the returned + * authentication occurs, this value will be written to the returned * authentication attestation. * @param enrolledPasswordHandle The handle against which the provided password will be * verified. * @param The plaintext blob to verify against enrolledPassword. * @return an opaque attestation of authentication on success, or null. */ - byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, + byte[] verifyChallenge(int uid, long challenge, in byte[] enrolledPasswordHandle, in byte[] providedPassword); + + /** + * Retrieves the secure identifier for the user with the provided Android ID, + * or 0 if none is found. + * @param uid the Android user id + */ + long getSecureUserId(int uid); + + /** + * Clears secure user id associated with the provided Android ID. + * Must be called when password is set to NONE. + * @param uid the Android user id. + */ + void clearSecureUserId(int uid); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index fa782e41c20c..cc7f88053a00 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant; import android.app.INotificationManager; import android.app.Notification; import android.app.Notification.Builder; +import android.app.NotificationManager.Policy; import android.app.Service; import android.content.ComponentName; import android.content.Context; @@ -501,6 +502,22 @@ public abstract class NotificationListenerService extends Service { } /** + * Gets the notification policy token associated with this listener. + * + * <p> + * Returns null if this listener is not currently active. + */ + public final Policy.Token getNotificationPolicyToken() { + if (!isBound()) return null; + try { + return getNotificationInterface().getPolicyTokenFromListener(mWrapper); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + return null; + } + } + + /** * Sets the desired {@link #getCurrentListenerHints() listener hints}. * * <p> diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 1ed47791aa5f..14e947ce328b 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -16,6 +16,7 @@ package android.service.notification; +import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; @@ -470,6 +471,59 @@ public class ZenModeConfig implements Parcelable { } }; + public Policy toNotificationPolicy() { + int priorityCategories = 0; + int prioritySenders = Policy.PRIORITY_SENDERS_ANY; + if (allowCalls) { + priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; + } + if (allowMessages) { + priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES; + } + if (allowEvents) { + priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS; + } + if (allowReminders) { + priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS; + } + if (allowRepeatCallers) { + priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; + } + switch (allowFrom) { + case SOURCE_ANYONE: + prioritySenders = Policy.PRIORITY_SENDERS_ANY; + break; + case SOURCE_CONTACT: + prioritySenders = Policy.PRIORITY_SENDERS_CONTACTS; + break; + case SOURCE_STAR: + prioritySenders = Policy.PRIORITY_SENDERS_STARRED; + break; + } + return new Policy(priorityCategories, prioritySenders); + } + + public void applyNotificationPolicy(Policy policy) { + if (policy == null) return; + allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0; + allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0; + allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0; + allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; + allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) + != 0; + switch (policy.prioritySenders) { + case Policy.PRIORITY_SENDERS_CONTACTS: + allowFrom = SOURCE_CONTACT; + break; + case Policy.PRIORITY_SENDERS_STARRED: + allowFrom = SOURCE_STAR; + break; + default: + allowFrom = SOURCE_ANYONE; + break; + } + } + public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { final long now = System.currentTimeMillis(); final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS; @@ -881,4 +935,5 @@ public class ZenModeConfig implements Parcelable { public interface Migration { ZenModeConfig migrate(XmlV1 v1); } + } diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl index 76b2be0a6760..ec66cc8d3623 100644 --- a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl +++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl @@ -24,7 +24,7 @@ import android.os.UserHandle; * @hide */ oneway interface ITrustAgentServiceCallback { - void grantTrust(CharSequence message, long durationMs, boolean initiatedByUser); + void grantTrust(CharSequence message, long durationMs, int flags); void revokeTrust(); void setManagingTrust(boolean managingTrust); void onConfigureCompleted(boolean result, IBinder token); diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index a3178e2d5cad..9d7ffad5052e 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -17,6 +17,7 @@ package android.service.trust; import android.Manifest; +import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; @@ -32,6 +33,8 @@ import android.os.RemoteException; import android.util.Log; import android.util.Slog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -69,6 +72,7 @@ import java.util.List; */ @SystemApi public class TrustAgentService extends Service { + private final String TAG = TrustAgentService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; private static final boolean DEBUG = false; @@ -86,6 +90,34 @@ public class TrustAgentService extends Service { */ public static final String TRUST_AGENT_META_DATA = "android.service.trust.trustagent"; + + /** + * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that trust is being granted + * as the direct result of user action - such as solving a security challenge. The hint is used + * by the system to optimize the experience. Behavior may vary by device and release, so + * one should only set this parameter if it meets the above criteria rather than relying on + * the behavior of any particular device or release. + */ + public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1 << 0; + + /** + * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that the agent would like + * to dismiss the keyguard. When using this flag, the {@code TrustAgentService} must ensure + * it is only set in response to a direct user action with the expectation of dismissing the + * keyguard. + */ + public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 1 << 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { + FLAG_GRANT_TRUST_INITIATED_BY_USER, + FLAG_GRANT_TRUST_DISMISS_KEYGUARD, + }) + public @interface GrantTrustFlags {} + + private static final int MSG_UNLOCK_ATTEMPT = 1; private static final int MSG_CONFIGURE = 2; private static final int MSG_TRUST_TIMEOUT = 3; @@ -228,11 +260,35 @@ public class TrustAgentService extends Service { * direct result of user action - such as solving a security challenge. The hint is used * by the system to optimize the experience. Behavior may vary by device and release, so * one should only set this parameter if it meets the above criteria rather than relying on - * the behavior of any particular device or release. + * the behavior of any particular device or release. Corresponds to + * {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER}. * @throws IllegalStateException if the agent is not currently managing trust. + * + * @deprecated use {@link #grantTrust(CharSequence, long, int)} instead. */ + @Deprecated public final void grantTrust( final CharSequence message, final long durationMs, final boolean initiatedByUser) { + grantTrust(message, durationMs, initiatedByUser ? FLAG_GRANT_TRUST_INITIATED_BY_USER : 0); + } + + /** + * Call to grant trust on the device. + * + * @param message describes why the device is trusted, e.g. "Trusted by location". + * @param durationMs amount of time in milliseconds to keep the device in a trusted state. + * Trust for this agent will automatically be revoked when the timeout expires unless + * extended by a subsequent call to this function. The timeout is measured from the + * invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}. + * For security reasons, the value should be no larger than necessary. + * The value may be adjusted by the system as necessary to comply with a policy controlled + * by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()} + * for determining when trust expires. + * @param flags TBDocumented + * @throws IllegalStateException if the agent is not currently managing trust. + */ + public final void grantTrust( + final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) { synchronized (mLock) { if (!mManagingTrust) { throw new IllegalStateException("Cannot grant trust if agent is not managing trust." @@ -240,7 +296,7 @@ public class TrustAgentService extends Service { } if (mCallback != null) { try { - mCallback.grantTrust(message.toString(), durationMs, initiatedByUser); + mCallback.grantTrust(message.toString(), durationMs, flags); } catch (RemoteException e) { onError("calling enableTrust()"); } @@ -250,7 +306,7 @@ public class TrustAgentService extends Service { mPendingGrantTrustTask = new Runnable() { @Override public void run() { - grantTrust(message, durationMs, initiatedByUser); + grantTrust(message, durationMs, flags); } }; } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 1674950e66ce..016541fe3e90 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -17,6 +17,7 @@ package android.service.wallpaper; import android.content.res.TypedArray; +import android.graphics.Canvas; import android.os.SystemProperties; import android.view.WindowInsets; @@ -185,6 +186,7 @@ public abstract class WallpaperService extends Service { DisplayManager mDisplayManager; Display mDisplay; + private int mDisplayState; final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { @@ -228,7 +230,19 @@ public abstract class WallpaperService extends Service { throw new UnsupportedOperationException( "Wallpapers do not support keep screen on"); } - + + @Override + public Canvas lockCanvas() { + if (mDisplayState == Display.STATE_DOZE + || mDisplayState == Display.STATE_DOZE_SUSPEND) { + try { + mSession.pokeDrawLock(mWindow); + } catch (RemoteException e) { + // System server died, can be ignored. + } + } + return super.lockCanvas(); + } }; final class WallpaperInputEventReceiver extends InputEventReceiver { @@ -831,9 +845,12 @@ public abstract class WallpaperService extends Service { mWindow.setSession(mSession); + mLayout.packageName = getPackageName(); + mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE); mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); @@ -873,8 +890,8 @@ public abstract class WallpaperService extends Service { void reportVisibility() { if (!mDestroyed) { - boolean visible = mVisible - & mDisplay != null && mDisplay.getState() != Display.STATE_OFF; + mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState(); + boolean visible = mVisible && mDisplayState != Display.STATE_OFF; if (mReportedVisible != visible) { mReportedVisible = visible; if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 7828851a28f3..67794b1c7d5f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -165,6 +165,19 @@ public class StaticLayout extends Layout { return this; } + public Builder setIndents(int[] leftIndents, int[] rightIndents) { + int leftLen = leftIndents == null ? 0 : leftIndents.length; + int rightLen = rightIndents == null ? 0 : rightIndents.length; + int[] indents = new int[Math.max(leftLen, rightLen)]; + for (int i = 0; i < indents.length; i++) { + int leftMargin = i < leftLen ? leftIndents[i] : 0; + int rightMargin = i < rightLen ? rightIndents[i] : 0; + indents[i] = leftMargin + rightMargin; + } + nSetIndents(mNativePtr, indents); + return this; + } + /** * Measurement and break iteration is done in native code. The protocol for using * the native code is as follows. @@ -1009,6 +1022,8 @@ public class StaticLayout extends Layout { private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator); + private static native void nSetIndents(long nativePtr, int[] indents); + // Set up paragraph text and settings; done as one big method to minimize jni crossings private static native void nSetupParagraph(long nativePtr, char[] text, int length, float firstWidth, int firstWidthLineCount, float restWidth, diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java index 866137cc3a63..354c15fae66f 100644 --- a/core/java/android/text/TextDirectionHeuristics.java +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -81,29 +81,47 @@ public class TextDirectionHeuristics { private static final int STATE_FALSE = 1; private static final int STATE_UNKNOWN = 2; - private static int isRtlText(int directionality) { - switch (directionality) { - case Character.DIRECTIONALITY_LEFT_TO_RIGHT: - return STATE_FALSE; - case Character.DIRECTIONALITY_RIGHT_TO_LEFT: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: - return STATE_TRUE; - default: - return STATE_UNKNOWN; - } - } - - private static int isRtlTextOrFormat(int directionality) { - switch (directionality) { + /* Returns STATE_TRUE for strong RTL characters, STATE_FALSE for strong LTR characters, and + * STATE_UNKNOWN for everything else. + */ + private static int isRtlCodePoint(int codePoint) { + switch (Character.getDirectionality(codePoint)) { case Character.DIRECTIONALITY_LEFT_TO_RIGHT: - case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: - case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: return STATE_FALSE; case Character.DIRECTIONALITY_RIGHT_TO_LEFT: case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: return STATE_TRUE; + case Character.DIRECTIONALITY_UNDEFINED: + // Unassigned characters still have bidi direction, defined at: + // http://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt + + if ((0x0590 <= codePoint && codePoint <= 0x08FF) || + (0xFB1D <= codePoint && codePoint <= 0xFDCF) || + (0xFDF0 <= codePoint && codePoint <= 0xFDFF) || + (0xFE70 <= codePoint && codePoint <= 0xFEFF) || + (0x10800 <= codePoint && codePoint <= 0x10FFF) || + (0x1E800 <= codePoint && codePoint <= 0x1EFFF)) { + // Unassigned RTL character + return STATE_TRUE; + } else if ( + // Potentially-unassigned Default_Ignorable. Ranges are from unassigned + // characters that have Unicode property Other_Default_Ignorable_Code_Point + // plus some enlargening to cover bidi isolates and simplify checks. + (0x2065 <= codePoint && codePoint <= 0x2069) || + (0xFFF0 <= codePoint && codePoint <= 0xFFF8) || + (0xE0000 <= codePoint && codePoint <= 0xE0FFF) || + // Non-character + (0xFDD0 <= codePoint && codePoint <= 0xFDEF) || + ((codePoint & 0xFFFE) == 0xFFFE) || + // Currency symbol + (0x20A0 <= codePoint && codePoint <= 0x20CF) || + // Unpaired surrogate + (0xD800 <= codePoint && codePoint <= 0xDFFF)) { + return STATE_UNKNOWN; + } else { + // Unassigned LTR character + return STATE_FALSE; + } default: return STATE_UNKNOWN; } @@ -181,14 +199,26 @@ public class TextDirectionHeuristics { /** * Algorithm that uses the first strong directional character to determine the paragraph - * direction. This is the standard Unicode Bidirectional algorithm. + * direction. This is the standard Unicode Bidirectional Algorithm (steps P2 and P3), with the + * exception that if no strong character is found, UNKNOWN is returned. */ private static class FirstStrong implements TextDirectionAlgorithm { @Override public int checkRtl(CharSequence cs, int start, int count) { int result = STATE_UNKNOWN; - for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) { - result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i))); + int openIsolateCount = 0; + for (int cp, i = start, end = start + count; + i < end && result == STATE_UNKNOWN; + i += Character.charCount(cp)) { + cp = Character.codePointAt(cs, i); + if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates + openIsolateCount += 1; + } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI) + if (openIsolateCount > 0) openIsolateCount -= 1; + } else if (openIsolateCount == 0) { + // Only consider the characters outside isolate pairs + result = isRtlCodePoint(cp); + } } return result; } @@ -200,9 +230,10 @@ public class TextDirectionHeuristics { } /** - * Algorithm that uses the presence of any strong directional non-format - * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the - * direction of text. + * Algorithm that uses the presence of any strong directional character of the type indicated + * in the constructor parameter to determine the direction of text. + * + * Characters inside isolate pairs are skipped. */ private static class AnyStrong implements TextDirectionAlgorithm { private final boolean mLookForRtl; @@ -210,22 +241,31 @@ public class TextDirectionHeuristics { @Override public int checkRtl(CharSequence cs, int start, int count) { boolean haveUnlookedFor = false; - for (int i = start, e = start + count; i < e; ++i) { - switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) { - case STATE_TRUE: - if (mLookForRtl) { - return STATE_TRUE; - } - haveUnlookedFor = true; - break; - case STATE_FALSE: - if (!mLookForRtl) { - return STATE_FALSE; - } - haveUnlookedFor = true; - break; - default: - break; + int openIsolateCount = 0; + for (int cp, i = start, end = start + count; i < end; i += Character.charCount(cp)) { + cp = Character.codePointAt(cs, i); + if (0x2066 <= cp && cp <= 0x2068) { // Opening isolates + openIsolateCount += 1; + } else if (cp == 0x2069) { // POP DIRECTIONAL ISOLATE (PDI) + if (openIsolateCount > 0) openIsolateCount -= 1; + } else if (openIsolateCount == 0) { + // Only consider the characters outside isolate pairs + switch (isRtlCodePoint(cp)) { + case STATE_TRUE: + if (mLookForRtl) { + return STATE_TRUE; + } + haveUnlookedFor = true; + break; + case STATE_FALSE: + if (!mLookForRtl) { + return STATE_FALSE; + } + haveUnlookedFor = true; + break; + default: + break; + } } } if (haveUnlookedFor) { diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java index 07c1ec3a9ff9..fe7571f62176 100644 --- a/core/java/android/text/method/BaseKeyListener.java +++ b/core/java/android/text/method/BaseKeyListener.java @@ -97,7 +97,7 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener // Delete a character. final int start = Selection.getSelectionEnd(content); final int end; - if (isForwardDelete || event.isShiftPressed() || isShiftActive) { + if (isForwardDelete) { end = TextUtils.getOffsetAfter(content, start); } else { end = TextUtils.getOffsetBefore(content, start); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 71863b7022e8..71e2251a8020 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -170,6 +170,15 @@ public final class Display { public static final int FLAG_PRESENTATION = 1 << 3; /** + * Display flag: Indicates that the contents of the display should not be scaled + * to fit the physical screen dimensions. Used for development only to emulate + * devices with smaller physicals screens while preserving density. + * + * @hide + */ + public static final int FLAG_SCALING_DISABLED = 1 << 30; + + /** * Display type: Unknown display type. * @hide */ diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index ecf45b446e11..243961c15dc4 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -538,6 +538,9 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_PRESENTATION) != 0) { result.append(", FLAG_PRESENTATION"); } + if ((flags & Display.FLAG_SCALING_DISABLED) != 0) { + result.append(", FLAG_SCALING_DISABLED"); + } return result.toString(); } } diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index ec8f802a179f..e9f23536489d 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -283,7 +283,7 @@ public class DisplayListCanvas extends Canvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); - nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk, + nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } @@ -293,11 +293,11 @@ public class DisplayListCanvas extends Canvas { Bitmap bitmap = patch.getBitmap(); throwIfCannotDraw(bitmap); final long nativePaint = paint == null ? 0 : paint.getNativeInstance(); - nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk, + nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } - private static native void nDrawPatch(long renderer, long bitmap, long chunk, + private static native void nDrawPatch(long renderer, Bitmap bitmap, long chunk, float left, float top, float right, float bottom, long paint); public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 2351548df75a..b8544c65a788 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -149,12 +149,30 @@ public class GestureDetector { } /** + * The listener that is used to notify when a stylus button press occurs. + */ + public interface OnStylusButtonPressListener { + /** + * Notified when a stylus button press occurs. This is when the stylus + * is touching the screen and the {@value MotionEvent#BUTTON_SECONDARY} + * is pressed. + * + * @param e The motion event that occurred during the stylus button + * press. + * @return true if the event is consumed, else false + */ + boolean onStylusButtonPress(MotionEvent e); + } + + /** * A convenience class to extend when you only want to listen for a subset * of all the gestures. This implements all methods in the - * {@link OnGestureListener} and {@link OnDoubleTapListener} but does - * nothing and return {@code false} for all applicable methods. + * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnStylusButtonPressListener} + * but does nothing and return {@code false} for all applicable methods. */ - public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener { + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener, + OnStylusButtonPressListener { + public boolean onSingleTapUp(MotionEvent e) { return false; } @@ -190,6 +208,10 @@ public class GestureDetector { public boolean onSingleTapConfirmed(MotionEvent e) { return false; } + + public boolean onStylusButtonPress(MotionEvent e) { + return false; + } } private int mTouchSlopSquare; @@ -211,10 +233,12 @@ public class GestureDetector { private final Handler mHandler; private final OnGestureListener mListener; private OnDoubleTapListener mDoubleTapListener; + private OnStylusButtonPressListener mStylusButtonListener; private boolean mStillDown; private boolean mDeferConfirmSingleTap; private boolean mInLongPress; + private boolean mInStylusButtonPress; private boolean mAlwaysInTapRegion; private boolean mAlwaysInBiggerTapRegion; @@ -358,6 +382,9 @@ public class GestureDetector { if (listener instanceof OnDoubleTapListener) { setOnDoubleTapListener((OnDoubleTapListener) listener); } + if (listener instanceof OnStylusButtonPressListener) { + setOnStylusButtonPressListener((OnStylusButtonPressListener) listener); + } init(context); } @@ -420,6 +447,19 @@ public class GestureDetector { } /** + * Sets the listener which will be called for stylus button related + * gestures. + * + * @param onStylusButtonPressListener the listener invoked for all the + * callbacks, or null to stop listening for stylus button + * gestures. + */ + public void setOnStylusButtonPressListener( + OnStylusButtonPressListener onStylusButtonPressListener) { + mStylusButtonListener = onStylusButtonPressListener; + } + + /** * Set whether longpress is enabled, if this is enabled when a user * presses and holds down you get a longpress event and nothing further. * If it's disabled the user can press and hold down and then later @@ -512,7 +552,18 @@ public class GestureDetector { break; case MotionEvent.ACTION_DOWN: - if (mDoubleTapListener != null) { + if (mStylusButtonListener != null + && ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS + && (ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { + if (mStylusButtonListener.onStylusButtonPress(ev)) { + mInStylusButtonPress = true; + handled = true; + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + } + } + + if (mDoubleTapListener != null && !mInStylusButtonPress) { boolean hadTapMessage = mHandler.hasMessages(TAP); if (hadTapMessage) mHandler.removeMessages(TAP); if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && @@ -540,8 +591,8 @@ public class GestureDetector { mStillDown = true; mInLongPress = false; mDeferConfirmSingleTap = false; - - if (mIsLongpressEnabled) { + + if (mIsLongpressEnabled && !mInStylusButtonPress) { mHandler.removeMessages(LONG_PRESS); mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT); @@ -551,7 +602,17 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mInLongPress) { + if (mStylusButtonListener != null && !mInStylusButtonPress && !mInLongPress + && ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS + && (ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { + if (mStylusButtonListener.onStylusButtonPress(ev)) { + mInStylusButtonPress = true; + handled = true; + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + } + } + if (mInLongPress || mInStylusButtonPress) { break; } final float scrollX = mLastFocusX - focusX; @@ -591,6 +652,9 @@ public class GestureDetector { } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; + } else if (mInStylusButtonPress) { + mHandler.removeMessages(TAP); + mInStylusButtonPress = false; } else if (mAlwaysInTapRegion) { handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { @@ -649,9 +713,8 @@ public class GestureDetector { mAlwaysInTapRegion = false; mAlwaysInBiggerTapRegion = false; mDeferConfirmSingleTap = false; - if (mInLongPress) { - mInLongPress = false; - } + mInLongPress = false; + mInStylusButtonPress = false; } private void cancelTaps() { @@ -662,9 +725,8 @@ public class GestureDetector { mAlwaysInTapRegion = false; mAlwaysInBiggerTapRegion = false; mDeferConfirmSingleTap = false; - if (mInLongPress) { - mInLongPress = false; - } + mInLongPress = false; + mInStylusButtonPress = false; } private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index d6625c837128..5994d4fbae39 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -70,6 +70,7 @@ interface IWindowManager int getBaseDisplayDensity(int displayId); void setForcedDisplayDensity(int displayId, int density); void clearForcedDisplayDensity(int displayId); + void setForcedDisplayScalingMode(int displayId, int mode); // 0 = auto, 1 = disable void setOverscan(int displayId, int left, int top, int right, int bottom); diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 5af2832015e5..794c8e7ce8ab 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -4257,7 +4257,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (deviceId != 0) { searchEvent = new SearchEvent(InputDevice.getDevice(deviceId)); } - result = cb.onSearchRequested(searchEvent); + try { + result = cb.onSearchRequested(searchEvent); + } catch (AbstractMethodError e) { + Log.e(TAG, "WindowCallback " + cb.getClass().getName() + " does not implement" + + " method onSearchRequested(SearchEvent); fa", e); + result = cb.onSearchRequested(); + } } if (!result && (getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) { diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 5017a38fdb4e..25c51279fc06 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -460,8 +460,6 @@ public class ThreadedRenderer extends HardwareRenderer { if (buffer != null) { long[] map = atlas.getMap(); if (map != null) { - // TODO Remove after fixing b/15425820 - validateMap(context, map); nSetAtlas(renderProxy, buffer, map); } // If IAssetAtlas is not the same class as the IBinder @@ -476,32 +474,6 @@ public class ThreadedRenderer extends HardwareRenderer { Log.w(LOG_TAG, "Could not acquire atlas", e); } } - - private static void validateMap(Context context, long[] map) { - Log.d("Atlas", "Validating map..."); - HashSet<Long> preloadedPointers = new HashSet<Long>(); - - // We only care about drawables that hold bitmaps - final Resources resources = context.getResources(); - final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); - - final int count = drawables.size(); - ArrayList<Bitmap> tmpList = new ArrayList<Bitmap>(); - for (int i = 0; i < count; i++) { - drawables.valueAt(i).addAtlasableBitmaps(tmpList); - for (int j = 0; j < tmpList.size(); j++) { - preloadedPointers.add(tmpList.get(j).getSkBitmap()); - } - tmpList.clear(); - } - - for (int i = 0; i < map.length; i += 4) { - if (!preloadedPointers.contains(map[i])) { - Log.w("Atlas", String.format("Pointer 0x%X, not in getPreloadedDrawables?", map[i])); - map[i] = 0; - } - } - } } static native void setupShadersDiskCache(String cacheFile); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 25fa34904f7a..6176b9a14ff5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18173,7 +18173,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { - throw new IllegalStateException("onMeasure() did not set the" + throw new IllegalStateException("View with id " + getId() + ": " + + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } @@ -20431,11 +20432,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); + int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. - return makeMeasureSpec(0, UNSPECIFIED); + return makeMeasureSpec(size, UNSPECIFIED); } - int size = getSize(measureSpec) + delta; + size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 98b895d893cc..afc0eeef91eb 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -494,6 +494,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ private int mNestedScrollAxes; + // Used to manage the list of transient views, added by addTransientView() + private List<Integer> mTransientIndices = null; + private List<View> mTransientViews = null; + + /** * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild. * @@ -2822,6 +2827,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager child.dispatchAttachedToWindow(info, visibility | (child.mViewFlags & VISIBILITY_MASK)); } + final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); + for (int i = 0; i < transientCount; ++i) { + View view = mTransientViews.get(i); + view.dispatchAttachedToWindow(info, visibility | (view.mViewFlags & VISIBILITY_MASK)); + } } @Override @@ -2991,6 +3001,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager children[i].dispatchDetachedFromWindow(); } clearDisappearingChildren(); + final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size(); + for (int i = 0; i < transientCount; ++i) { + View view = mTransientViews.get(i); + view.dispatchDetachedFromWindow(); + } super.dispatchDetachedFromWindow(); } @@ -3291,6 +3306,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final long drawingTime = getDrawingTime(); if (usingRenderNodeProperties) canvas.insertReorderBarrier(); + final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); + int transientIndex = transientCount != 0 ? 0 : -1; // Only use the preordered list if not HW accelerated, since the HW pipeline will do the // draw reordering internally final ArrayList<View> preorderedList = usingRenderNodeProperties @@ -3298,6 +3315,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { + while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { + final View transientChild = mTransientViews.get(transientIndex); + if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || + transientChild.getAnimation() != null) { + more |= drawChild(canvas, transientChild, drawingTime); + } + transientIndex++; + if (transientIndex >= transientCount) { + transientIndex = -1; + } + } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); @@ -3305,6 +3333,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager more |= drawChild(canvas, child, drawingTime); } } + while (transientIndex >= 0) { + // there may be additional transient views after the normal views + final View transientChild = mTransientViews.get(transientIndex); + if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || + transientChild.getAnimation() != null) { + more |= drawChild(canvas, transientChild, drawingTime); + } + transientIndex++; + if (transientIndex >= transientCount) { + break; + } + } if (preorderedList != null) preorderedList.clear(); // Draw any disappearing views that have animations @@ -3785,6 +3825,135 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * This method adds a view to this container at the specified index purely for the + * purposes of allowing that view to draw even though it is not a normal child of + * the container. That is, the view does not participate in layout, focus, accessibility, + * input, or other normal view operations; it is purely an item to be drawn during the normal + * rendering operation of this container. The index that it is added at is the order + * in which it will be drawn, with respect to the other views in the container. + * For example, a transient view added at index 0 will be drawn before all other views + * in the container because it will be drawn first (including before any real view + * at index 0). There can be more than one transient view at any particular index; + * these views will be drawn in the order in which they were added to the list of + * transient views. The index of transient views can also be greater than the number + * of normal views in the container; that just means that they will be drawn after all + * other views are drawn. + * + * <p>Note that since transient views do not participate in layout, they must be sized + * manually or, more typically, they should just use the size that they had before they + * were removed from their container.</p> + * + * <p>Transient views are useful for handling animations of views that have been removed + * from the container, but which should be animated out after the removal. Adding these + * views as transient views allows them to participate in drawing without side-effecting + * the layout of the container.</p> + * + * <p>Transient views must always be explicitly {@link #removeTransientView(View) removed} + * from the container when they are no longer needed. For example, a transient view + * which is added in order to fade it out in its old location should be removed + * once the animation is complete.</p> + * + * @param view The view to be added + * @param index The index at which this view should be drawn, must be >= 0. + * This value is relative to the {@link #getChildAt(int) index} values in the normal + * child list of this container, where any transient view at a particular index will + * be drawn before any normal child at that same index. + */ + public void addTransientView(View view, int index) { + if (index < 0) { + return; + } + if (mTransientIndices == null) { + mTransientIndices = new ArrayList<Integer>(); + mTransientViews = new ArrayList<View>(); + } + final int oldSize = mTransientIndices.size(); + if (oldSize > 0) { + int insertionIndex; + for (insertionIndex = 0; insertionIndex < oldSize; ++insertionIndex) { + if (index < mTransientIndices.get(insertionIndex)) { + break; + } + } + mTransientIndices.add(insertionIndex, index); + mTransientViews.add(insertionIndex, view); + } else { + mTransientIndices.add(index); + mTransientViews.add(view); + } + view.mParent = this; + view.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK)); + invalidate(true); + } + + /** + * Removes a view from the list of transient views in this container. If there is no + * such transient view, this method does nothing. + * + * @param view The transient view to be removed + */ + public void removeTransientView(View view) { + if (mTransientViews == null) { + return; + } + final int size = mTransientViews.size(); + for (int i = 0; i < size; ++i) { + if (view == mTransientViews.get(i)) { + mTransientViews.remove(i); + mTransientIndices.remove(i); + view.mParent = null; + view.dispatchDetachedFromWindow(); + invalidate(true); + return; + } + } + } + + /** + * Returns the number of transient views in this container. Specific transient + * views and the index at which they were added can be retrieved via + * {@link #getTransientView(int)} and {@link #getTransientViewIndex(int)}. + * + * @see #addTransientView(View, int) + * @return The number of transient views in this container + */ + public int getTransientViewCount() { + return mTransientIndices == null ? 0 : mTransientIndices.size(); + } + + /** + * Given a valid position within the list of transient views, returns the index of + * the transient view at that position. + * + * @param position The position of the index being queried. Must be at least 0 + * and less than the value returned by {@link #getTransientViewCount()}. + * @return The index of the transient view stored in the given position if the + * position is valid, otherwise -1 + */ + public int getTransientViewIndex(int position) { + if (position < 0 || mTransientIndices == null || position >= mTransientIndices.size()) { + return -1; + } + return mTransientIndices.get(position); + } + + /** + * Given a valid position within the list of transient views, returns the + * transient view at that position. + * + * @param position The position of the view being queried. Must be at least 0 + * and less than the value returned by {@link #getTransientViewCount()}. + * @return The transient view stored in the given position if the + * position is valid, otherwise null + */ + public View getTransientView(int position) { + if (mTransientViews == null || position >= mTransientViews.size()) { + return null; + } + return mTransientViews.get(position); + } + + /** * <p>Adds a child view. If no layout parameters are already set on the child, the * default parameters for this ViewGroup are set on the child.</p> * @@ -4096,6 +4265,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.getVisibility() != View.GONE) { notifySubtreeAccessibilityStateChangedIfNeeded(); } + + if (mTransientIndices != null) { + final int transientCount = mTransientIndices.size(); + for (int i = 0; i < transientCount; ++i) { + final int oldIndex = mTransientIndices.get(i); + if (index <= oldIndex) { + mTransientIndices.set(i, oldIndex + 1); + } + } + } } private void addInArray(View child, int index) { @@ -4340,6 +4519,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (view.getVisibility() != View.GONE) { notifySubtreeAccessibilityStateChangedIfNeeded(); } + + int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); + for (int i = 0; i < transientCount; ++i) { + final int oldIndex = mTransientIndices.get(i); + if (index < oldIndex) { + mTransientIndices.set(i, oldIndex - 1); + } + } } /** @@ -6353,6 +6540,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void onStopNestedScroll(View child) { // Stop any recursive nested scrolling. stopNestedScroll(); + mNestedScrollAxes = 0; } /** diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 9d0d5ff4ecb3..49a72cefb794 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -595,6 +595,8 @@ public abstract class Window { title="Panel"; } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) { title="SubPanel"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) { + title="AboveSubPanel"; } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) { title="AtchDlg"; } else { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 54d78f3369a7..e98391016017 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -164,13 +164,14 @@ public interface WindowManager extends ViewManager { * be used by applications, and a special permission is required * to use them. * </ul> - * + * * @see #TYPE_BASE_APPLICATION * @see #TYPE_APPLICATION * @see #TYPE_APPLICATION_STARTING * @see #TYPE_APPLICATION_PANEL * @see #TYPE_APPLICATION_MEDIA * @see #TYPE_APPLICATION_SUB_PANEL + * @see #TYPE_APPLICATION_ABOVE_SUB_PANEL * @see #TYPE_APPLICATION_ATTACHED_DIALOG * @see #TYPE_STATUS_BAR * @see #TYPE_SEARCH_BAR @@ -193,6 +194,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL, to = "TYPE_APPLICATION_PANEL"), @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA, to = "TYPE_APPLICATION_MEDIA"), @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL, to = "TYPE_APPLICATION_SUB_PANEL"), + @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL, to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"), @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG, to = "TYPE_APPLICATION_ATTACHED_DIALOG"), @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY, to = "TYPE_APPLICATION_MEDIA_OVERLAY"), @ViewDebug.IntToString(from = TYPE_STATUS_BAR, to = "TYPE_STATUS_BAR"), @@ -260,40 +262,40 @@ public interface WindowManager extends ViewManager { * End of types of application windows. */ public static final int LAST_APPLICATION_WINDOW = 99; - + /** * Start of types of sub-windows. The {@link #token} of these windows * must be set to the window they are attached to. These types of * windows are kept next to their attached window in Z-order, and their * coordinate space is relative to their attached window. */ - public static final int FIRST_SUB_WINDOW = 1000; - + public static final int FIRST_SUB_WINDOW = 1000; + /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ - public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; - + public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; + /** * Window type: window for showing media (such as video). These windows * are displayed behind their attached window. */ - public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; - + public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1; + /** * Window type: a sub-panel on top of an application window. These * windows are displayed on top their attached window and any * {@link #TYPE_APPLICATION_PANEL} panels. */ - public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2; + public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2; /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout * of the window happens as that of a top-level window, <em>not</em> * as a child of its container. */ - public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; - + public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3; + /** * Window type: window for showing overlays on top of media windows. * These windows are displayed between TYPE_APPLICATION_MEDIA and the @@ -301,19 +303,26 @@ public interface WindowManager extends ViewManager { * is a big ugly hack so: * @hide */ - public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4; - + public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4; + + /** + * Window type: a above sub-panel on top of an application window and it's + * sub-panel windows. These windows are displayed on top of their attached window + * and any {@link #TYPE_APPLICATION_SUB_PANEL} panels. + */ + public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5; + /** * End of types of sub-windows. */ - public static final int LAST_SUB_WINDOW = 1999; - + public static final int LAST_SUB_WINDOW = 1999; + /** * Start of system-specific window types. These are not normally * created by applications. */ public static final int FIRST_SYSTEM_WINDOW = 2000; - + /** * Window type: the status bar. There can be only one status bar * window; it is placed at the top of the screen, and all other diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index e7c432806d9d..7ab5aaa252ce 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2427,9 +2427,7 @@ public class WebView extends AbsoluteLayout @Override public void onProvideVirtualAssistStructure(ViewAssistStructure structure) { - super.onProvideVirtualAssistStructure(structure); - // TODO: enable when chromium backend lands. - // mProvider.getViewDelegate().onProvideVirtualAssistStructure(structure); + mProvider.getViewDelegate().onProvideVirtualAssistStructure(structure); } /** @hide */ diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index cafe05309efb..4d8dce1f121e 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -76,9 +76,54 @@ public final class WebViewFactory { private static boolean sAddressSpaceReserved = false; private static PackageInfo sPackageInfo; + /** @hide */ + public static String[] getWebViewPackageNames() { + return AppGlobals.getInitialApplication().getResources().getStringArray( + com.android.internal.R.array.config_webViewPackageNames); + } + + // TODO (gsennton) remove when committing webview xts test change public static String getWebViewPackageName() { - return AppGlobals.getInitialApplication().getString( - com.android.internal.R.string.config_webViewPackageName); + String[] webViewPackageNames = getWebViewPackageNames(); + return webViewPackageNames[webViewPackageNames.length-1]; + } + + /** + * Return the package info of the first package in the webview priority list that contains + * webview. + * + * @hide + */ + public static PackageInfo findPreferredWebViewPackage() { + PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); + + for (String packageName : getWebViewPackageNames()) { + try { + PackageInfo packageInfo = pm.getPackageInfo(packageName, + PackageManager.GET_META_DATA); + ApplicationInfo applicationInfo = packageInfo.applicationInfo; + + // If the correct flag is set the package contains webview. + if (getWebViewLibrary(applicationInfo) != null) { + return packageInfo; + } + } catch (PackageManager.NameNotFoundException e) { + } + } + throw new AndroidRuntimeException("Could not find a loadable WebView package"); + } + + private static ApplicationInfo getWebViewApplicationInfo() { + if (sPackageInfo == null) + return findPreferredWebViewPackage().applicationInfo; + else + return sPackageInfo.applicationInfo; + } + + private static String getWebViewLibrary(ApplicationInfo ai) { + if (ai.metaData != null) + return ai.metaData.getString("com.android.webview.WebViewLibrary"); + return null; } public static PackageInfo getLoadedPackageInfo() { @@ -99,6 +144,11 @@ public final class WebViewFactory { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { + // First fetch the package info so we can log the webview package version. + sPackageInfo = findPreferredWebViewPackage(); + Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " + + sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")"); + Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); loadNativeLibrary(); Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); @@ -137,15 +187,10 @@ public final class WebViewFactory { private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException { Application initialApplication = AppGlobals.getInitialApplication(); try { - // First fetch the package info so we can log the webview package version. - String packageName = getWebViewPackageName(); - sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0); - Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName + - " (code " + sPackageInfo.versionCode + ")"); - // Construct a package context to load the Java code into the current app. - Context webViewContext = initialApplication.createPackageContext(packageName, - Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); + Context webViewContext = initialApplication.createPackageContext( + sPackageInfo.packageName, + Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); initialApplication.getAssets().addAssetPath( webViewContext.getApplicationInfo().sourceDir); ClassLoader clazzLoader = webViewContext.getClassLoader(); @@ -272,10 +317,8 @@ public final class WebViewFactory { private static String[] getWebViewNativeLibraryPaths() throws PackageManager.NameNotFoundException { - final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so"; - - PackageManager pm = AppGlobals.getInitialApplication().getPackageManager(); - ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0); + ApplicationInfo ai = getWebViewApplicationInfo(); + final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai); String path32; String path64; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index e7b6238c91a6..c9d9a8c0513a 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3116,6 +3116,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + private boolean performStylusButtonPressAction(MotionEvent ev) { + if (ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS + && ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY) + && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { + final View child = getChildAt(mMotionPosition - mFirstPosition); + if (child != null) { + final int longPressPosition = mMotionPosition; + final long longPressId = mAdapter.getItemId(mMotionPosition); + if (performLongPress(child, longPressPosition, longPressId)) { + mTouchMode = TOUCH_MODE_REST; + setPressed(false); + child.setPressed(false); + return true; + } + } + } + return false; + } + boolean performLongPress(final View child, final int longPressPosition, final long longPressId) { // CHOICE_MODE_MULTIPLE_MODAL takes over long press. @@ -3757,8 +3776,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION - && performButtonActionOnTouchDown(ev)) { - removeCallbacks(mPendingCheckForTap); + && (performButtonActionOnTouchDown(ev) || performStylusButtonPressAction(ev))) { + removeCallbacks(mPendingCheckForTap); } } @@ -3800,6 +3819,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchMode = TOUCH_MODE_DONE_WAITING; updateSelectorState(); } else if (motionView != null) { + if (performStylusButtonPressAction(ev)) { + removeCallbacks(mPendingCheckForTap); + removeCallbacks(mPendingCheckForLongPress); + } + // Still within bounds, update the hotspot. final float[] point = mTmpPoint; point[0] = x; diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index d6f9f786c033..ff74c605dcd2 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -703,9 +703,9 @@ public abstract class AbsSeekBar extends ProgressBar { // fallthrough case KeyEvent.KEYCODE_DPAD_RIGHT: increment = isLayoutRtl() ? -increment : increment; - int progress = getProgress() + increment; - if (progress > 0 && progress < getMax()) { - setProgress(progress, true); + + // Let progress bar handle clamping values. + if (setProgress(getProgress() + increment, true)) { onKeyChange(); return true; } @@ -729,10 +729,10 @@ public abstract class AbsSeekBar extends ProgressBar { if (isEnabled()) { final int progress = getProgress(); if (progress > 0) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); } if (progress < getMax()) { - info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); } } } @@ -743,29 +743,26 @@ public abstract class AbsSeekBar extends ProgressBar { if (super.performAccessibilityActionInternal(action, arguments)) { return true; } + if (!isEnabled()) { return false; } - final int progress = getProgress(); - final int increment = Math.max(1, Math.round((float) getMax() / 5)); - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (progress <= 0) { - return false; - } - setProgress(progress - increment, true); - onKeyChange(); - return true; + + if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD + || action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { + int increment = Math.max(1, Math.round((float) getMax() / 5)); + if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { + increment = -increment; } - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (progress >= getMax()) { - return false; - } - setProgress(progress + increment, true); + + // Let progress bar handle clamping values. + if (setProgress(getProgress() + increment, true)) { onKeyChange(); return true; } + return false; } + return false; } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 1be05f38eb69..955ad06814cb 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -17,6 +17,7 @@ package android.widget; import android.R; +import android.annotation.Nullable; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.content.ClipData; @@ -1008,14 +1009,14 @@ public class Editor { stopSelectionActionMode(); } else { stopSelectionActionMode(); - startSelectionActionModeWithSelection(); + startSelectionActionModeWithSelectionAndStartDrag(); } handled = true; } // Start a new selection if (!handled) { - handled = startSelectionActionModeWithSelection(); + handled = startSelectionActionModeWithSelectionAndStartDrag(); } return handled; @@ -1281,74 +1282,79 @@ public class Editor { EXTRACT_UNKNOWN, outText); } - private boolean extractTextInternal(ExtractedTextRequest request, + private boolean extractTextInternal(@Nullable ExtractedTextRequest request, int partialStartOffset, int partialEndOffset, int delta, - ExtractedText outText) { + @Nullable ExtractedText outText) { + if (request == null || outText == null) { + return false; + } + final CharSequence content = mTextView.getText(); - if (content != null) { - if (partialStartOffset != EXTRACT_NOTHING) { - final int N = content.length(); - if (partialStartOffset < 0) { - outText.partialStartOffset = outText.partialEndOffset = -1; - partialStartOffset = 0; - partialEndOffset = N; - } else { - // Now use the delta to determine the actual amount of text - // we need. - partialEndOffset += delta; - // Adjust offsets to ensure we contain full spans. - if (content instanceof Spanned) { - Spanned spanned = (Spanned)content; - Object[] spans = spanned.getSpans(partialStartOffset, - partialEndOffset, ParcelableSpan.class); - int i = spans.length; - while (i > 0) { - i--; - int j = spanned.getSpanStart(spans[i]); - if (j < partialStartOffset) partialStartOffset = j; - j = spanned.getSpanEnd(spans[i]); - if (j > partialEndOffset) partialEndOffset = j; - } - } - outText.partialStartOffset = partialStartOffset; - outText.partialEndOffset = partialEndOffset - delta; + if (content == null) { + return false; + } - if (partialStartOffset > N) { - partialStartOffset = N; - } else if (partialStartOffset < 0) { - partialStartOffset = 0; - } - if (partialEndOffset > N) { - partialEndOffset = N; - } else if (partialEndOffset < 0) { - partialEndOffset = 0; + if (partialStartOffset != EXTRACT_NOTHING) { + final int N = content.length(); + if (partialStartOffset < 0) { + outText.partialStartOffset = outText.partialEndOffset = -1; + partialStartOffset = 0; + partialEndOffset = N; + } else { + // Now use the delta to determine the actual amount of text + // we need. + partialEndOffset += delta; + // Adjust offsets to ensure we contain full spans. + if (content instanceof Spanned) { + Spanned spanned = (Spanned)content; + Object[] spans = spanned.getSpans(partialStartOffset, + partialEndOffset, ParcelableSpan.class); + int i = spans.length; + while (i > 0) { + i--; + int j = spanned.getSpanStart(spans[i]); + if (j < partialStartOffset) partialStartOffset = j; + j = spanned.getSpanEnd(spans[i]); + if (j > partialEndOffset) partialEndOffset = j; } } - if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { - outText.text = content.subSequence(partialStartOffset, - partialEndOffset); - } else { - outText.text = TextUtils.substring(content, partialStartOffset, - partialEndOffset); + outText.partialStartOffset = partialStartOffset; + outText.partialEndOffset = partialEndOffset - delta; + + if (partialStartOffset > N) { + partialStartOffset = N; + } else if (partialStartOffset < 0) { + partialStartOffset = 0; + } + if (partialEndOffset > N) { + partialEndOffset = N; + } else if (partialEndOffset < 0) { + partialEndOffset = 0; } - } else { - outText.partialStartOffset = 0; - outText.partialEndOffset = 0; - outText.text = ""; - } - outText.flags = 0; - if (MetaKeyKeyListener.getMetaState(content, MetaKeyKeyListener.META_SELECTING) != 0) { - outText.flags |= ExtractedText.FLAG_SELECTING; } - if (mTextView.isSingleLine()) { - outText.flags |= ExtractedText.FLAG_SINGLE_LINE; + if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { + outText.text = content.subSequence(partialStartOffset, + partialEndOffset); + } else { + outText.text = TextUtils.substring(content, partialStartOffset, + partialEndOffset); } - outText.startOffset = 0; - outText.selectionStart = mTextView.getSelectionStart(); - outText.selectionEnd = mTextView.getSelectionEnd(); - return true; + } else { + outText.partialStartOffset = 0; + outText.partialEndOffset = 0; + outText.text = ""; } - return false; + outText.flags = 0; + if (MetaKeyKeyListener.getMetaState(content, MetaKeyKeyListener.META_SELECTING) != 0) { + outText.flags |= ExtractedText.FLAG_SELECTING; + } + if (mTextView.isSingleLine()) { + outText.flags |= ExtractedText.FLAG_SINGLE_LINE; + } + outText.startOffset = 0; + outText.selectionStart = mTextView.getSelectionStart(); + outText.selectionEnd = mTextView.getSelectionEnd(); + return true; } boolean reportExtractedText() { @@ -1680,9 +1686,34 @@ public class Editor { } /** + * Starts a Selection Action Mode with the current selection and enters drag mode. This should + * be used whenever the mode is started from a touch event. + * + * @return true if the selection mode was actually started. + */ + private boolean startSelectionActionModeWithSelectionAndStartDrag() { + boolean selectionStarted = startSelectionActionModeWithSelectionInternal(); + if (selectionStarted) { + getSelectionController().enterDrag(); + } + return selectionStarted; + } + + /** + * Starts a Selection Action Mode with the current selection and ensures the selection handles + * are shown. This should be used when the mode is started from a non-touch event. + * * @return true if the selection mode was actually started. */ boolean startSelectionActionModeWithSelection() { + boolean selectionStarted = startSelectionActionModeWithSelectionInternal(); + if (selectionStarted) { + getSelectionController().show(); + } + return selectionStarted; + } + + private boolean startSelectionActionModeWithSelectionInternal() { if (mSelectionActionMode != null) { // Selection action mode is already started mSelectionActionMode.invalidate(); @@ -1721,10 +1752,6 @@ public class Editor { imm.showSoftInput(mTextView, 0, null); } } - - if (selectionStarted) { - getSelectionController().enterDrag(); - } return selectionStarted; } @@ -3093,6 +3120,7 @@ public class Editor { if (item.getIntent() != null && item.getIntent().getAction().equals(Intent.ACTION_PROCESS_TEXT)) { item.getIntent().putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText()); + mPreserveDetachedSelection = true; mTextView.startActivityForResult( item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE); return true; @@ -4231,7 +4259,7 @@ public class Editor { boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; if (stayedInArea && isPositionOnText(x, y)) { - startSelectionActionModeWithSelection(); + startSelectionActionModeWithSelectionAndStartDrag(); mDiscardNextActionUp = true; } } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 4b5407a8a5dc..552b274065f9 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -662,7 +662,8 @@ class FastScroller { final int adjMaxWidth = maxWidth - marginLeft - marginRight; final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST); - final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(container.height(), + MeasureSpec.UNSPECIFIED); view.measure(widthMeasureSpec, heightMeasureSpec); // Align to the left or right. @@ -701,7 +702,8 @@ class FastScroller { final int containerWidth = container.width(); final int adjMaxWidth = containerWidth - marginLeft - marginRight; final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(adjMaxWidth, MeasureSpec.AT_MOST); - final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(container.height(), + MeasureSpec.UNSPECIFIED); preview.measure(widthMeasureSpec, heightMeasureSpec); // Align at the vertical center, 10% from the top. @@ -766,7 +768,8 @@ class FastScroller { final Rect container = mContainerRect; final int maxWidth = container.width(); final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST); - final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(container.height(), + MeasureSpec.UNSPECIFIED); track.measure(widthMeasureSpec, heightMeasureSpec); final int top; diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index be0ca71907df..9ecdc9c35214 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1071,7 +1071,8 @@ public class GridView extends AbsListView { p.forceAdd = true; int childHeightSpec = getChildMeasureSpec( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height); + MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), + MeasureSpec.UNSPECIFIED), 0, p.height); int childWidthSpec = getChildMeasureSpec( MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width); child.measure(childWidthSpec, childHeightSpec); diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index da15302b3552..72f51c91da5f 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -1058,8 +1058,11 @@ public class LinearLayout extends ViewGroup { // use as much space as it wants because we can shrink things // later (and re-measure). if (baselineAligned) { - final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - child.measure(freeSpec, freeSpec); + final int freeWidthSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED); + final int freeHeightSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED); + child.measure(freeWidthSpec, freeHeightSpec); } else { skippedMeasure = true; } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index def5929e33ca..a79c8e891c9a 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1155,7 +1155,7 @@ public class ListView extends AbsListView { heightMode == MeasureSpec.UNSPECIFIED)) { final View child = obtainView(0, mIsScrap); - measureScrapChild(child, 0, widthMeasureSpec); + measureScrapChild(child, 0, widthMeasureSpec, heightSize); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); @@ -1188,7 +1188,7 @@ public class ListView extends AbsListView { mWidthMeasureSpec = widthMeasureSpec; } - private void measureScrapChild(View child, int position, int widthMeasureSpec) { + private void measureScrapChild(View child, int position, int widthMeasureSpec, int heightHint) { LayoutParams p = (LayoutParams) child.getLayoutParams(); if (p == null) { p = (AbsListView.LayoutParams) generateDefaultLayoutParams(); @@ -1204,7 +1204,7 @@ public class ListView extends AbsListView { if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { - childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + childHeightSpec = MeasureSpec.makeMeasureSpec(heightHint, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } @@ -1271,7 +1271,7 @@ public class ListView extends AbsListView { for (i = startPosition; i <= endPosition; ++i) { child = obtainView(i, isScrap); - measureScrapChild(child, i, widthMeasureSpec); + measureScrapChild(child, i, widthMeasureSpec, maxHeight); if (i > 0) { // Count the divider for all but one child @@ -1941,7 +1941,8 @@ public class ListView extends AbsListView { if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { - childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + childHeightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), + MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } else { @@ -2695,7 +2696,8 @@ public class ListView extends AbsListView { if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { - childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + childHeightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), + MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 24e9cbef3091..b59ae17ebcea 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -43,6 +43,7 @@ import android.graphics.drawable.shapes.Shape; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.MathUtils; import android.util.Pools.SynchronizedPool; import android.view.Gravity; import android.view.RemotableViewMethod; @@ -1341,23 +1342,22 @@ public class ProgressBar extends View { } @android.view.RemotableViewMethod - synchronized void setProgress(int progress, boolean fromUser) { + synchronized boolean setProgress(int progress, boolean fromUser) { if (mIndeterminate) { - return; + // Not applicable. + return false; } - if (progress < 0) { - progress = 0; - } + progress = MathUtils.constrain(progress, 0, mMax); - if (progress > mMax) { - progress = mMax; + if (progress == mProgress) { + // No change from current. + return false; } - if (progress != mProgress) { - mProgress = progress; - refreshProgress(R.id.progress, mProgress, fromUser); - } + mProgress = progress; + refreshProgress(R.id.progress, mProgress, fromUser); + return true; } /** diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index b95c27d35208..202616969eb3 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -1238,7 +1238,8 @@ public class ScrollView extends FrameLayout { } @Override - protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { + protected void measureChild(View child, int parentWidthMeasureSpec, + int parentHeightMeasureSpec) { ViewGroup.LayoutParams lp = child.getLayoutParams(); int childWidthMeasureSpec; @@ -1247,7 +1248,8 @@ public class ScrollView extends FrameLayout { childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); - childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @@ -1261,7 +1263,7 @@ public class ScrollView extends FrameLayout { mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); + MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 3746ec6108bc..095cc444c025 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -811,9 +811,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { View itemView = null; int itemType = 0; final int widthMeasureSpec = - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED); final int heightMeasureSpec = - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED); // Make sure the number of items we'll measure is capped. If it's a huge data set // with wildly varying sizes, oh well. diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 9496e6295dfc..aa7168c617f0 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -173,11 +173,12 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { } // First, measure with no constraint - final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int width = MeasureSpec.getSize(widthMeasureSpec); + final int unspecifiedWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED); mImposedTabsHeight = -1; super.measureHorizontal(unspecifiedWidth, heightMeasureSpec); - int extraWidth = getMeasuredWidth() - MeasureSpec.getSize(widthMeasureSpec); + int extraWidth = getMeasuredWidth() - width; if (extraWidth > 0) { final int count = getChildCount(); diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java index 093bdcf61d21..6fdd8748bd6d 100644 --- a/core/java/android/widget/TableLayout.java +++ b/core/java/android/widget/TableLayout.java @@ -467,7 +467,7 @@ public class TableLayout extends LinearLayout { */ @Override void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { - findLargestCells(widthMeasureSpec); + findLargestCells(widthMeasureSpec, heightMeasureSpec); shrinkAndStretchColumns(widthMeasureSpec); super.measureVertical(widthMeasureSpec, heightMeasureSpec); @@ -479,7 +479,7 @@ public class TableLayout extends LinearLayout { * * @param widthMeasureSpec the measure constraint imposed by our parent */ - private void findLargestCells(int widthMeasureSpec) { + private void findLargestCells(int widthMeasureSpec, int heightMeasureSpec) { boolean firstRow = true; // find the maximum width for each column @@ -502,7 +502,7 @@ public class TableLayout extends LinearLayout { final ViewGroup.LayoutParams layoutParams = row.getLayoutParams(); layoutParams.height = LayoutParams.WRAP_CONTENT; - final int[] widths = row.getColumnsWidths(widthMeasureSpec); + final int[] widths = row.getColumnsWidths(widthMeasureSpec, heightMeasureSpec); final int newLength = widths.length; // this is the first row, we just need to copy the values if (firstRow) { diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index faf5b84809c4..f73ee49da2f1 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -283,7 +283,7 @@ public class TableRow extends LinearLayout { * column, in this row * {@hide} */ - int[] getColumnsWidths(int widthMeasureSpec) { + int[] getColumnsWidths(int widthMeasureSpec, int heightMeasureSpec) { final int numColumns = getVirtualChildCount(); if (mColumnWidths == null || numColumns != mColumnWidths.length) { mColumnWidths = new int[numColumns]; @@ -302,7 +302,9 @@ public class TableRow extends LinearLayout { spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT); break; case LayoutParams.MATCH_PARENT: - spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + spec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(heightMeasureSpec), + MeasureSpec.UNSPECIFIED); break; default: spec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b44a8863eb81..732830027290 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -236,6 +236,9 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; * @attr ref android.R.styleable#TextView_elegantTextHeight * @attr ref android.R.styleable#TextView_letterSpacing * @attr ref android.R.styleable#TextView_fontFeatureSettings + * @attr ref android.R.styleable#TextView_breakStrategy + * @attr ref android.R.styleable#TextView_leftIndents + * @attr ref android.R.styleable#TextView_rightIndents */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { @@ -551,6 +554,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private float mSpacingAdd = 0.0f; private int mBreakStrategy; + private int[] mLeftIndents; + private int[] mRightIndents; private int mMaximum = Integer.MAX_VALUE; private int mMaxMode = LINES; @@ -1146,6 +1151,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_breakStrategy: mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); + break; + + case com.android.internal.R.styleable.TextView_leftIndents: + TypedArray margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID)); + mLeftIndents = parseDimensionArray(margins); + break; + + case com.android.internal.R.styleable.TextView_rightIndents: + margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID)); + mRightIndents = parseDimensionArray(margins); + break; } } a.recycle(); @@ -1421,6 +1437,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private int[] parseDimensionArray(TypedArray dimens) { + if (dimens == null) { + return null; + } + int[] result = new int[dimens.length()]; + for (int i = 0; i < result.length; i++) { + result[i] = dimens.getDimensionPixelSize(i, 0); + } + return result; + } + /** * @hide */ @@ -3019,6 +3046,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Set indents. Arguments are arrays holding an indent amount, one per line, measured in + * pixels. For lines past the last element in the array, the last element repeats. + * + * @param leftIndents array of indent values for left margin, in pixels + * @param rightIndents array of indent values for right margin, in pixels + * + * @see #getLeftIndents() + * @see #getRightIndents() + * + * @attr ref android.R.styleable#TextView_leftIndents + * @attr ref android.R.styleable#TextView_rightIndents + */ + public void setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) { + mLeftIndents = leftIndents; + mRightIndents = rightIndents; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * Get left indents. See {#link setMargins} for more details. + * + * @return left indents + * @see #setIndents(int[], int[]) + * @attr ref android.R.styleable#TextView_leftIndents + */ + public int[] getLeftIndents() { + return mLeftIndents; + } + + /** + * Get right indents. See {#link setMargins} for more details. + * + * @return right indents + * @see #setIndents(int[], int[]) + * @attr ref android.R.styleable#TextView_rightIndents + */ + public int[] getRightIndents() { + return mRightIndents; + } + + /** * Sets font feature settings. The format is the same as the CSS * font-feature-settings attribute: * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings @@ -6564,6 +6636,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setSpacingAdd(mSpacingAdd) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy); + if (mLeftIndents != null || mRightIndents != null) { + builder.setIndents(mLeftIndents, mRightIndents); + } if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth) @@ -6652,6 +6727,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setSpacingAdd(mSpacingAdd) .setIncludePad(mIncludePad) .setBreakStrategy(mBreakStrategy); + if (mLeftIndents != null || mRightIndents != null) { + builder.setIndents(mLeftIndents, mRightIndents); + } if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth) @@ -9124,6 +9202,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void replaceSelectionWithText(CharSequence text) { ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); + mEditor.startSelectionActionModeWithSelection(); } /** diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 4f0e29ebed39..7c5c56555bdf 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -95,9 +95,6 @@ interface IBatteryStats { void noteWifiState(int wifiState, String accessPoint); void noteWifiSupplicantStateChanged(int supplState, boolean failedAuth); void noteWifiRssiChanged(int newRssi); - void noteBluetoothOn(); - void noteBluetoothOff(); - void noteBluetoothState(int bluetoothState); void noteFullWifiLockAcquired(int uid); void noteFullWifiLockReleased(int uid); void noteWifiScanStarted(int uid); diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 59dbec6098d3..a53d46c8cead 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -136,6 +136,14 @@ public final class BatteryStatsHelper { profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0; } + public static boolean checkHasBluetoothPowerReporting(BatteryStats stats, + PowerProfile profile) { + return stats.hasBluetoothActivityReporting() && + profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE) != 0 && + profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX) != 0 && + profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX) != 0; + } + public BatteryStatsHelper(Context context) { this(context, true); } @@ -256,7 +264,8 @@ public final class BatteryStatsHelper { } public static String makemAh(double power) { - if (power < .00001) return String.format("%.8f", power); + if (power == 0) return "0"; + else if (power < .00001) return String.format("%.8f", power); else if (power < .0001) return String.format("%.7f", power); else if (power < .001) return String.format("%.6f", power); else if (power < .01) return String.format("%.5f", power); @@ -342,7 +351,11 @@ public final class BatteryStatsHelper { mWifiPowerCalculator.reset(); if (mBluetoothPowerCalculator == null) { - mBluetoothPowerCalculator = new BluetoothPowerCalculator(); + if (checkHasBluetoothPowerReporting(mStats, mPowerProfile)) { + mBluetoothPowerCalculator = new BluetoothPowerCalculator(); + } else { + mBluetoothPowerCalculator = new BluetoothPowerCalculator(); + } } mBluetoothPowerCalculator.reset(); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 87605f680d27..405c861a498a 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -19,8 +19,6 @@ package com.android.internal.os; import android.annotation.Nullable; import android.app.ActivityManager; import android.bluetooth.BluetoothActivityEnergyInfo; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; @@ -84,7 +82,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -107,7 +104,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 123 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 125 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -176,7 +173,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public interface ExternalStatsSync { - void scheduleSync(); + void scheduleSync(String reason); } public final MyHandler mHandler; @@ -250,6 +247,8 @@ public final class BatteryStatsImpl extends BatteryStats { int mNumHistoryTagChars = 0; int mHistoryBufferLastPos = -1; boolean mHistoryOverflow = false; + int mActiveHistoryStates = 0xffffffff; + int mActiveHistoryStates2 = 0xffffffff; long mLastHistoryElapsedRealtime = 0; long mTrackRunningHistoryElapsedRealtime = 0; long mTrackRunningHistoryUptime = 0; @@ -313,7 +312,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mWakeLockNesting; boolean mWakeLockImportant; - boolean mRecordAllHistory; + public boolean mRecordAllHistory; boolean mNoAutoReset; int mScreenState = Display.STATE_UNKNOWN; @@ -384,12 +383,6 @@ public final class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mWifiSignalStrengthsTimer = new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS]; - boolean mBluetoothOn; - StopwatchTimer mBluetoothOnTimer; - - int mBluetoothState = -1; - final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES]; - int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; long mMobileRadioActiveStartTime; StopwatchTimer mMobileRadioActiveTimer; @@ -398,8 +391,7 @@ public final class BatteryStatsImpl extends BatteryStats { LongSamplingCounter mMobileRadioActiveUnknownTime; LongSamplingCounter mMobileRadioActiveUnknownCount; - /** Bluetooth headset object */ - BluetoothHeadset mBtHeadset; + int mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; /** * These provide time bases that discount the time the device is plugged @@ -458,9 +450,6 @@ public final class BatteryStatsImpl extends BatteryStats { long mLastWriteTime = 0; // Milliseconds - private int mBluetoothPingCount; - private int mBluetoothPingStart = -1; - private int mPhoneServiceState = -1; private int mPhoneServiceStateRaw = -1; private int mPhoneSimStateRaw = -1; @@ -1803,32 +1792,6 @@ public final class BatteryStatsImpl extends BatteryStats { return kwlt; } - private int getCurrentBluetoothPingCount() { - if (mBtHeadset != null) { - List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices(); - if (deviceList.size() > 0) { - return mBtHeadset.getBatteryUsageHint(deviceList.get(0)); - } - } - return -1; - } - - public int getBluetoothPingCount() { - if (mBluetoothPingStart == -1) { - return mBluetoothPingCount; - } else if (mBtHeadset != null) { - return getCurrentBluetoothPingCount() - mBluetoothPingStart; - } - return 0; - } - - public void setBtHeadset(BluetoothHeadset headset) { - if (headset != null && mBtHeadset == null && isOnBattery() && mBluetoothPingStart == -1) { - mBluetoothPingStart = getCurrentBluetoothPingCount(); - } - mBtHeadset = headset; - } - private int writeHistoryTag(HistoryTag tag) { Integer idxObj = mHistoryTagPool.get(tag); int idx; @@ -2259,8 +2222,8 @@ public final class BatteryStatsImpl extends BatteryStats { } final long timeDiff = (mHistoryBaseTime+elapsedRealtimeMs) - mHistoryLastWritten.time; - final int diffStates = mHistoryLastWritten.states^cur.states; - final int diffStates2 = mHistoryLastWritten.states2^cur.states2; + final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates); + final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2); final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states; final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2; if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff=" @@ -2325,11 +2288,32 @@ public final class BatteryStatsImpl extends BatteryStats { return; } + // After overflow, we allow various bit-wise states to settle to 0. + boolean writeAnyway = false; + final int curStates = cur.states & HistoryItem.SETTLE_TO_ZERO_STATES + & mActiveHistoryStates; + if (mHistoryLastWritten.states != curStates) { + // mActiveHistoryStates keeps track of which bits in .states are now being + // forced to 0. + int old = mActiveHistoryStates; + mActiveHistoryStates &= curStates | ~HistoryItem.SETTLE_TO_ZERO_STATES; + writeAnyway |= old != mActiveHistoryStates; + } + final int curStates2 = cur.states2 & HistoryItem.SETTLE_TO_ZERO_STATES2 + & mActiveHistoryStates2; + if (mHistoryLastWritten.states2 != curStates2) { + // mActiveHistoryStates2 keeps track of which bits in .states2 are now being + // forced to 0. + int old = mActiveHistoryStates2; + mActiveHistoryStates2 &= curStates2 | ~HistoryItem.SETTLE_TO_ZERO_STATES2; + writeAnyway |= old != mActiveHistoryStates2; + } + // Once we've reached the maximum number of items, we only // record changes to the battery level and the most interesting states. // Once we've reached the maximum maximum number of items, we only // record changes to the battery level. - if (mHistoryLastWritten.batteryLevel == cur.batteryLevel && + if (!writeAnyway && mHistoryLastWritten.batteryLevel == cur.batteryLevel && (dataSize >= MAX_MAX_HISTORY_BUFFER || ((mHistoryLastWritten.states^cur.states) & HistoryItem.MOST_INTERESTING_STATES) == 0 @@ -2360,6 +2344,8 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryBufferLastPos = mHistoryBuffer.dataPosition(); mHistoryLastLastWritten.setTo(mHistoryLastWritten); mHistoryLastWritten.setTo(mHistoryBaseTime + elapsedRealtimeMs, cmd, cur); + mHistoryLastWritten.states &= mActiveHistoryStates; + mHistoryLastWritten.states2 &= mActiveHistoryStates2; writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); mLastHistoryElapsedRealtime = elapsedRealtimeMs; cur.wakelockTag = null; @@ -2411,8 +2397,8 @@ public final class BatteryStatsImpl extends BatteryStats { // into one record. if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE && (mHistoryBaseTime+elapsedRealtimeMs) < (mHistoryEnd.time+1000) - && ((mHistoryEnd.states^cur.states)&mChangedStates) == 0 - && ((mHistoryEnd.states2^cur.states2)&mChangedStates2) == 0) { + && ((mHistoryEnd.states^cur.states)&mChangedStates&mActiveHistoryStates) == 0 + && ((mHistoryEnd.states2^cur.states2)&mChangedStates2&mActiveHistoryStates2) == 0) { // If the current is the same as the one before, then we no // longer need the entry. if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE @@ -2424,8 +2410,8 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryEnd = mHistoryLastEnd; mHistoryLastEnd = null; } else { - mChangedStates |= mHistoryEnd.states^cur.states; - mChangedStates2 |= mHistoryEnd.states^cur.states2; + mChangedStates |= mHistoryEnd.states^(cur.states&mActiveHistoryStates); + mChangedStates2 |= mHistoryEnd.states^(cur.states2&mActiveHistoryStates2); mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, cur); } return; @@ -2447,7 +2433,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (mHistoryEnd != null && mHistoryEnd.batteryLevel == cur.batteryLevel && (mNumHistoryItems >= MAX_MAX_HISTORY_ITEMS - || ((mHistoryEnd.states^cur.states) + || ((mHistoryEnd.states^(cur.states&mActiveHistoryStates)) & HistoryItem.MOST_INTERESTING_STATES) == 0)) { return; } @@ -2456,7 +2442,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE); } - void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, + public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code, String name, int uid) { mHistoryCur.eventCode = code; mHistoryCur.eventTag = mHistoryCur.localEventTag; @@ -2515,23 +2501,15 @@ public final class BatteryStatsImpl extends BatteryStats { mNumHistoryTagChars = 0; mHistoryBufferLastPos = -1; mHistoryOverflow = false; + mActiveHistoryStates = 0xffffffff; + mActiveHistoryStates2 = 0xffffffff; mLastRecordedClockTime = 0; mLastRecordedClockRealtime = 0; } public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime, long realtime) { - if (mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime)) { - if (unplugged) { - // Track bt headset ping count - mBluetoothPingStart = getCurrentBluetoothPingCount(); - mBluetoothPingCount = 0; - } else { - // Track bt headset ping count - mBluetoothPingCount = getBluetoothPingCount(); - mBluetoothPingStart = -1; - } - } + mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime); boolean unpluggedScreenOff = unplugged && screenOff; if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) { @@ -3396,7 +3374,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (!mPhoneOn) { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3409,7 +3387,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (mPhoneOn) { final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG; + mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtime, uptime); @@ -3626,7 +3604,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = true; mWifiOnTimer.startRunningLocked(elapsedRealtime); - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("wifi-off"); } } @@ -3640,7 +3618,7 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mWifiOn = false; mWifiOnTimer.stopRunningLocked(elapsedRealtime); - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("wifi-on"); } } @@ -3788,6 +3766,25 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteWifiRadioPowerState(int powerState, long timestampNs) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + if (mWifiRadioPowerState != powerState) { + final boolean active = + powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM + || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; + if (active) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + } else { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + } + if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(elapsedRealtime, uptime); + mWifiRadioPowerState = powerState; + } + } + public void noteWifiRunningLocked(WorkSource ws) { if (!mGlobalWifiRunning) { final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -3803,7 +3800,7 @@ public final class BatteryStatsImpl extends BatteryStats { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime); } - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("wifi-running"); } else { Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running"); } @@ -3842,7 +3839,7 @@ public final class BatteryStatsImpl extends BatteryStats { int uid = mapUid(ws.get(i)); getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime); } - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("wifi-stopped"); } else { Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running"); } @@ -3857,7 +3854,7 @@ public final class BatteryStatsImpl extends BatteryStats { } mWifiState = wifiState; mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime); - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("wifi-state"); } } @@ -3919,46 +3916,6 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public void noteBluetoothOnLocked() { - if (!mBluetoothOn) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG; - if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(elapsedRealtime, uptime); - mBluetoothOn = true; - mBluetoothOnTimer.startRunningLocked(elapsedRealtime); - scheduleSyncExternalStatsLocked(); - } - } - - public void noteBluetoothOffLocked() { - if (mBluetoothOn) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG; - if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(elapsedRealtime, uptime); - mBluetoothOn = false; - mBluetoothOnTimer.stopRunningLocked(elapsedRealtime); - scheduleSyncExternalStatsLocked(); - } - } - - public void noteBluetoothStateLocked(int bluetoothState) { - if (DEBUG) Log.i(TAG, "Bluetooth state -> " + bluetoothState); - if (mBluetoothState != bluetoothState) { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - if (mBluetoothState >= 0) { - mBluetoothStateTimer[mBluetoothState].stopRunningLocked(elapsedRealtime); - } - mBluetoothState = bluetoothState; - mBluetoothStateTimer[bluetoothState].startRunningLocked(elapsedRealtime); - } - } - int mWifiFullLockNesting = 0; public void noteFullWifiLockAcquiredLocked(int uid) { @@ -4313,20 +4270,6 @@ public final class BatteryStatsImpl extends BatteryStats { return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which); } - @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) { - return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); - } - - @Override public long getBluetoothStateTime(int bluetoothState, - long elapsedRealtimeUs, int which) { - return mBluetoothStateTimer[bluetoothState].getTotalTimeLocked( - elapsedRealtimeUs, which); - } - - @Override public int getBluetoothStateCount(int bluetoothState, int which) { - return mBluetoothStateTimer[bluetoothState].getCountLocked(which); - } - @Override public boolean hasBluetoothActivityReporting() { return mHasBluetoothEnergyReporting; } @@ -6780,10 +6723,6 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i, null, mOnBatteryTimeBase); } - mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase); - } mAudioOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase); mVideoOnTimer = new StopwatchTimer(null, -8, null, mOnBatteryTimeBase); mFlashlightOnTimer = new StopwatchTimer(null, -9, null, mOnBatteryTimeBase); @@ -7399,10 +7338,6 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].reset(false); } - mBluetoothOnTimer.reset(false); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i].reset(false); - } for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mBluetoothActivityCounters[i].reset(false); mWifiActivityCounters[i].reset(false); @@ -7727,16 +7662,12 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( info.getControllerIdleTimeMillis()); - final double powerDrainMaMs; - if (mPowerProfile.getAveragePower( - PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) == 0) { - powerDrainMaMs = 0.0; - } else { - powerDrainMaMs = info.getControllerEnergyUsed() - / mPowerProfile.getAveragePower( - PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE); + final double opVoltage = mPowerProfile.getAveragePower( + PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE); + if (opVoltage != 0) { + mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked( + (long)(info.getControllerEnergyUsed() / opVoltage)); } - mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked((long) powerDrainMaMs); } } @@ -7824,8 +7755,13 @@ public final class BatteryStatsImpl extends BatteryStats { info.getControllerTxTimeMillis()); mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked( info.getControllerIdleTimeMillis()); - mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked( - info.getControllerEnergyUsed()); + + final double opVoltage = mPowerProfile.getAveragePower( + PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE); + if (opVoltage != 0) { + mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked( + (long) (info.getControllerEnergyUsed() / opVoltage)); + } } } @@ -7871,9 +7807,9 @@ public final class BatteryStatsImpl extends BatteryStats { if (mCharging != charging) { mCharging = charging; if (charging) { - mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG; } else { - mHistoryCur.states &= ~HistoryItem.STATE_CHARGING_FLAG; + mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG; } mHandler.sendEmptyMessage(MSG_REPORT_CHARGING); return true; @@ -8039,9 +7975,9 @@ public final class BatteryStatsImpl extends BatteryStats { } } - private void scheduleSyncExternalStatsLocked() { + private void scheduleSyncExternalStatsLocked(String reason) { if (mExternalSync != null) { - mExternalSync.scheduleSync(); + mExternalSync.scheduleSync(reason); } } @@ -8067,7 +8003,7 @@ public final class BatteryStatsImpl extends BatteryStats { } } // Always start out assuming charging, that will be updated later. - mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG; + mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG; mHistoryCur.batteryStatus = (byte)status; mHistoryCur.batteryLevel = (byte)level; mMaxChargeStepLevel = mMinDischargeStepLevel = @@ -8109,7 +8045,7 @@ public final class BatteryStatsImpl extends BatteryStats { // TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record // which will pull external stats. - scheduleSyncExternalStatsLocked(); + scheduleSyncExternalStatsLocked("battery-level"); } if (mHistoryCur.batteryStatus != status) { mHistoryCur.batteryStatus = (byte)status; @@ -8910,6 +8846,7 @@ public final class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in); mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in); + mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer.readSummaryFromParcelLocked(in); mGlobalWifiRunning = false; @@ -8923,16 +8860,9 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].readSummaryFromParcelLocked(in); } - mBluetoothOn = false; - mBluetoothOnTimer.readSummaryFromParcelLocked(in); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i].readSummaryFromParcelLocked(in); - } - for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mBluetoothActivityCounters[i].readSummaryFromParcelLocked(in); } - for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mWifiActivityCounters[i].readSummaryFromParcelLocked(in); } @@ -9246,10 +9176,6 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); - } for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mBluetoothActivityCounters[i].writeSummaryFromParcelLocked(out); } @@ -9542,6 +9468,7 @@ public final class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in); mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in); + mWifiRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; mWifiOn = false; mWifiOnTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase, in); mGlobalWifiRunning = false; @@ -9558,17 +9485,9 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i] = new StopwatchTimer(null, -800-i, null, mOnBatteryTimeBase, in); } - mBluetoothOn = false; - mBluetoothOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase, in); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, - null, mOnBatteryTimeBase, in); - } - for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mBluetoothActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); } - for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in); } @@ -9598,9 +9517,6 @@ public final class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.readFromParcel(in); mLastWriteTime = in.readLong(); - mBluetoothPingCount = in.readInt(); - mBluetoothPingStart = -1; - mKernelWakelockStats.clear(); int NKW = in.readInt(); for (int ikw = 0; ikw < NKW; ikw++) { @@ -9718,10 +9634,6 @@ public final class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } - mBluetoothOnTimer.writeToParcel(out, uSecRealtime); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime); - } for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) { mBluetoothActivityCounters[i].writeToParcel(out); } @@ -9748,8 +9660,6 @@ public final class BatteryStatsImpl extends BatteryStats { mChargeStepTracker.writeToParcel(out); out.writeLong(mLastWriteTime); - out.writeInt(getBluetoothPingCount()); - if (inclUids) { out.writeInt(mKernelWakelockStats.size()); for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { @@ -9851,6 +9761,7 @@ public final class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveTimer.logState(pr, " "); pr.println("*** Mobile network active adjusted timer:"); mMobileRadioActiveAdjustedTime.logState(pr, " "); + pr.println("*** mWifiRadioPowerState=" + mWifiRadioPowerState); pr.println("*** Wifi timer:"); mWifiOnTimer.logState(pr, " "); pr.println("*** WifiRunning timer:"); @@ -9867,12 +9778,6 @@ public final class BatteryStatsImpl extends BatteryStats { pr.println("*** Wifi signal strength #" + i + ":"); mWifiSignalStrengthsTimer[i].logState(pr, " "); } - pr.println("*** Bluetooth timer:"); - mBluetoothOnTimer.logState(pr, " "); - for (int i=0; i< NUM_BLUETOOTH_STATES; i++) { - pr.println("*** Bluetooth active type #" + i + ":"); - mBluetoothStateTimer[i].logState(pr, " "); - } pr.println("*** Flashlight timer:"); mFlashlightOnTimer.logState(pr, " "); } diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 4fb8b55a5ddc..961b0df61596 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -71,7 +71,7 @@ public class WifiPowerCalculator extends PowerCalculator { app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs; double powerDrain = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN, - statsType) / (1000*60*60); + statsType) / (double)(1000*60*60); if (powerDrain == 0) { // Some controllers do not report power drain, so we can calculate it here. powerDrain = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java index 01723673d194..c4e2ef632d4f 100644 --- a/core/java/com/android/internal/os/WifiPowerEstimator.java +++ b/core/java/com/android/internal/os/WifiPowerEstimator.java @@ -63,7 +63,7 @@ public class WifiPowerEstimator extends PowerCalculator { mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); - final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType); + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); double wifiBatchScanPower = 0; diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 42d875df11fb..2946456aecce 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -367,7 +367,8 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi if (mTitleLayout != null && mCustomView == null) { if (mTitleOptional) { - final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int titleWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, + MeasureSpec.UNSPECIFIED); mTitleLayout.measure(titleWidthSpec, childSpecHeight); final int titleWidth = mTitleLayout.getMeasuredWidth(); final boolean titleFits = titleWidth <= availableWidth; diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 2219ad10c720..1e9694534355 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -203,8 +203,8 @@ public final class FloatingToolbar { if (mContentRect.top > mPopup.getHeight()) { y = mContentRect.top - mPopup.getHeight(); mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP; - } else if (mContentRect.top > getEstimatedToolbarHeight(mContext)) { - y = mContentRect.top - getEstimatedToolbarHeight(mContext); + } else if (mContentRect.top > mPopup.getToolbarHeightWithVerticalMargin()) { + y = mContentRect.top - mPopup.getToolbarHeightWithVerticalMargin(); mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN; } else { y = mContentRect.bottom; @@ -264,7 +264,8 @@ public final class FloatingToolbar { private final View mParent; private final PopupWindow mPopupWindow; private final ViewGroup mContentContainer; - private final int mPadding; + private final int mMarginHorizontal; + private final int mMarginVertical; private final Animation.AnimationListener mOnOverflowOpened = new Animation.AnimationListener() { @@ -365,7 +366,10 @@ public final class FloatingToolbar { .TOUCHABLE_INSETS_REGION); } }); - mPadding = parent.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_margin); + mMarginHorizontal = parent.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); + mMarginVertical = parent.getResources() + .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin); } /** @@ -474,6 +478,10 @@ public final class FloatingToolbar { return mContentContainer.getContext(); } + int getToolbarHeightWithVerticalMargin() { + return getEstimatedToolbarHeight(mParent.getContext()) + mMarginVertical * 2; + } + /** * Performs the "grow and fade in from the bottom" animation on the floating popup. */ @@ -506,7 +514,7 @@ public final class FloatingToolbar { mMainPanel.fadeOut(true); Size overflowPanelSize = mOverflowPanel.measure(); - final int targetWidth = getOverflowWidth(mParent.getContext()); + final int targetWidth = overflowPanelSize.getWidth(); final int targetHeight = overflowPanelSize.getHeight(); final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP); final int startWidth = mContentContainer.getWidth(); @@ -624,10 +632,14 @@ public final class FloatingToolbar { // Make sure the main panel is at the correct position. if (mContentContainer.getChildAt(0) == mMainPanel.getView()) { - mContentContainer.setX(mPadding); - float y = mPadding; + float x = mPopupWindow.getWidth() + - (mMainPanel.getView().getMeasuredWidth() + mMarginHorizontal); + mContentContainer.setX(x); + + float y = mMarginVertical; if (mOverflowDirection == OVERFLOW_DIRECTION_UP) { - y = getHeight() - getEstimatedToolbarHeight(mParent.getContext()) - mPadding; + y = getHeight() + - (mMainPanel.getView().getMeasuredHeight() + mMarginVertical); } mContentContainer.setY(y); } @@ -661,8 +673,8 @@ public final class FloatingToolbar { width = Math.max(width, overflowPanelSize.getWidth()); height = Math.max(height, overflowPanelSize.getHeight()); } - mPopupWindow.setWidth(width + mPadding * 2); - mPopupWindow.setHeight(height + mPadding * 2); + mPopupWindow.setWidth(width + mMarginHorizontal * 2); + mPopupWindow.setHeight(height + mMarginVertical * 2); } /** @@ -748,22 +760,22 @@ public final class FloatingToolbar { final MenuItem menuItem = remainingMenuItems.peek(); Button menuItemButton = createMenuItemButton(mContext, menuItem); - // Adding additional left padding for the first button to even out button spacing. + // Adding additional start padding for the first button to even out button spacing. if (isFirstItem) { - menuItemButton.setPadding( - 2 * menuItemButton.getPaddingLeft(), + menuItemButton.setPaddingRelative( + (int) (1.5 * menuItemButton.getPaddingStart()), menuItemButton.getPaddingTop(), - menuItemButton.getPaddingRight(), + menuItemButton.getPaddingEnd(), menuItemButton.getPaddingBottom()); isFirstItem = false; } - // Adding additional right padding for the last button to even out button spacing. + // Adding additional end padding for the last button to even out button spacing. if (remainingMenuItems.size() == 1) { - menuItemButton.setPadding( - menuItemButton.getPaddingLeft(), + menuItemButton.setPaddingRelative( + menuItemButton.getPaddingStart(), menuItemButton.getPaddingTop(), - 2 * menuItemButton.getPaddingRight(), + (int) (1.5 * menuItemButton.getPaddingEnd()), menuItemButton.getPaddingBottom()); } @@ -836,10 +848,12 @@ public final class FloatingToolbar { private final ViewGroup mBackButtonContainer; private final View mBackButton; private final ListView mListView; + private final TextView mListViewItemWidthCalculator; private final ViewFader mViewFader; private final Runnable mCloseOverflow; private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener; + private int mOverflowWidth = 0; /** * Initializes a floating toolbar popup overflow view panel. @@ -865,7 +879,7 @@ public final class FloatingToolbar { mBackButtonContainer = new LinearLayout(context); mBackButtonContainer.addView(mBackButton); - mListView = createOverflowListView(context); + mListView = createOverflowListView(); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { @@ -878,6 +892,10 @@ public final class FloatingToolbar { mContentView.addView(mListView); mContentView.addView(mBackButtonContainer); + + mListViewItemWidthCalculator = createOverflowMenuItemButton(context); + mListViewItemWidthCalculator.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } /** @@ -888,6 +906,7 @@ public final class FloatingToolbar { overflowListViewAdapter.clear(); overflowListViewAdapter.addAll(menuItems); setListViewHeight(); + setOverflowWidth(); } public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) { @@ -943,7 +962,21 @@ public final class FloatingToolbar { mListView.setLayoutParams(params); } - private static ListView createOverflowListView(final Context context) { + private int setOverflowWidth() { + for (int i = 0; i < mListView.getAdapter().getCount(); i++) { + MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(i); + Preconditions.checkNotNull(menuItem); + mListViewItemWidthCalculator.setText(menuItem.getTitle()); + mListViewItemWidthCalculator.measure( + MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + mOverflowWidth = Math.max( + mListViewItemWidthCalculator.getMeasuredWidth(), mOverflowWidth); + } + return mOverflowWidth; + } + + private ListView createOverflowListView() { + final Context context = mContentView.getContext(); final ListView overflowListView = new ListView(context); overflowListView.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -962,6 +995,7 @@ public final class FloatingToolbar { MenuItem menuItem = getItem(position); menuButton.setText(menuItem.getTitle()); menuButton.setContentDescription(menuItem.getTitle()); + menuButton.setMinimumWidth(mOverflowWidth); return menuButton; } }; @@ -1036,7 +1070,8 @@ public final class FloatingToolbar { private static PopupWindow createPopupWindow(View content) { ViewGroup popupContentHolder = new LinearLayout(content.getContext()); PopupWindow popupWindow = new PopupWindow(popupContentHolder); - popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + popupWindow.setWindowLayoutType( + WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); popupWindow.setAnimationStyle(0); popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); content.setLayoutParams(new ViewGroup.LayoutParams( @@ -1077,11 +1112,6 @@ public final class FloatingToolbar { return shrinkFadeOutFromBottomAnimation; } - private static int getOverflowWidth(Context context) { - return context.getResources() - .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_width); - } - private static int getEstimatedToolbarHeight(Context context) { return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height); } diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 65feab174e9a..0066ed0db859 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -400,11 +400,13 @@ public class SlidingTab extends ViewGroup { /** * Ensure all the dependent widgets are measured. */ - public void measure() { - tab.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); - text.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + public void measure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + tab.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.UNSPECIFIED)); + text.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.UNSPECIFIED)); } /** @@ -491,8 +493,8 @@ public class SlidingTab extends ViewGroup { } } - mLeftSlider.measure(); - mRightSlider.measure(); + mLeftSlider.measure(widthMeasureSpec, heightMeasureSpec); + mRightSlider.measure(widthMeasureSpec, heightMeasureSpec); final int leftTabWidth = mLeftSlider.getTabWidth(); final int rightTabWidth = mRightSlider.getTabWidth(); final int leftTabHeight = mLeftSlider.getTabHeight(); diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 7a934bde7834..5c95f8a9a9ae 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -857,6 +857,13 @@ static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapHandle) { bitmap->unlockPixels(); } +static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { + SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkPixelRef* pixelRef = bitmap ? bitmap->pixelRef() : nullptr; + SkSafeRef(pixelRef); + return reinterpret_cast<jlong>(pixelRef); +} + /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gBitmapMethods[] = { @@ -896,6 +903,7 @@ static JNINativeMethod gBitmapMethods[] = { (void*)Bitmap_copyPixelsFromBuffer }, { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs }, { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw }, + { "nativeRefPixelRef", "(J)J", (void*)Bitmap_refPixelRef }, }; int register_android_graphics_Bitmap(JNIEnv* env) diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp index 3f8bfe291013..348b0ec083b7 100644 --- a/core/jni/android/graphics/NinePatch.cpp +++ b/core/jni/android/graphics/NinePatch.cpp @@ -64,7 +64,7 @@ public: return JNI_FALSE; } - static jlong validateNinePatchChunk(JNIEnv* env, jobject, jlong, jbyteArray obj) { + static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) { size_t chunkSize = env->GetArrayLength(obj); if (chunkSize < (int) (sizeof(Res_png_9patch))) { jniThrowRuntimeException(env, "Array too small for chunk."); @@ -88,13 +88,13 @@ public: } } - static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds, const SkBitmap* bitmap, + static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds, const SkBitmap& bitmap, Res_png_9patch* chunk, const SkPaint* paint, jint destDensity, jint srcDensity) { if (destDensity == srcDensity || destDensity == 0 || srcDensity == 0) { ALOGV("Drawing unscaled 9-patch: (%g,%g)-(%g,%g)", SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop), SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom)); - NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL); + NinePatch_Draw(canvas, bounds, bitmap, *chunk, paint, NULL); } else { canvas->save(); @@ -111,25 +111,25 @@ public: SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom), srcDensity, destDensity); - NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL); + NinePatch_Draw(canvas, bounds, bitmap, *chunk, paint, NULL); canvas->restore(); } } static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF, - jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, + jobject jbitmap, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->asSkCanvas(); - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); SkASSERT(canvas); SkASSERT(boundsRectF); - SkASSERT(bitmap); SkASSERT(chunk); // paint is optional + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); SkRect bounds; GraphicsJNI::jrectf_to_rect(env, boundsRectF, &bounds); @@ -137,36 +137,36 @@ public: } static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect, - jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, + jobject jbitmap, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->asSkCanvas(); - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); SkASSERT(canvas); SkASSERT(boundsRect); - SkASSERT(bitmap); SkASSERT(chunk); // paint is optional + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); SkRect bounds; GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds); draw(env, canvas, bounds, bitmap, chunk, paint, destDensity, srcDensity); } - static jlong getTransparentRegion(JNIEnv* env, jobject, jlong bitmapHandle, + static jlong getTransparentRegion(JNIEnv* env, jobject, jobject jbitmap, jlong chunkHandle, jobject boundsRect) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); - SkASSERT(bitmap); SkASSERT(chunk); SkASSERT(boundsRect); + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); SkRect bounds; GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds); SkRegion* region = NULL; - NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, ®ion); + NinePatch_Draw(NULL, bounds, bitmap, *chunk, NULL, ®ion); return reinterpret_cast<jlong>(region); } @@ -176,13 +176,16 @@ public: ///////////////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gNinePatchMethods[] = { - { "isNinePatchChunk", "([B)Z", (void*) SkNinePatchGlue::isNinePatchChunk }, - { "validateNinePatchChunk", "(J[B)J", (void*) SkNinePatchGlue::validateNinePatchChunk }, - { "nativeFinalize", "(J)V", (void*) SkNinePatchGlue::finalize }, - { "nativeDraw", "(JLandroid/graphics/RectF;JJJII)V", (void*) SkNinePatchGlue::drawF }, - { "nativeDraw", "(JLandroid/graphics/Rect;JJJII)V", (void*) SkNinePatchGlue::drawI }, - { "nativeGetTransparentRegion", "(JJLandroid/graphics/Rect;)J", - (void*) SkNinePatchGlue::getTransparentRegion } + { "isNinePatchChunk", "([B)Z", (void*) SkNinePatchGlue::isNinePatchChunk }, + { "validateNinePatchChunk", "([B)J", + (void*) SkNinePatchGlue::validateNinePatchChunk }, + { "nativeFinalize", "(J)V", (void*) SkNinePatchGlue::finalize }, + { "nativeDraw", "(JLandroid/graphics/RectF;Landroid/graphics/Bitmap;JJII)V", + (void*) SkNinePatchGlue::drawF }, + { "nativeDraw", "(JLandroid/graphics/Rect;Landroid/graphics/Bitmap;JJII)V", + (void*) SkNinePatchGlue::drawI }, + { "nativeGetTransparentRegion", "(Landroid/graphics/Bitmap;JLandroid/graphics/Rect;)J", + (void*) SkNinePatchGlue::getTransparentRegion } }; int register_android_graphics_NinePatch(JNIEnv* env) { diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 50a106917ee8..9b5fb3a60119 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -318,11 +318,12 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle, indices, indexCount, *paint); } -static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong bitmapHandle, +static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jobject jbitmap, jfloat left, jfloat top, jlong paintHandle, jint canvasDensity, jint screenDensity, jint bitmapDensity) { Canvas* canvas = get_canvas(canvasHandle); - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) { @@ -332,9 +333,9 @@ static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong b filteredPaint = *paint; } filteredPaint.setFilterQuality(kLow_SkFilterQuality); - canvas->drawBitmap(*bitmap, left, top, &filteredPaint); + canvas->drawBitmap(bitmap, left, top, &filteredPaint); } else { - canvas->drawBitmap(*bitmap, left, top, paint); + canvas->drawBitmap(bitmap, left, top, paint); } } else { canvas->save(SkCanvas::kMatrixClip_SaveFlag); @@ -348,37 +349,39 @@ static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong b } filteredPaint.setFilterQuality(kLow_SkFilterQuality); - canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint); + canvas->drawBitmap(bitmap, 0, 0, &filteredPaint); canvas->restore(); } } -static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, +static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap, jlong matrixHandle, jlong paintHandle) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); - get_canvas(canvasHandle)->drawBitmap(*bitmap, *matrix, paint); + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); + get_canvas(canvasHandle)->drawBitmap(bitmap, *matrix, paint); } -static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, +static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, jlong paintHandle, jint screenDensity, jint bitmapDensity) { Canvas* canvas = get_canvas(canvasHandle); - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); if (screenDensity != 0 && screenDensity != bitmapDensity) { Paint filteredPaint; if (paint) { filteredPaint = *paint; } filteredPaint.setFilterQuality(kLow_SkFilterQuality); - canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom, + canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, &filteredPaint); } else { - canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom, + canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint); } } @@ -406,16 +409,17 @@ static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, get_canvas(canvasHandle)->drawBitmap(bitmap, x, y, paint); } -static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, +static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap, jint meshWidth, jint meshHeight, jfloatArray jverts, jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) { const int ptCount = (meshWidth + 1) * (meshHeight + 1); AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1)); AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount); - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); - get_canvas(canvasHandle)->drawBitmapMesh(*bitmap, meshWidth, meshHeight, + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); + get_canvas(canvasHandle)->drawBitmapMesh(bitmap, meshWidth, meshHeight, vertA.ptr(), colorA.ptr(), paint); } @@ -700,11 +704,11 @@ static JNINativeMethod gMethods[] = { {"native_drawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc}, {"native_drawPath","(JJJ)V", (void*) CanvasJNI::drawPath}, {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices}, - {"native_drawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap}, - {"nativeDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix}, - {"native_drawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect}, + {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap}, + {"nativeDrawBitmapMatrix", "(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix}, + {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect}, {"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray}, - {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh}, + {"nativeDrawBitmapMesh", "(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh}, {"native_drawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars}, {"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString}, {"native_drawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars}, diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 1f688e107fd1..610c7ed83335 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -957,6 +957,13 @@ static jint android_media_AudioTrack_attachAuxEffect(JNIEnv *env, jobject thiz, return nativeToJavaStatus( lpTrack->attachAuxEffect(effectId) ); } +static jboolean android_media_AudioTrack_setOutputDevice( + JNIEnv *env, jobject thiz, jint device_id) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + return lpTrack->setOutputDevice(device_id) == NO_ERROR; +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -1002,6 +1009,8 @@ static JNINativeMethod gMethods[] = { "(F)I", (void *)android_media_AudioTrack_setAuxEffectSendLevel}, {"native_attachAuxEffect", "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, + {"native_setOutputDevice", "(I)Z", + (void *)android_media_AudioTrack_setOutputDevice}, }; diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index 87c58d62dee8..5e73ef250435 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -132,6 +132,13 @@ static void nSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleN } } +static void nSetIndents(JNIEnv* env, jclass, jlong nativePtr, jintArray indents) { + ScopedIntArrayRO indentArr(env, indents); + std::vector<float> indentVec(indentArr.get(), indentArr.get() + indentArr.size()); + LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr); + b->setIndents(indentVec); +} + // Basically similar to Paint.getTextRunAdvances but with C++ interface static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) { @@ -171,6 +178,7 @@ static JNINativeMethod gMethods[] = { {"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator}, {"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale}, {"nSetupParagraph", "(J[CIFIF[III)V", (void*) nSetupParagraph}, + {"nSetIndents", "(J[I)V", (void*) nSetIndents}, {"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun}, {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun}, {"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun}, diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp index f2e6c4b5a8fd..f42c89c12aa8 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -133,10 +133,10 @@ static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobj // ---------------------------------------------------------------------------- static void android_view_DisplayListCanvas_drawPatch(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong bitmapPtr, jlong patchPtr, + jlong rendererPtr, jobject jbitmap, jlong patchPtr, float left, float top, float right, float bottom, jlong paintPtr) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr); - + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap); DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr); Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(patchPtr); Paint* paint = reinterpret_cast<Paint*>(paintPtr); @@ -276,7 +276,7 @@ static JNINativeMethod gMethods[] = { { "nCallDrawGLFunction", "(JJ)V", (void*) android_view_DisplayListCanvas_callDrawGLFunction }, - { "nDrawPatch", "(JJJFFFFJ)V", (void*) android_view_DisplayListCanvas_drawPatch }, + { "nDrawPatch", "(JLandroid/graphics/Bitmap;JFFFFJ)V", (void*) android_view_DisplayListCanvas_drawPatch }, { "nDrawRects", "(JJJ)V", (void*) android_view_DisplayListCanvas_drawRegionAsRects }, { "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 13877fb549d0..ec57eba5d4da 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -517,13 +517,6 @@ android:description="@string/permdesc_accessCoarseLocation" android:protectionLevel="dangerous" /> - <!-- Allows an application to create mock location providers for testing. --> - <permission android:name="android.permission.ACCESS_MOCK_LOCATION" - android:permissionGroup="android.permission-group.LOCATION" - android:label="@string/permlab_accessMockLocation" - android:description="@string/permdesc_accessMockLocation" - android:protectionLevel="dangerous" /> - <!-- ====================================================================== --> <!-- Permissions for accessing the device telephony --> <!-- ====================================================================== --> @@ -734,12 +727,6 @@ <!-- =============================================================== --> <eat-comment /> - - <!-- =============================================================== --> - <!-- Permissions for accessing the user dictionary --> - <!-- =============================================================== --> - <eat-comment /> - <!-- Used for permissions that provide access to the user calendar to create / view events.--> <permission-group android:name="android.permission-group.USER_DICTIONARY" @@ -848,6 +835,12 @@ android:protectionLevel="signature|system" /> <uses-permission android:name="android.permission.LOCATION_HARDWARE"/> + <!-- Allows an application to create mock location providers for testing. --> + <permission android:name="android.permission.ACCESS_MOCK_LOCATION" + android:label="@string/permlab_accessMockLocation" + android:description="@string/permdesc_accessMockLocation" + android:protectionLevel="normal" /> + <!-- ======================================= --> <!-- Permissions for accessing networks --> <!-- ======================================= --> @@ -2408,6 +2401,15 @@ android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="signature" /> + <!-- The system process that pulls carrier configuration from carrier apps will + have this permission. Carrier apps that provide + {@link android.service.carrier.CarrierConfigService} should require this + permission for clients binding to their service. --> + <permission android:name="android.permission.BIND_CARRIER_CONFIG_SERVICE" + android:label="@string/permlab_bindCarrierConfigService" + android:description="@string/permdesc_bindCarrierConfigService" + android:protectionLevel="signature|system" /> + <!-- The system process is explicitly the only one allowed to launch the confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> diff --git a/core/res/res/color/ratingbar_background_material.xml b/core/res/res/color/ratingbar_background_material.xml index e6f7488aa6fa..5af6de1bc0fc 100644 --- a/core/res/res/color/ratingbar_background_material.xml +++ b/core/res/res/color/ratingbar_background_material.xml @@ -20,6 +20,10 @@ android:color="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" /> <item + android:state_focused="true" + android:color="?attr/colorControlActivated" + android:alpha="?attr/disabledAlpha" /> + <item android:color="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> </selector> diff --git a/core/res/res/drawable-hdpi/ic_audio_bt_alpha.png b/core/res/res/drawable-hdpi/ic_audio_bt_alpha.png Binary files differdeleted file mode 100644 index 597c384cb7af..000000000000 --- a/core/res/res/drawable-hdpi/ic_audio_bt_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_audio_bt_mute_alpha.png b/core/res/res/drawable-hdpi/ic_audio_bt_mute_alpha.png Binary files differdeleted file mode 100644 index 298db927e52c..000000000000 --- a/core/res/res/drawable-hdpi/ic_audio_bt_mute_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_audio_phone_am_alpha.png b/core/res/res/drawable-hdpi/ic_audio_phone_am_alpha.png Binary files differdeleted file mode 100644 index 8a7d67ab5082..000000000000 --- a/core/res/res/drawable-hdpi/ic_audio_phone_am_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_audio_bt_alpha.png b/core/res/res/drawable-mdpi/ic_audio_bt_alpha.png Binary files differdeleted file mode 100644 index 282c643ddcf9..000000000000 --- a/core/res/res/drawable-mdpi/ic_audio_bt_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_audio_bt_mute_alpha.png b/core/res/res/drawable-mdpi/ic_audio_bt_mute_alpha.png Binary files differdeleted file mode 100644 index f734c1c630bb..000000000000 --- a/core/res/res/drawable-mdpi/ic_audio_bt_mute_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_audio_phone_am_alpha.png b/core/res/res/drawable-mdpi/ic_audio_phone_am_alpha.png Binary files differdeleted file mode 100644 index beda721fdc3c..000000000000 --- a/core/res/res/drawable-mdpi/ic_audio_phone_am_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_audio_bt_alpha.png b/core/res/res/drawable-xhdpi/ic_audio_bt_alpha.png Binary files differdeleted file mode 100644 index b8aa083ac178..000000000000 --- a/core/res/res/drawable-xhdpi/ic_audio_bt_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_audio_bt_mute_alpha.png b/core/res/res/drawable-xhdpi/ic_audio_bt_mute_alpha.png Binary files differdeleted file mode 100644 index 93a248178a35..000000000000 --- a/core/res/res/drawable-xhdpi/ic_audio_bt_mute_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_audio_phone_am_alpha.png b/core/res/res/drawable-xhdpi/ic_audio_phone_am_alpha.png Binary files differdeleted file mode 100644 index 2a04619b4faa..000000000000 --- a/core/res/res/drawable-xhdpi/ic_audio_phone_am_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/ic_audio_bt_alpha.png b/core/res/res/drawable-xxhdpi/ic_audio_bt_alpha.png Binary files differdeleted file mode 100755 index 140edac614b5..000000000000 --- a/core/res/res/drawable-xxhdpi/ic_audio_bt_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/ic_audio_bt_mute_alpha.png b/core/res/res/drawable-xxhdpi/ic_audio_bt_mute_alpha.png Binary files differdeleted file mode 100644 index 97829b456fda..000000000000 --- a/core/res/res/drawable-xxhdpi/ic_audio_bt_mute_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/ic_audio_phone_am_alpha.png b/core/res/res/drawable-xxhdpi/ic_audio_phone_am_alpha.png Binary files differdeleted file mode 100644 index 1fd54a10eaa8..000000000000 --- a/core/res/res/drawable-xxhdpi/ic_audio_phone_am_alpha.png +++ /dev/null diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml new file mode 100644 index 000000000000..81cbe393bf80 --- /dev/null +++ b/core/res/res/drawable/btn_colored_material.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="@dimen/button_inset_horizontal_material" + android:insetTop="@dimen/button_inset_vertical_material" + android:insetRight="@dimen/button_inset_horizontal_material" + android:insetBottom="@dimen/button_inset_vertical_material"> + <ripple android:color="?attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle" + android:tint="@color/btn_colored_material"> + <corners android:radius="@dimen/control_corner_material" /> + <solid android:color="@color/white" /> + <padding android:left="@dimen/button_padding_horizontal_material" + android:top="@dimen/button_padding_vertical_material" + android:right="@dimen/button_padding_horizontal_material" + android:bottom="@dimen/button_padding_vertical_material" /> + </shape> + </item> + </ripple> +</inset> diff --git a/core/res/res/drawable/ic_audio_bt.xml b/core/res/res/drawable/ic_audio_bt.xml deleted file mode 100644 index 4f5af3d09588..000000000000 --- a/core/res/res/drawable/ic_audio_bt.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?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. ---> - -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_bt_alpha" - android:tint="?attr/colorControlNormal" /> diff --git a/core/res/res/drawable/ic_audio_bt_mute.xml b/core/res/res/drawable/ic_audio_bt_mute.xml deleted file mode 100644 index d2004c0efdd3..000000000000 --- a/core/res/res/drawable/ic_audio_bt_mute.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?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. ---> - -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_bt_mute_alpha" - android:tint="?attr/colorControlNormal" /> diff --git a/packages/SystemUI/res/layout/volume_panel_dialog.xml b/core/res/res/drawable/ic_audio_media.xml index 700102ff338e..a453b3db6434 100644 --- a/packages/SystemUI/res/layout/volume_panel_dialog.xml +++ b/core/res/res/drawable/ic_audio_media.xml @@ -13,15 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/volume_panel_z" - android:layout_marginLeft="@dimen/notification_side_padding" - android:layout_marginRight="@dimen/notification_side_padding" - android:background="@drawable/qs_background_primary" - android:translationZ="@dimen/volume_panel_z" > +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="32.0dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="32.0dp" > - <include layout="@layout/volume_panel" /> + <path + android:fillColor="?android:attr/colorControlNormal" + android:pathData="M12.0,3.0l0.0,9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12.0 6.0,14.01 6.0,16.5S8.01,21.0 10.5,21.0c2.31,0.0 4.2,-1.75 4.45,-4.0L15.0,17.0L15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0z" /> + +</vector> -</FrameLayout>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_audio_media_mute.xml b/core/res/res/drawable/ic_audio_media_mute.xml new file mode 100644 index 000000000000..2e7f6dc7b8ba --- /dev/null +++ b/core/res/res/drawable/ic_audio_media_mute.xml @@ -0,0 +1,30 @@ +<!-- + Copyright (C) 2015 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" + android:height="32.0dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="32.0dp" > + + <path + android:fillColor="?android:attr/colorControlNormal" + android:pathData="M15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C15.0,8.8 15.0,6.0 15.0,6.0z" /> + <path + android:fillColor="?android:attr/colorControlNormal" + android:pathData="M4.8,3.9L3.5,5.1l6.9,6.9C8.0,12.1 6.0,14.0 6.0,16.5C6.0,19.0 8.0,21.0 10.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3c0.0,0.0 0.0,-0.1 0.0,-0.1l4.0,4.0l1.3,-1.3L4.8,3.9z" /> + +</vector> + diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml index f24791973182..e1af94c29d48 100644 --- a/core/res/res/layout/floating_popup_container.xml +++ b/core/res/res/layout/floating_popup_container.xml @@ -19,7 +19,9 @@ android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="@dimen/floating_toolbar_height" + android:padding="0dp" + android:layout_margin="0dp" android:elevation="2dp" android:focusable="true" android:focusableInTouchMode="true" - android:background="@android:color/background_light" /> + android:background="@color/floating_toolbar_background_color"/> diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml index 9fa13bdeb94e..70227fa36911 100644 --- a/core/res/res/layout/floating_popup_menu_button.xml +++ b/core/res/res/layout/floating_popup_menu_button.xml @@ -19,13 +19,15 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:minWidth="@dimen/floating_toolbar_menu_button_side_padding" - android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding" - android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding" + android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding" + android:paddingEnd="@dimen/floating_toolbar_menu_button_side_padding" android:paddingTop="0dp" android:paddingBottom="0dp" + android:layout_margin="0dp" android:singleLine="true" android:ellipsize="end" android:fontFamily="sans-serif" android:textSize="@dimen/floating_toolbar_text_size" android:textAllCaps="true" - android:background="?attr/selectableItemBackground" />
\ No newline at end of file + android:textColor="@color/floating_toolbar_text_color" + android:background="?attr/selectableItemBackground" /> diff --git a/core/res/res/layout/floating_popup_overflow_list_item b/core/res/res/layout/floating_popup_overflow_list_item index 9294f3b1ab4b..c0db1bd64755 100644 --- a/core/res/res/layout/floating_popup_overflow_list_item +++ b/core/res/res/layout/floating_popup_overflow_list_item @@ -22,12 +22,14 @@ android:gravity="center_vertical" android:minWidth="@dimen/floating_toolbar_menu_button_side_padding" android:minHeight="@dimen/floating_toolbar_height" - android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding" - android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding" + android:paddingStart="@dimen/floating_toolbar_overflow_side_padding" + android:paddingEnd="@dimen/floating_toolbar_overflow_side_padding" android:paddingTop="0dp" android:paddingBottom="0dp" + android:layout_margin="0dp" android:singleLine="true" android:ellipsize="end" android:fontFamily="sans-serif" android:textSize="@dimen/floating_toolbar_text_size" + android:textColor="@color/floating_toolbar_text_color" android:textAllCaps="true" /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index a623d0cb5f16..674c695a6776 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4322,6 +4322,10 @@ <!-- Line breaking stratgegy balances line lengths. --> <enum name="balanced" value="2" /> </attr> + <!-- Array of indents, one dimension value per line, left side. --> + <attr name="leftIndents" format="reference" /> + <!-- Array of indents, one dimension value per line, right side. --> + <attr name="rightIndents" format="reference" /> </declare-styleable> <declare-styleable name="TextViewAppearance"> <!-- Base text color, typeface, size, and style. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 5ffe57e439af..12d7e6009ebf 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -501,9 +501,16 @@ <attr name="excludeFromRecents" format="boolean" /> <!-- Specify that an Activity should be shown over the lock screen and, - in a multiuser environment, across all users' windows --> + in a multiuser environment, across all users' windows. + @deprecated use {@link android.R.attr#showForAllUsers} instead. --> <attr name="showOnLockScreen" format="boolean" /> + <!-- Specify that an Activity should be shown even if the current/foreground user + is different from the user of the Activity. This will also force the + <code>android.view.LayoutParams.FLAG_SHOW_WHEN_LOCKED</code> flag + to be set for all windows of this activity --> + <attr name="showForAllUsers" format="boolean" /> + <!-- Specify the authorities under which this content provider can be found. Multiple authorities may be supplied by separating them with a semicolon. Authority names should use a Java-style naming @@ -907,10 +914,11 @@ what gets persisted. --> <attr name="persistableMode"> <!-- The default. If this activity forms the root of a task then that task will be - persisted across reboots but only the launching intent will be used. All - activities above this activity in the task will not be persisted. In addition - this activity will not be passed a PersistableBundle into which it could have - stored its state. --> + persisted across reboots but only the launching intent will be used. If the task + relinquishes its identity then the intent used is that of the topmost inherited + identity. All activities above this activity in the task will not be persisted. + In addition this activity will not be passed a PersistableBundle into which it + could have stored its state. --> <enum name="persistRootOnly" value="0" /> <!-- If this activity forms the root of a task then that task will not be persisted across reboots --> @@ -1038,6 +1046,47 @@ activity. --> <attr name="resizeableActivity" format="boolean" /> + <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode. + While in lockTask mode the system will not launch non-permitted tasks until + lockTask mode is disabled. + <p>While in lockTask mode with multiple permitted tasks running, each launched task is + permitted to finish, transitioning to the previous locked task, until there is only one + task remaining. At that point the last task running is not permitted to finish. --> + <attr name="lockTaskMode"> + <!-- This is the default value. Tasks will not launch into lockTask mode but can be + placed there by calling {@link android.app.Activity#startLockTask}. If a task with + this mode has been whitelisted using {@link + android.app.admin.DevicePolicyManager#setLockTaskPackages} then calling startLockTask + will enter lockTask mode immediately, otherwise the user will be presented with a + dialog to approve entering lockTask mode. + <p>If the system is already in lockTask mode when a new task rooted at this activity + is launched that task will or will not start depending on whether the package of this + activity has been whitelisted. + <p>Tasks rooted at this activity can only exit lockTask mode using stopLockTask(). --> + <enum name="lockTaskModeDefault" value="0"/> + <!-- Tasks will not launch into lockTask mode and cannot be placed there using + {@link android.app.Activity#startLockTask} or be pinned from the Overview screen. + If the system is already in lockTask mode when a new task rooted at this activity is + launched that task will not be started. + <p>Note: This mode is only available to system and privileged applications. + Non-privileged apps with this value will be treated as lockTaskModeDefault. + --> + <enum name="lockTaskModeNever" value="1"/> + <!-- Tasks rooted at this activity will always launch into lockTask mode. If the system is + already in lockTask mode when this task is launched then the new task will be launched + on top of the current task. Tasks launched in this mode are capable of exiting + lockTask mode using finish(), whereas tasks entering lockTask mode using + startLockTask() must use stopLockTask() to exit. + <p>Note: This mode is only available to system and privileged applications. + Non-privileged apps with this value will be treated as lockTaskModeDefault. + --> + <enum name="lockTaskModeAlways" value="2"/> + <!-- If the DevicePolicyManager (DPM) authorizes this package ({@link + android.app.admin.DevicePolicyManager#setLockTaskPackages}) then this mode is + identical to lockTaskModeAlways. If the DPM does not authorize this package then this + mode is identical to lockTaskModeDefault. --> + <enum name="lockTaskModeIfWhitelisted" value="3"/> + </attr> <!-- When set installer will extract native libraries. If set to false libraries in the apk must be stored and page-aligned. --> <attr name="extractNativeLibs" format="boolean"/> @@ -1684,7 +1733,7 @@ {@link android.app.Activity} class that is available as part of the package's application components, implementing a part of the application's user interface. - + <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter} tags can be included inside of an activity, to specify the Intents that it can handle. If none are specified, the activity can @@ -1719,6 +1768,7 @@ <attr name="alwaysRetainTaskState" /> <attr name="stateNotNeeded" /> <attr name="excludeFromRecents" /> + <!-- @deprecated use {@link android.R.attr#showForAllUsers} instead. --> <attr name="showOnLockScreen" /> <!-- Specify whether the activity is enabled or not (that is, can be instantiated by the system). It can also be specified for an application as a whole, in which case a value of "false" @@ -1746,12 +1796,14 @@ <attr name="relinquishTaskIdentity" /> <attr name="resumeWhilePausing" /> <attr name="resizeableActivity" /> + <attr name="lockTaskMode" /> + <attr name="showForAllUsers" /> </declare-styleable> - + <!-- The <code>activity-alias</code> tag declares a new name for an existing {@link #AndroidManifestActivity activity} tag. - + <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter} tags can be included inside of an activity-alias, to specify the Intents that it can handle. If none are specified, the activity can @@ -1769,7 +1821,7 @@ must be in the same manifest as the alias, and have been defined in that manifest before the alias here. This must use a Java-style naming convention to ensure the name is unique, for example - "com.mycompany.MyName". --> + "com.mycompany.MyName". --> <attr name="targetActivity" format="string" /> <attr name="label" /> <attr name="description" /> @@ -1785,7 +1837,7 @@ <attr name="exported" /> <attr name="parentActivityName" /> </declare-styleable> - + <!-- The <code>meta-data</code> tag is used to attach additional arbitrary data to an application component. The data can later be retrieved programmatically from the diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index b9825c534705..f1d22424731a 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -174,4 +174,8 @@ <color name="Pink_800">#ffad1457</color> <color name="Red_700">#ffc53929</color> <color name="Red_800">#ffb93221</color> + + <!-- Floating toolbar colors --> + <color name="floating_toolbar_text_color">#DD000000</color> + <color name="floating_toolbar_background_color">#FAFAFA</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8c78e74fe166..07f8c60e834d 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -423,6 +423,9 @@ point on the move. A value of 0 means no periodic scans will be used in the framework. --> <integer translatable="false" name="config_wifi_framework_scan_interval">300000</integer> + <!-- Integer indicating disconnect mode scan interval in milliseconds --> + <integer translatable="false" name="config_wifi_disconnected_scan_interval">15000</integer> + <!-- Integer indicating associated partial scan interval in milliseconds --> <integer translatable="false" name="config_wifi_framework_associated_scan_interval">20000</integer> @@ -472,6 +475,9 @@ <!-- Wifi driver supports batched scan --> <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool> + <!-- Wifi HAL supported PNO --> + <bool translatable="false" name="config_wifi_hal_pno_enable">false</bool> + <!-- Idle Receive current for wifi radio. 0 by default--> <integer translatable="false" name="config_wifi_idle_receive_cur_ma">1</integer> @@ -1962,8 +1968,10 @@ string that's stored in 8-bit unpacked format) characters.--> <bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool> - <!-- Package name providing WebView implementation. --> - <string name="config_webViewPackageName" translatable="false">com.android.webview</string> + <!-- List of package names (ordered by preference) providing WebView implementations. --> + <string-array name="config_webViewPackageNames" translatable="false"> + <item>com.android.webview</item> + </string-array> <!-- If EMS is not supported, framework breaks down EMS into single segment SMS and adds page info " x/y". This config is used to set which carrier doesn't diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 100b1610ae5f..bbba712c81a1 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -387,12 +387,14 @@ <!-- Floating toolbar dimensions --> <dimen name="floating_toolbar_height">48dp</dimen> - <dimen name="floating_toolbar_menu_button_side_padding">8dp</dimen> + <dimen name="floating_toolbar_menu_button_side_padding">16dp</dimen> + <dimen name="floating_toolbar_overflow_side_padding">18dp</dimen> <dimen name="floating_toolbar_text_size">14sp</dimen> <dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen> - <dimen name="floating_toolbar_default_width">250dp</dimen> - <dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen> - <dimen name="floating_toolbar_overflow_width">130dp</dimen> - <dimen name="floating_toolbar_margin">2dp</dimen> + <dimen name="floating_toolbar_default_width">264dp</dimen> + <dimen name="floating_toolbar_minimum_overflow_height">144dp</dimen> + <dimen name="floating_toolbar_horizontal_margin">16dp</dimen> + <dimen name="floating_toolbar_vertical_margin">8dp</dimen> + <dimen name="chooser_grid_padding">0dp</dimen> </resources> diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index 937e92ad1fd6..92d5aa1633ca 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -139,7 +139,7 @@ <dimen name="timepicker_selector_radius">20dp</dimen> <dimen name="timepicker_selector_stroke">2dp</dimen> <dimen name="timepicker_center_dot_radius">3dp</dimen> - <dimen name="timepicker_selector_dot_radius">3dp</dimen> + <dimen name="timepicker_selector_dot_radius">2dp</dimen> <dimen name="timepicker_text_inset_normal">22dp</dimen> <dimen name="timepicker_text_inset_inner">58dp</dimen> <dimen name="timepicker_text_size_normal">16sp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index c2f2c6d86b79..208afb7d96c9 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2641,6 +2641,7 @@ <public type="style" name="Theme.Material.DayNight.Panel" /> <public type="style" name="Theme.Material.Light.LightStatusBar" /> <public type="style" name="ThemeOverlay.Material.Dialog" /> + <public type="style" name="TextAppearance.Material.Widget.Button.Inverse" /> <public type="id" name="pasteAsPlainText" /> <public type="id" name="undo" /> @@ -2663,4 +2664,11 @@ <!-- Animation --> <public type="attr" name="durationScaleHint" /> + + <public type="attr" name="lockTaskMode" /> + + <public type="attr" name="leftIndents" /> + <public type="attr" name="rightIndents" /> + + <public type="attr" name="showForAllUsers" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3d1fd7ce720d..59366cc934dc 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1323,6 +1323,108 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_use_sip">Allows the app to make and receive SIP calls.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_register_sim_subscription">register new telecom SIM connections</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_register_sim_subscription">Allows the app to register new telecom SIM connections.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_register_call_provider">register new telecom connections</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_register_call_provider">Allows the app to register new telecom connections.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_connection_manager">manage telecom connections</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_connection_manager">Allows the app to manage telecom connections.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bind_incall_service">interact with in-call screen</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bind_incall_service">Allows the app to control when and how the user sees the in-call screen.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bind_connection_service">interact with telephony services</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bind_connection_service">Allows the app to interact with telephony services to make/receive calls.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_control_incall_experience">provide an in-call user experience</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_control_incall_experience">Allows the app to provide an in-call user experience.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_readNetworkUsageHistory">read historical network usage</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_readNetworkUsageHistory">Allows the app to read historical network usage for specific networks and apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_manageNetworkPolicy">manage network policy</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_manageNetworkPolicy">Allows the app to manage network policies and define app-specific rules.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_modifyNetworkAccounting">modify network usage accounting</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_modifyNetworkAccounting">Allows the app to modify how network usage is accounted against apps. Not for use by normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_accessNotifications">access notifications</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessNotifications">Allows the app to retrieve, examine, and clear notifications, including those posted by other apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindNotificationListenerService">bind to a notification listener service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindConditionProviderService">bind to a condition provider service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindDreamService">bind to a dream service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindDreamService">Allows the holder to bind to the top-level interface of a dream service. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_invokeCarrierSetup">invoke the carrier-provided configuration app</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_invokeCarrierSetup">Allows the holder to invoke the carrier-provided configuration app. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_accessNetworkConditions">listen for observations on network conditions</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessNetworkConditions">Allows an application to listen for observations on network conditions. Should never be needed for normal apps.</string> + + <string name="permlab_setInputCalibration">change input device calibration</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_setInputCalibration">Allows the app to modify the calibration parameters of the touch screen. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_accessDrmCertificates">access DRM certificates</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</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> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_removeDrmCertificates">remove DRM certificates</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_removeDrmCertificates">Allows an application to remove DRM certficates. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindCarrierMessagingService">bind to a carrier messaging service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindCarrierMessagingService">Allows the holder to bind to the top-level interface of a carrier messaging service. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindCarrierConfigService">bind to a carrier config service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindCarrierConfigService">Allows the holder to bind to a carrier config service. Should never be needed for normal apps.</string> + <!-- Policy administration --> <!-- Title of policy access to limiting the user's password choices --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 88cac7271190..b874f6379e16 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -289,6 +289,10 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Material.Widget"/> <style name="TextAppearance.Material.Widget.Button" parent="TextAppearance.Material.Button" /> + <style name="TextAppearance.Material.Widget.Button.Inverse"> + <item name="textColor">?attr/textColorPrimaryInverse</item> + </style> + <style name="TextAppearance.Material.Widget.EditText"> <item name="textColor">?attr/textColorPrimaryInverse</item> <item name="textColorHint">?attr/textColorHintInverse</item> @@ -461,7 +465,8 @@ please see styles_device_defaults.xml. <!-- Colored bordered ink button --> <style name="Widget.Material.Button.Colored"> - <item name="backgroundTint">@color/btn_colored_material</item> + <item name="background">@drawable/btn_colored_material</item> + <item name="textAppearance">@style/TextAppearance.Material.Widget.Button.Inverse</item> </style> <!-- Small bordered ink button --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e5b1cb5bc169..180b4158f5f4 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -378,7 +378,9 @@ <java-symbol type="integer" name="config_shortPressOnSleepBehavior" /> <java-symbol type="integer" name="config_wifi_framework_scan_interval" /> <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" /> + <java-symbol type="integer" name="config_wifi_disconnected_scan_interval" /> <java-symbol type="integer" name="config_wifi_scan_interval_p2p_connected" /> + <java-symbol type="bool" name="config_wifi_hal_pno_enable" /> <java-symbol type="integer" name="db_connection_pool_size" /> <java-symbol type="integer" name="db_journal_size_limit" /> <java-symbol type="integer" name="db_wal_autocheckpoint" /> @@ -1163,11 +1165,10 @@ <java-symbol type="drawable" name="expander_open_holo_dark" /> <java-symbol type="drawable" name="ic_audio_alarm" /> <java-symbol type="drawable" name="ic_audio_alarm_mute" /> - <java-symbol type="drawable" name="ic_audio_bt" /> - <java-symbol type="drawable" name="ic_audio_bt_mute" /> + <java-symbol type="drawable" name="ic_audio_media" /> + <java-symbol type="drawable" name="ic_audio_media_mute" /> <java-symbol type="drawable" name="ic_audio_notification" /> <java-symbol type="drawable" name="ic_audio_notification_mute" /> - <java-symbol type="drawable" name="ic_audio_phone" /> <java-symbol type="drawable" name="ic_audio_ring_notif" /> <java-symbol type="drawable" name="ic_audio_ring_notif_mute" /> <java-symbol type="drawable" name="ic_audio_ring_notif_vibrate" /> @@ -1995,7 +1996,7 @@ <java-symbol type="attr" name="actionModeWebSearchDrawable" /> <java-symbol type="string" name="websearch" /> <java-symbol type="drawable" name="ic_media_video_poster" /> - <java-symbol type="string" name="config_webViewPackageName" /> + <java-symbol type="array" name="config_webViewPackageNames" /> <!-- From SubtitleView --> <java-symbol type="dimen" name="subtitle_corner_radius" /> @@ -2223,12 +2224,13 @@ <java-symbol type="layout" name="floating_popup_overflow_list_item" /> <java-symbol type="dimen" name="floating_toolbar_height" /> <java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" /> + <java-symbol type="dimen" name="floating_toolbar_overflow_side_padding" /> <java-symbol type="dimen" name="floating_toolbar_text_size" /> <java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" /> <java-symbol type="dimen" name="floating_toolbar_default_width" /> <java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" /> - <java-symbol type="dimen" name="floating_toolbar_overflow_width" /> - <java-symbol type="dimen" name="floating_toolbar_margin" /> + <java-symbol type="dimen" name="floating_toolbar_horizontal_margin" /> + <java-symbol type="dimen" name="floating_toolbar_vertical_margin" /> <java-symbol type="drawable" name="ic_chevron_left" /> <java-symbol type="drawable" name="ic_chevron_right" /> diff --git a/docs/html/guide/topics/manifest/application-element.jd b/docs/html/guide/topics/manifest/application-element.jd index b5af9c38e992..d6ad656f2a51 100644 --- a/docs/html/guide/topics/manifest/application-element.jd +++ b/docs/html/guide/topics/manifest/application-element.jd @@ -32,6 +32,7 @@ page.title=<application> android:<a href="#testOnly">testOnly</a>=["true" | "false"] android:<a href="#theme">theme</a>="<i>resource or theme</i>" android:<a href="#uioptions">uiOptions</a>=["none" | "splitActionBarWhenNarrow"] + android:<a href="#usesCleartextTraffic">usesCleartextTraffic</a>=["true" | "false"] android:<a href="#vmSafeMode">vmSafeMode</a>=["true" | "false"] > . . . </application></pre></dd> @@ -446,6 +447,32 @@ href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> developer guide.< <p>This attribute was added in API level 14.</p> </dd> +<dt><a name="usesCleartextTraffic"></a>{@code android:usesCleartextTraffic}</dt> +<dd>Indicates whether the app intends to use cleartext network traffic, such as cleartext HTTP. +The default value is {@code "true"}. + +<p>When the attribute is set to {@code "false"}, platform components (for example, HTTP and FTP +stacks, {@link android.webkit.WebView}, {@link android.app.DownloadManager}, +{@link android.media.MediaPlayer}) will refuse the app's requests to use cleartext traffic. +Third-party libraries are strongly encouraged to honor this setting as well. The key reason for +avoiding cleartext traffic is the lack of confidentiality, authenticity, and protections against +tampering: a network attacker can eavesdrop on transmitted data and also modify it without being +detected. + +<p>This flag is honored on a best effort basis because it's impossible to prevent all cleartext +traffic from Android applications given the level of access provided to them. For example, there's +no expectation that the {@link java.net.Socket} API will honor this flag because it cannot +determine whether its traffic is in cleartext. However, most network traffic from applications is +handled by higher-level network stacks/components which can honor this flag by either reading it +from {@link android.content.pm.ApplicationInfo#flags ApplicationInfo.flags} or +{@link android.security.NetworkSecurityPolicy#isCleartextTrafficPermitted() NetworkSecurityPolicy.isCleartextTrafficPermitted()}. + +<p>During app development, StrictMode can be used to identify any cleartext traffic from the app: see +{@link android.os.StrictMode.VmPolicy.Builder#detectCleartextNetwork() StrictMode.VmPolicy.Builder.detectCleartextNetwork()}. + +<p>This attribute was added in API level 23.</p> +</dd> + <dt><a name="vmSafeMode"></a>{@code android:vmSafeMode}</dt> <dd>Indicates whether the app would like the virtual machine (VM) to operate in safe mode. The default value is {@code "false"}. diff --git a/docs/html/tools/help/hprof-conv.jd b/docs/html/tools/help/hprof-conv.jd index f96def205fb1..982f3379e468 100644 --- a/docs/html/tools/help/hprof-conv.jd +++ b/docs/html/tools/help/hprof-conv.jd @@ -8,9 +8,13 @@ The <code>hprof-conv</code> tool converts the HPROF file that is generated by the Android SDK tools to a standard format so you can view the file in a profiling tool of your choice. </p> -<pre> hprof-conv <infile> <outfile></pre> +<pre> hprof-conv [-z] <infile> <outfile></pre> <p> You can use "-" for <code><infile></code> or <code><outfile></code> to specify stdin or stdout. </p> + +<p> +You can use "-z" to filter out zygote allocations shared by all applications. +</p> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index e2f7799786a0..be5c52b23fb5 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1568,9 +1568,13 @@ public final class Bitmap implements Parcelable { nativePrepareToDraw(mSkBitmapPtr); } - /** @hide */ - public final long getSkBitmap() { - return mSkBitmapPtr; + /** + * Refs the underlying SkPixelRef and returns a pointer to it. + * + * @hide + * */ + public final long refSkPixelRef() { + return nativeRefPixelRef(mSkBitmapPtr); } private static class BitmapFinalizer { @@ -1661,4 +1665,5 @@ public final class Bitmap implements Parcelable { private static native boolean nativeHasMipMap(long nativeBitmap); private static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap); private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1); + private static native long nativeRefPixelRef(long nativeBitmap); } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 2acb8baaaa06..1c568844f5a5 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1335,7 +1335,7 @@ public class Canvas { */ public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) { throwIfCannotDraw(bitmap); - native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top, + native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity); } @@ -1381,7 +1381,7 @@ public class Canvas { bottom = src.bottom; } - native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top, right, bottom, + native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, bitmap.mDensity); } @@ -1428,7 +1428,7 @@ public class Canvas { bottom = src.bottom; } - native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top, right, bottom, + native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity, bitmap.mDensity); } @@ -1509,7 +1509,7 @@ public class Canvas { * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) { - nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getSkBitmap(), matrix.ni(), + nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(), paint != null ? paint.getNativeInstance() : 0); } @@ -1564,7 +1564,7 @@ public class Canvas { // no mul by 2, since we need only 1 color per vertex checkRange(colors.length, colorOffset, count); } - nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getSkBitmap(), meshWidth, meshHeight, + nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint != null ? paint.getNativeInstance() : 0); } @@ -2052,13 +2052,13 @@ public class Canvas { private static native void native_drawPath(long nativeCanvas, long nativePath, long nativePaint); - private native void native_drawBitmap(long nativeCanvas, long nativeBitmap, + private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top, long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity); - private native void native_drawBitmap(long nativeCanvas, long nativeBitmap, + private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity); @@ -2068,11 +2068,11 @@ public class Canvas { boolean hasAlpha, long nativePaintOrZero); private static native void nativeDrawBitmapMatrix(long nativeCanvas, - long nativeBitmap, + Bitmap bitmap, long nativeMatrix, long nativePaint); private static native void nativeDrawBitmapMesh(long nativeCanvas, - long nativeBitmap, + Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 9c4299aa5d70..21a212a0ec42 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -98,7 +98,7 @@ public class NinePatch { public NinePatch(Bitmap bitmap, byte[] chunk, String srcName) { mBitmap = bitmap; mSrcName = srcName; - mNativeChunk = validateNinePatchChunk(mBitmap.getSkBitmap(), chunk); + mNativeChunk = validateNinePatchChunk(chunk); } /** @@ -199,12 +199,12 @@ public class NinePatch { } void drawSoftware(Canvas canvas, RectF location, Paint paint) { - nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.getSkBitmap(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap, mNativeChunk, paint != null ? paint.getNativeInstance() : 0, canvas.mDensity, mBitmap.mDensity); } void drawSoftware(Canvas canvas, Rect location, Paint paint) { - nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.getSkBitmap(), mNativeChunk, + nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap, mNativeChunk, paint != null ? paint.getNativeInstance() : 0, canvas.mDensity, mBitmap.mDensity); } @@ -252,7 +252,7 @@ public class NinePatch { * that are transparent. */ public final Region getTransparentRegion(Rect bounds) { - long r = nativeGetTransparentRegion(mBitmap.getSkBitmap(), mNativeChunk, bounds); + long r = nativeGetTransparentRegion(mBitmap, mNativeChunk, bounds); return r != 0 ? new Region(r) : null; } @@ -271,11 +271,11 @@ public class NinePatch { * If validation is successful, this method returns a native Res_png_9patch* * object used by the renderers. */ - private static native long validateNinePatchChunk(long bitmap, byte[] chunk); + private static native long validateNinePatchChunk(byte[] chunk); private static native void nativeFinalize(long chunk); - private static native void nativeDraw(long canvas_instance, RectF loc, long bitmap_instance, + private static native void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance, long c, long paint_instance_or_null, int destDensity, int srcDensity); - private static native void nativeDraw(long canvas_instance, Rect loc, long bitmap_instance, + private static native void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance, long c, long paint_instance_or_null, int destDensity, int srcDensity); - private static native long nativeGetTransparentRegion(long bitmap, long chunk, Rect location); + private static native long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location); } diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 1c068be7fc8f..c259c25b0e5c 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -535,6 +535,12 @@ public class AndroidKeyStore extends KeyStoreSpi { args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeyStoreKeyProperties.UserAuthenticator.allToKeymaster( params.getUserAuthenticators())); + long secureUserId = GateKeeper.getSecureUserId(); + if (secureUserId == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to import keys requiring user authentication"); + } + args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, secureUserId); } if (params.isInvalidatedOnNewFingerprintEnrolled()) { // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java new file mode 100644 index 000000000000..c9f06e9bbc81 --- /dev/null +++ b/keystore/java/android/security/GateKeeper.java @@ -0,0 +1,30 @@ +package android.security; + +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.service.gatekeeper.IGateKeeperService; + +/** + * Convenience class for accessing the gatekeeper service. + * + * @hide + */ +public abstract class GateKeeper { + + private GateKeeper() {} + + public static IGateKeeperService getService() { + return IGateKeeperService.Stub.asInterface( + ServiceManager.getService("android.service.gatekeeper.IGateKeeperService")); + } + + public static long getSecureUserId() throws IllegalStateException { + try { + return GateKeeper.getService().getSecureUserId(UserHandle.myUserId()); + } catch (RemoteException e) { + throw new IllegalStateException( + "Failed to obtain secure user ID from gatekeeper", e); + } + } +} diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java index 7bc637823c93..37e00b28cd83 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -547,18 +547,12 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if (mIvRequired) { // IV is needed if ((mIv == null) && (mEncrypting)) { - // TODO: Switch to keymaster-generated IV code below once keymaster supports - // that. - // IV is needed but was not provided by the caller -- generate an IV. - mIv = new byte[mBlockSizeBytes]; - SecureRandom rng = (mRng != null) ? mRng : new SecureRandom(); - rng.nextBytes(mIv); -// // IV was not provided by the caller and thus will be generated by keymaster. -// // Mix in some additional entropy from the provided SecureRandom. -// if (mRng != null) { -// mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; -// mRng.nextBytes(mAdditionalEntropyForBegin); -// } + // IV was not provided by the caller and thus will be generated by keymaster. + // Mix in some additional entropy from the provided SecureRandom. + if (mRng != null) { + mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; + mRng.nextBytes(mAdditionalEntropyForBegin); + } } } } diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 72c485ad56d9..d1abe12d6353 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -167,6 +167,12 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeyStoreKeyProperties.UserAuthenticator.allToKeymaster( spec.getUserAuthenticators())); + long secureUserId = GateKeeper.getSecureUserId(); + if (secureUserId == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to generate keys requiring user authentication"); + } + args.addLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, secureUserId); } if (spec.isInvalidatedOnNewFingerprintEnrolled()) { // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java index b1f330f355ef..206103f44908 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/KeyStoreKeyProperties.java @@ -217,7 +217,7 @@ public abstract class KeyStoreKeyProperties { } @Retention(RetentionPolicy.SOURCE) - @IntDef({Origin.GENERATED, Origin.IMPORTED}) + @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) public @interface OriginEnum {} /** @@ -233,14 +233,22 @@ public abstract class KeyStoreKeyProperties { public static final int IMPORTED = 1 << 1; /** + * Origin of the key is unknown. This can occur only for keys backed by an old TEE + * implementation which does not record origin information. + */ + public static final int UNKNOWN = 1 << 2; + + /** * @hide */ public static @OriginEnum int fromKeymaster(int origin) { switch (origin) { - case KeymasterDefs.KM_ORIGIN_HARDWARE: + case KeymasterDefs.KM_ORIGIN_GENERATED: return GENERATED; case KeymasterDefs.KM_ORIGIN_IMPORTED: return IMPORTED; + case KeymasterDefs.KM_ORIGIN_UNKNOWN: + return UNKNOWN; default: throw new IllegalArgumentException("Unknown origin: " + origin); } diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp index 4d2e3a097b55..882826ead10e 100644 --- a/libs/hwui/AssetAtlas.cpp +++ b/libs/hwui/AssetAtlas.cpp @@ -82,12 +82,12 @@ void AssetAtlas::updateTextureId() { /////////////////////////////////////////////////////////////////////////////// AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap); + ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); return index >= 0 ? mEntries.valueAt(index) : nullptr; } Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap); + ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); return index >= 0 ? mEntries.valueAt(index)->texture : nullptr; } @@ -120,7 +120,7 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { const float height = float(mTexture->height); for (int i = 0; i < count; ) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(map[i++]); + SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]); // NOTE: We're converting from 64 bit signed values to 32 bit // signed values. This is guaranteed to be safe because the "x" // and "y" coordinate values are guaranteed to be representable @@ -131,21 +131,21 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { bool rotated = map[i++] > 0; // Bitmaps should never be null, we're just extra paranoid - if (!bitmap) continue; + if (!pixelRef) continue; const UvMapper mapper( - x / width, (x + bitmap->width()) / width, - y / height, (y + bitmap->height()) / height); + x / width, (x + pixelRef->info().width()) / width, + y / height, (y + pixelRef->info().height()) / height); Texture* texture = new DelegateTexture(caches, mTexture); - texture->blend = !bitmap->isOpaque(); - texture->width = bitmap->width(); - texture->height = bitmap->height(); + texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType()); + texture->width = pixelRef->info().width(); + texture->height = pixelRef->info().height(); - Entry* entry = new Entry(bitmap, x, y, rotated, texture, mapper, *this); + Entry* entry = new Entry(pixelRef, x, y, rotated, texture, mapper, *this); texture->uvMapper = &entry->uvMapper; - mEntries.add(entry->bitmap, entry); + mEntries.add(entry->pixelRef, entry); } } diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h index 1772effb911b..17c5281cb3de 100644 --- a/libs/hwui/AssetAtlas.h +++ b/libs/hwui/AssetAtlas.h @@ -48,24 +48,8 @@ public: * Entry representing the position and rotation of a * bitmap inside the atlas. */ - struct Entry { - /** - * The bitmap that generated this atlas entry. - */ - SkBitmap* bitmap; - - /** - * Location of the bitmap inside the atlas, in pixels. - */ - int x; - int y; - - /** - * If set, the bitmap is rotated 90 degrees (clockwise) - * inside the atlas. - */ - bool rotated; - + class Entry { + public: /* * A "virtual texture" object that represents the texture * this entry belongs to. This texture should never be @@ -80,11 +64,6 @@ public: const UvMapper uvMapper; /** - * Atlas this entry belongs to. - */ - const AssetAtlas& atlas; - - /** * Unique identifier used to merge bitmaps and 9-patches stored * in the atlas. */ @@ -93,10 +72,37 @@ public: } private: - Entry(SkBitmap* bitmap, int x, int y, bool rotated, - Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas): - bitmap(bitmap), x(x), y(y), rotated(rotated), - texture(texture), uvMapper(mapper), atlas(atlas) { + /** + * The pixel ref that generated this atlas entry. + */ + SkPixelRef* pixelRef; + + /** + * Location of the bitmap inside the atlas, in pixels. + */ + int x; + int y; + + /** + * If set, the bitmap is rotated 90 degrees (clockwise) + * inside the atlas. + */ + bool rotated; + + /** + * Atlas this entry belongs to. + */ + const AssetAtlas& atlas; + + Entry(SkPixelRef* pixelRef, int x, int y, bool rotated, + Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas) + : texture(texture) + , uvMapper(mapper) + , pixelRef(pixelRef) + , x(x) + , y(y) + , rotated(rotated) + , atlas(atlas) { } ~Entry() { @@ -178,7 +184,7 @@ private: const bool mBlendKey; const bool mOpaqueKey; - KeyedVector<const SkBitmap*, Entry*> mEntries; + KeyedVector<const SkPixelRef*, Entry*> mEntries; }; // class AssetAtlas }; // namespace uirenderer diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 8757e15006b7..4d596fee5fef 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -220,7 +220,7 @@ void DisplayListRenderer::drawLayer(DeferredLayerUpdater* layerHandle, float x, } void DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { - bitmap = refBitmap(bitmap); + bitmap = refBitmap(*bitmap); paint = refPaint(paint); addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint)); @@ -286,7 +286,7 @@ void DisplayListRenderer::drawBitmap(const SkBitmap& bitmap, float srcLeft, floa dstRight = srcRight - srcLeft; dstBottom = srcBottom - srcTop; - addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(&bitmap), + addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap), srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint)); restore(); @@ -294,7 +294,7 @@ void DisplayListRenderer::drawBitmap(const SkBitmap& bitmap, float srcLeft, floa } } - addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(&bitmap), + addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap), srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint)); } @@ -307,17 +307,17 @@ void DisplayListRenderer::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, paint = refPaint(paint); colors = refBuffer<int>(colors, vertexCount); // 1 color per vertex - addDrawOp(new (alloc()) DrawBitmapMeshOp(refBitmap(&bitmap), meshWidth, meshHeight, + addDrawOp(new (alloc()) DrawBitmapMeshOp(refBitmap(bitmap), meshWidth, meshHeight, vertices, colors, paint)); } -void DisplayListRenderer::drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch, +void DisplayListRenderer::drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch, float left, float top, float right, float bottom, const SkPaint* paint) { - bitmap = refBitmap(bitmap); + const SkBitmap* bitmapPtr = refBitmap(bitmap); patch = refPatch(patch); paint = refPaint(paint); - addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint)); + addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patch, left, top, right, bottom, paint)); } void DisplayListRenderer::drawColor(int color, SkXfermode::Mode mode) { diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index ff698f59bcb1..44cf546244b3 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -100,7 +100,7 @@ public: // Bitmap-based void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); // TODO: move drawPatch() to Canvas.h - void drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch, + void drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch, float left, float top, float right, float bottom, const SkPaint* paint); // Shapes @@ -347,7 +347,7 @@ private: return cachedRegion; } - inline const SkBitmap* refBitmap(const SkBitmap* bitmap) { + inline const SkBitmap* refBitmap(const SkBitmap& bitmap) { // Note that this assumes the bitmap is immutable. There are cases this won't handle // correctly, such as creating the bitmap from scratch, drawing with it, changing its // contents, and drawing again. The only fix would be to always copy it the first time, diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index aa722d025d6c..d06534ef9d9c 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1009,12 +1009,13 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); } + Rect modelRect = Rect(rect.getWidth(), rect.getHeight()); Glop glop; GlopBuilder(mRenderState, mCaches, &glop) .setMeshTexturedIndexedQuads(&quadVertices[0], count * 6) .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap) .setTransform(currentSnapshot()->getOrthoMatrix(), *currentTransform(), false) - .setModelViewOffsetRectSnap(0, 0, rect) + .setModelViewOffsetRectSnap(rect.left, rect.top, modelRect) .setRoundRectClipState(currentSnapshot()->roundRectClipState) .build(); DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop)); diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index d3b8d706e606..454fedc2b179 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -59,13 +59,13 @@ void ResourceCache::unlock() { mLock.unlock(); } -const SkBitmap* ResourceCache::insert(const SkBitmap* bitmapResource) { +const SkBitmap* ResourceCache::insert(const SkBitmap& bitmapResource) { Mutex::Autolock _l(mLock); BitmapKey bitmapKey(bitmapResource); ssize_t index = mBitmapCache.indexOfKey(bitmapKey); if (index == NAME_NOT_FOUND) { - SkBitmap* cachedBitmap = new SkBitmap(*bitmapResource); + SkBitmap* cachedBitmap = new SkBitmap(bitmapResource); index = mBitmapCache.add(bitmapKey, cachedBitmap); return cachedBitmap; } @@ -121,7 +121,7 @@ void ResourceCache::decrementRefcountLocked(void* resource) { } void ResourceCache::decrementRefcountLocked(const SkBitmap* bitmapResource) { - BitmapKey bitmapKey(bitmapResource); + BitmapKey bitmapKey(*bitmapResource); ssize_t index = mBitmapCache.indexOfKey(bitmapKey); LOG_ALWAYS_FATAL_IF(index == NAME_NOT_FOUND, diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index fae55d14fead..6c483faf3508 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -53,11 +53,11 @@ public: class BitmapKey { public: - BitmapKey(const SkBitmap* bitmap) + BitmapKey(const SkBitmap& bitmap) : mRefCount(1) - , mBitmapDimensions(bitmap->dimensions()) - , mPixelRefOrigin(bitmap->pixelRefOrigin()) - , mPixelRefStableID(bitmap->pixelRef()->getStableID()) { } + , mBitmapDimensions(bitmap.dimensions()) + , mPixelRefOrigin(bitmap.pixelRefOrigin()) + , mPixelRefStableID(bitmap.pixelRef()->getStableID()) { } void operator=(const BitmapKey& other); bool operator==(const BitmapKey& other) const; @@ -101,7 +101,7 @@ public: * The cache stores a copy of the provided resource or refs an existing resource * if the bitmap has previously been inserted and returns the cached copy. */ - const SkBitmap* insert(const SkBitmap* resource); + const SkBitmap* insert(const SkBitmap& resource); void incrementRefcount(const Res_png_9patch* resource); diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java index 05bcf791af77..df128c919798 100644 --- a/location/java/android/location/GpsMeasurement.java +++ b/location/java/android/location/GpsMeasurement.java @@ -80,6 +80,8 @@ public class GpsMeasurement implements Parcelable { private static final int HAS_TIME_FROM_LAST_BIT = (1<<14); private static final int HAS_DOPPLER_SHIFT = (1<<15); private static final int HAS_DOPPLER_SHIFT_UNCERTAINTY = (1<<16); + private static final int HAS_USED_IN_FIX = (1<<17); + private static final int GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE = (1<<18); /** * The indicator is not available or it is unknown. @@ -137,10 +139,17 @@ public class GpsMeasurement implements Parcelable { public static final short STATE_TOW_DECODED = (1<<3); /** + * The state of the GPS receiver contains millisecond ambiguity. + * + * @hide + */ + public static final short STATE_MSEC_AMBIGUOUS = (1<<4); + + /** * All the GPS receiver state flags. */ - private static final short STATE_ALL = - STATE_CODE_LOCK | STATE_BIT_SYNC | STATE_SUBFRAME_SYNC | STATE_TOW_DECODED; + private static final short STATE_ALL = STATE_CODE_LOCK | STATE_BIT_SYNC | STATE_SUBFRAME_SYNC + | STATE_TOW_DECODED | STATE_MSEC_AMBIGUOUS; /** * The state of the 'Accumulated Delta Range' is invalid or unknown. @@ -295,6 +304,9 @@ public class GpsMeasurement implements Parcelable { if ((mState & STATE_TOW_DECODED) == STATE_TOW_DECODED) { builder.append("TowDecoded|"); } + if ((mState & STATE_MSEC_AMBIGUOUS) == STATE_MSEC_AMBIGUOUS) { + builder.append("MsecAmbiguous"); + } int remainingStates = mState & ~STATE_ALL; if (remainingStates > 0) { builder.append("Other("); @@ -361,6 +373,15 @@ public class GpsMeasurement implements Parcelable { /** * Gets the Pseudorange rate at the timestamp in m/s. * The reported value includes {@link #getPseudorangeRateUncertaintyInMetersPerSec()}. + * + * The correction of a given Pseudorange Rate value includes corrections from receiver and + * satellite clock frequency errors. + * {@link #isPseudorangeRateCorrected()} identifies the type of value reported. + * + * A positive 'uncorrected' value indicates that the SV is moving away from the receiver. + * The sign of the 'uncorrected' Pseudorange Rate and its relation to the sign of + * {@link #getDopplerShiftInHz()} is given by the equation: + * pseudorange rate = -k * doppler shift (where k is a constant) */ public double getPseudorangeRateInMetersPerSec() { return mPseudorangeRateInMetersPerSec; @@ -374,6 +395,18 @@ public class GpsMeasurement implements Parcelable { } /** + * See {@link #getPseudorangeRateInMetersPerSec()} for more details. + * + * @return {@code true} if {@link #getPseudorangeRateInMetersPerSec()} contains a corrected + * value, {@code false} if it contains an uncorrected value. + * + * @hide + */ + public boolean isPseudorangeRateCorrected() { + return !isFlagSet(GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE); + } + + /** * Gets the pseudorange's rate uncertainty (1-Sigma) in m/s. * The uncertainty is represented as an absolute (single sided) value. */ @@ -437,6 +470,11 @@ public class GpsMeasurement implements Parcelable { * The reported value includes {@link #getAccumulatedDeltaRangeUncertaintyInMeters()}. * * The availability of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + * + * A positive value indicates that the SV is moving away from the receiver. + * The sign of {@link #getAccumulatedDeltaRangeInMeters()} and its relation to the sign of + * {@link #getCarrierPhase()} is given by the equation: + * accumulated delta range = -k * carrier phase (where k is a constant) */ public double getAccumulatedDeltaRangeInMeters() { return mAccumulatedDeltaRangeInMeters; @@ -452,6 +490,8 @@ public class GpsMeasurement implements Parcelable { /** * Gets the accumulated delta range's uncertainty (1-Sigma) in meters. * The uncertainty is represented as an absolute (single sided) value. + * + * The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. */ public double getAccumulatedDeltaRangeUncertaintyInMeters() { return mAccumulatedDeltaRangeUncertaintyInMeters; @@ -460,7 +500,7 @@ public class GpsMeasurement implements Parcelable { /** * Sets the accumulated delta range's uncertainty (1-sigma) in meters. * - * The availability of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + * The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. */ public void setAccumulatedDeltaRangeUncertaintyInMeters(double value) { mAccumulatedDeltaRangeUncertaintyInMeters = value; @@ -1235,6 +1275,10 @@ public class GpsMeasurement implements Parcelable { mPseudorangeRateInMetersPerSec, "PseudorangeRateUncertaintyInMetersPerSec", mPseudorangeRateUncertaintyInMetersPerSec)); + builder.append(String.format( + format, + "PseudorangeRateIsCorrected", + isPseudorangeRateCorrected())); builder.append(String.format( format, diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java index b893f3f15dc2..5b12a61a2c36 100644 --- a/location/java/android/location/GpsNavigationMessage.java +++ b/location/java/android/location/GpsNavigationMessage.java @@ -60,6 +60,28 @@ public class GpsNavigationMessage implements Parcelable { */ public static final byte TYPE_CNAV2 = 4; + /** + * The Navigation Message Status is 'unknown'. + * + * @hide + */ + public static final short STATUS_UNKNOWN = 0; + + /** + * The Navigation Message was received without any parity error in its navigation words. + * + * @hide + */ + public static final short STATUS_PARITY_PASSED = (1<<0); + + /** + * The Navigation Message was received with words that failed parity check, but the receiver was + * able to correct those words. + * + * @hide + */ + public static final short STATUS_PARITY_REBUILT = (1<<1); + // End enumerations in sync with gps.h private byte mType; @@ -67,6 +89,7 @@ public class GpsNavigationMessage implements Parcelable { private short mMessageId; private short mSubmessageId; private byte[] mData; + private short mStatus; GpsNavigationMessage() { initialize(); @@ -81,6 +104,7 @@ public class GpsNavigationMessage implements Parcelable { mMessageId = navigationMessage.mMessageId; mSubmessageId = navigationMessage.mSubmessageId; mData = navigationMessage.mData; + mStatus = navigationMessage.mStatus; } /** @@ -194,6 +218,41 @@ public class GpsNavigationMessage implements Parcelable { mData = value; } + /** + * Gets the Status of the navigation message contained in the object. + * + * @hide + */ + public short getStatus() { + return mStatus; + } + + /** + * Sets the status of the navigation message. + * + * @hide + */ + public void setStatus(short value) { + mStatus = value; + } + + /** + * Gets a string representation of the 'status'. + * For internal and logging use only. + */ + private String getStatusString() { + switch (mStatus) { + case STATUS_UNKNOWN: + return "Unknown"; + case STATUS_PARITY_PASSED: + return "ParityPassed"; + case STATUS_PARITY_REBUILT: + return "ParityRebuilt"; + default: + return "<Invalid:" + mStatus + ">"; + } + } + public static final Creator<GpsNavigationMessage> CREATOR = new Creator<GpsNavigationMessage>() { @Override @@ -210,6 +269,13 @@ public class GpsNavigationMessage implements Parcelable { parcel.readByteArray(data); navigationMessage.setData(data); + if (parcel.dataAvail() >= Integer.SIZE) { + int status = parcel.readInt(); + navigationMessage.setStatus((short) status); + } else { + navigationMessage.setStatus(STATUS_UNKNOWN); + } + return navigationMessage; } @@ -226,6 +292,7 @@ public class GpsNavigationMessage implements Parcelable { parcel.writeInt(mSubmessageId); parcel.writeInt(mData.length); parcel.writeByteArray(mData); + parcel.writeInt(mStatus); } @Override @@ -240,6 +307,7 @@ public class GpsNavigationMessage implements Parcelable { builder.append(String.format(format, "Type", getTypeString())); builder.append(String.format(format, "Prn", mPrn)); + builder.append(String.format(format, "Status", getStatusString())); builder.append(String.format(format, "MessageId", mMessageId)); builder.append(String.format(format, "SubmessageId", mSubmessageId)); @@ -261,5 +329,6 @@ public class GpsNavigationMessage implements Parcelable { mMessageId = -1; mSubmessageId = -1; mData = EMPTY_ARRAY; + mStatus = STATUS_UNKNOWN; } } diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDeviceInfo.java index df4d60d13765..d58b1d132a9f 100644 --- a/media/java/android/media/AudioDevice.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -20,9 +20,8 @@ import android.util.SparseIntArray; /** * Class to provide information about the audio devices. - * @hide */ -public class AudioDevice { +public class AudioDeviceInfo { /** * A device type associated with an unknown or uninitialized device. @@ -42,7 +41,7 @@ public class AudioDevice { */ public static final int TYPE_WIRED_HEADSET = 3; /** - * A device type describing a pair of wired headphones . + * A device type describing a pair of wired headphones. */ public static final int TYPE_WIRED_HEADPHONES = 4; /** @@ -54,7 +53,7 @@ public class AudioDevice { */ public static final int TYPE_LINE_DIGITAL = 6; /** - * A device type describing a Bluetooth device typically used for telephony . + * A device type describing a Bluetooth device typically used for telephony. */ public static final int TYPE_BLUETOOTH_SCO = 7; /** @@ -106,46 +105,92 @@ public class AudioDevice { */ public static final int TYPE_AUX_LINE = 19; - AudioDevicePortConfig mConfig; + private final AudioDevicePort mPort; - AudioDevice(AudioDevicePortConfig config) { - mConfig = new AudioDevicePortConfig(config); + AudioDeviceInfo(AudioDevicePort port) { + mPort = port; } /** * @hide - * CANDIDATE FOR PUBLIC API - * @return + * @return The internal device ID. */ - public boolean isInputDevice() { - return (mConfig.port().role() == AudioPort.ROLE_SOURCE); + public int getId() { + return mPort.handle().id(); } /** - * @hide - * CANDIDATE FOR PUBLIC API - * @return + * @return The human-readable name of the audio device. */ - public boolean isOutputDevice() { - return (mConfig.port().role() == AudioPort.ROLE_SINK); + public String getName() { + return mPort.name(); } /** - * @hide - * CANDIDATE FOR PUBLIC API - * @return + * @return The "address" string of the device. This generally contains device-specific + * parameters. */ - public int getDeviceType() { - return INT_TO_EXT_DEVICE_MAPPING.get(mConfig.port().type(), TYPE_UNKNOWN); + // TODO Is there a compelling reason to expose this? + public String getAddress() { + return mPort.address(); + } + + /** + * @return true if the audio device is a source for audio data (e.e an input). + */ + public boolean isSource() { + return mPort.role() == AudioPort.ROLE_SOURCE; } /** - * @hide - * CANDIDATE FOR PUBLIC API - * @return + * @return true if the audio device is a sink for audio data (i.e. an output). */ - public String getAddress() { - return mConfig.port().address(); + public boolean isSink() { + return mPort.role() == AudioPort.ROLE_SINK; + } + + /** + * @return An array of sample rates supported by the audio device. + */ + public int[] getSampleRates() { + return mPort.samplingRates(); + } + + /** + * @return An array of channel masks supported by the audio device (defined in + * AudioFormat.java). + */ + public int[] getChannelMasks() { + return mPort.channelMasks(); + } + + /** + * @return An array of channel counts supported by the audio device. + */ + public int[] getChannelCounts() { + int[] masks = getChannelMasks(); + int[] counts = new int[masks.length]; + for (int mask_index = 0; mask_index < masks.length; mask_index++) { + counts[mask_index] = isSink() + ? AudioFormat.channelCountFromOutChannelMask(masks[mask_index]) + : AudioFormat.channelCountFromInChannelMask(masks[mask_index]); + } + return counts; + } + + /** + * @return An array of audio format IDs supported by the audio device (defined in + * AudioFormat.java) + */ + public int[] getFormats() { + return mPort.formats(); + } + + /** + * @return The device type identifier of the audio device (i.e. TYPE_BUILTIN_SPEAKER). + */ + public int getType() { + return INT_TO_EXT_DEVICE_MAPPING.get(mPort.type(), TYPE_UNKNOWN); } /** @hide */ diff --git a/media/java/android/media/AudioDevicesManager.java b/media/java/android/media/AudioDevicesManager.java index ee11eef1622a..ca238d77dc6b 100644 --- a/media/java/android/media/AudioDevicesManager.java +++ b/media/java/android/media/AudioDevicesManager.java @@ -17,24 +17,54 @@ package android.media; import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.HashMap; import java.util.Iterator; -/** @hide - * API candidate +/** + * AudioDevicesManager implements the Android Media Audio device enumeration and notification + * functionality. This functionality is in two comlementary parts. + * <ol> + * <li>{@link AudioDevicesManager#listDevices(int)} gets the list of current audio devices + * </li> + * <li>{@link AudioDevicesManager#addOnAudioDeviceConnectionListener(OnAudioDeviceConnectionListener, android.os.Handler)} + * provides a mechanism for applications to be informed of audio device connect/disconnect events. + * </li> + * </ol> */ public class AudioDevicesManager { + private static String TAG = "AudioDevicesManager"; - private static boolean DEBUG = true; + + private static boolean DEBUG = false; private AudioManager mAudioManager = null; + private OnAmPortUpdateListener mPortListener = null; - /* - * Enum/Selection API + /** + * The message sent to apps when the contents of the device list changes if they provide + * a {#link Handler} object to addOnAudioDeviceConnectionListener(). + */ + private final static int MSG_DEVICES_LIST_CHANGE = 0; + + private ArrayMap<OnAudioDeviceConnectionListener, NativeEventHandlerDelegate> + mDeviceConnectionListeners = + new ArrayMap<OnAudioDeviceConnectionListener, NativeEventHandlerDelegate>(); + + /** + * @hide + * The AudioDevicesManager class is used to enumerate the physical audio devices connected + * to the system. See also {@link AudioDeviceInfo}. */ public AudioDevicesManager(Context context) { mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); @@ -42,214 +72,120 @@ public class AudioDevicesManager { mAudioManager.registerAudioPortUpdateListener(mPortListener); } - /** @hide - * API candidate + /** + * Specifies to the {@link AudioDevicesManager#listDevices(int)} method to include + * source (i.e. input) audio devices. */ - //TODO Merge this class into android.media.AudioDevice - public class AudioDeviceInfo { - private AudioDevicePort mPort = null; - - /** @hide */ - /* package */ AudioDeviceInfo(AudioDevicePort port) { - mPort = port; - } - - public int getId() { return mPort.handle().id(); } - - public String getName() { return mPort.name(); } - - public int getType() { - return mPort.type(); - } - - public String getAddress() { - return mPort.address(); - } - - public int getRole() { return mPort.role(); } - - public int[] getSampleRates() { return mPort.samplingRates(); } - - public int[] getChannelMasks() { return mPort.channelMasks(); } - - public int[] getChannelCounts() { - int[] masks = getChannelMasks(); - int[] counts = new int[masks.length]; - for (int mask_index = 0; mask_index < masks.length; mask_index++) { - counts[mask_index] = getRole() == AudioPort.ROLE_SINK - ? AudioFormat.channelCountFromOutChannelMask(masks[mask_index]) - : AudioFormat.channelCountFromInChannelMask(masks[mask_index]); - } - return counts; - } - - /* The format IDs are in AudioFormat.java */ - public int[] getFormats() { return mPort.formats(); } + public static final int LIST_DEVICES_INPUTS = 0x0001; - public String toString() { return "" + getId() + " - " + getName(); } - } - - /** @hide */ - public static final int LIST_DEVICES_OUTPUTS = 0x0001; - /** @hide */ - public static final int LIST_DEVICES_INPUTS = 0x0002; - /** @hide */ - public static final int LIST_DEVICES_BUILTIN = 0x0004; - /** @hide */ - public static final int LIST_DEVICES_USB = 0x0008; - // TODO implement the semantics for these. - /** @hide */ - public static final int LIST_DEVICES_WIRED = 0x0010; - /** @hide */ - public static final int LIST_DEVICES_UNWIRED = 0x0020; + /** + * Specifies to the {@link AudioDevicesManager#listDevices(int)} method to include + * sink (i.e. output) audio devices. + */ + public static final int LIST_DEVICES_OUTPUTS = 0x0002; - /** @hide */ + /** + * Specifies to the {@link AudioDevicesManager#listDevices(int)} method to include both + * source and sink devices. + */ public static final int LIST_DEVICES_ALL = LIST_DEVICES_OUTPUTS | LIST_DEVICES_INPUTS; + /** + * Determines if a given AudioDevicePort meets the specified filter criteria. + * @param port The port to test. + * @param flags A set of bitflags specifying the criteria to test. + * @see {@link LIST_DEVICES_OUTPUTS} and {@link LIST_DEVICES_INPUTS} + **/ private boolean checkFlags(AudioDevicePort port, int flags) { - // Inputs / Outputs - boolean passed = - port.role() == AudioPort.ROLE_SINK && (flags & LIST_DEVICES_OUTPUTS) != 0 || - port.role() == AudioPort.ROLE_SOURCE && (flags & LIST_DEVICES_INPUTS) != 0; - - // USB - if (passed && (flags & LIST_DEVICES_USB) != 0) { - int role = port.role(); - int type = port.type(); - Slog.i(TAG, " role:" + role + " type:0x" + Integer.toHexString(type)); - passed = - (role == AudioPort.ROLE_SINK && (type & AudioSystem.DEVICE_OUT_ALL_USB) != 0) || - (role == AudioPort.ROLE_SOURCE && (type & AudioSystem.DEVICE_IN_ALL_USB) != 0); - } - - return passed; + return port.role() == AudioPort.ROLE_SINK && (flags & LIST_DEVICES_OUTPUTS) != 0 || + port.role() == AudioPort.ROLE_SOURCE && (flags & LIST_DEVICES_INPUTS) != 0; } - /** @hide */ - public ArrayList<AudioDeviceInfo> listDevices(int flags) { - Slog.i(TAG, "AudioManager.listDevices(" + Integer.toHexString(flags) + ")"); - + /** + * Generates a list of AudioDeviceInfo objects corresponding to the audio devices currently + * connected to the system and meeting the criteria specified in the <code>flags</code> + * parameter. + * @param flags A set of bitflags specifying the criteria to test. + * @see {@link LIST_DEVICES_OUTPUTS}, {@link LIST_DEVICES_INPUTS} and {@link LIST_DEVICES_ALL}. + * @return A (possibly zero-length) array of AudioDeviceInfo objects. + */ + public AudioDeviceInfo[] listDevices(int flags) { ArrayList<AudioDevicePort> ports = new ArrayList<AudioDevicePort>(); int status = mAudioManager.listAudioDevicePorts(ports); - - Slog.i(TAG, " status:" + status + " numPorts:" + ports.size()); - - ArrayList<AudioDeviceInfo> deviceList = new ArrayList<AudioDeviceInfo>(); - - if (status == AudioManager.SUCCESS) { - deviceList = new ArrayList<AudioDeviceInfo>(); - for (AudioDevicePort port : ports) { - if (checkFlags(port, flags)) { - deviceList.add(new AudioDeviceInfo(port)); - } - } + if (status != AudioManager.SUCCESS) { + // fail and bail! + return new AudioDeviceInfo[0]; } - return deviceList; - } - private ArrayList<OnAudioDeviceConnectionListener> mDeviceConnectionListeners = - new ArrayList<OnAudioDeviceConnectionListener>(); - - private HashMap<Integer, AudioPort> mCurrentPortlist = - new HashMap<Integer, AudioPort>(); - - private ArrayList<AudioDeviceInfo> calcAddedDevices(AudioPort[] portList) { - ArrayList<AudioDeviceInfo> addedDevices = new ArrayList<AudioDeviceInfo>(); - synchronized(mCurrentPortlist) { - for(int portIndex = 0; portIndex < portList.length; portIndex++) { - if (portList[portIndex] instanceof AudioDevicePort) { - if (!mCurrentPortlist.containsKey(portList[portIndex].handle().id())) { - addedDevices.add(new AudioDeviceInfo((AudioDevicePort)portList[portIndex])); - } - } + // figure out how many AudioDeviceInfo we need space for + int numRecs = 0; + for (AudioDevicePort port : ports) { + if (checkFlags(port, flags)) { + numRecs++; } } - return addedDevices; - } - private boolean hasPortId(AudioPort[] portList, int id) { - for(int portIndex = 0; portIndex < portList.length; portIndex++) { - if (portList[portIndex].handle().id() == id) { - return true; + // Now load them up + AudioDeviceInfo[] deviceList = new AudioDeviceInfo[numRecs]; + int slot = 0; + for (AudioDevicePort port : ports) { + if (checkFlags(port, flags)) { + deviceList[slot++] = new AudioDeviceInfo(port); } } - return false; - } - - private ArrayList<AudioDeviceInfo> calcRemovedDevices(AudioPort[] portList) { - ArrayList<AudioDeviceInfo> removedDevices = new ArrayList<AudioDeviceInfo>(); - synchronized (mCurrentPortlist) { - Iterator it = mCurrentPortlist.entrySet().iterator(); - while (it.hasNext()) { - HashMap.Entry pairs = (HashMap.Entry)it.next(); - if (pairs.getValue() instanceof AudioDevicePort) { - if (!hasPortId(portList, ((Integer)pairs.getKey()).intValue())) { - removedDevices.add(new AudioDeviceInfo((AudioDevicePort)pairs.getValue())); - } - } - } - } - return removedDevices; + return deviceList; } - private void buildCurrentDevicesList(AudioPort[] portList) { - synchronized (mCurrentPortlist) { - mCurrentPortlist.clear(); - for (int portIndex = 0; portIndex < portList.length; portIndex++) { - if (portList[portIndex] instanceof AudioDevicePort) { - mCurrentPortlist.put(portList[portIndex].handle().id(), - (AudioDevicePort)portList[portIndex]); - } + /** + * Adds an {@link OnAudioDeviceConnectionListener} to receive notifications of changes + * to the set of connected audio devices. + */ + public void addOnAudioDeviceConnectionListener(OnAudioDeviceConnectionListener listener, + android.os.Handler handler) { + if (listener != null && !mDeviceConnectionListeners.containsKey(listener)) { + synchronized (mDeviceConnectionListeners) { + mDeviceConnectionListeners.put( + listener, new NativeEventHandlerDelegate(listener, handler)); } } } - /** @hide */ - public void addDeviceConnectionListener(OnAudioDeviceConnectionListener listener) { + /** + * Removes an {@link OnAudioDeviceConnectionListener} which has been previously registered + * to receive notifications of changes to the set of connected audio devices. + */ + public void removeOnAudioDeviceConnectionListener(OnAudioDeviceConnectionListener listener) { synchronized (mDeviceConnectionListeners) { - mDeviceConnectionListeners.add(listener); + if (mDeviceConnectionListeners.containsKey(listener)) { + mDeviceConnectionListeners.remove(listener); + } } } - /** @hide */ - public void removeDeviceConnectionListener(OnAudioDeviceConnectionListener listener) { + /** + * Sends device list change notification to all listeners. + */ + private void broadcastDeviceListChange() { + Collection<NativeEventHandlerDelegate> values; synchronized (mDeviceConnectionListeners) { - mDeviceConnectionListeners.remove(listener); + values = mDeviceConnectionListeners.values(); + } + for(NativeEventHandlerDelegate delegate : values) { + Handler handler = delegate.getHandler(); + if (handler != null) { + handler.sendEmptyMessage(MSG_DEVICES_LIST_CHANGE); + } } } /** - * @hide + * Handles Port list update notifications from the AudioManager */ private class OnAmPortUpdateListener implements AudioManager.OnAudioPortUpdateListener { static final String TAG = "OnAmPortUpdateListener"; public void onAudioPortListUpdate(AudioPort[] portList) { - Slog.i(TAG, "onAudioPortListUpdate() " + portList.length + " ports."); - ArrayList<AudioDeviceInfo> addedDevices = calcAddedDevices(portList); - ArrayList<AudioDeviceInfo> removedDevices = calcRemovedDevices(portList); - - ArrayList<OnAudioDeviceConnectionListener> listeners = null; - synchronized (mDeviceConnectionListeners) { - listeners = - new ArrayList<OnAudioDeviceConnectionListener>(mDeviceConnectionListeners); - } - - // Connect - if (addedDevices.size() != 0) { - for (OnAudioDeviceConnectionListener listener : listeners) { - listener.onConnect(addedDevices); - } - } - - // Disconnect? - if (removedDevices.size() != 0) { - for (OnAudioDeviceConnectionListener listener : listeners) { - listener.onDisconnect(removedDevices); - } - } - - buildCurrentDevicesList(portList); + broadcastDeviceListChange(); } /** @@ -257,14 +193,70 @@ public class AudioDevicesManager { * @param patchList the updated list of audio patches */ public void onAudioPatchListUpdate(AudioPatch[] patchList) { - Slog.i(TAG, "onAudioPatchListUpdate() " + patchList.length + " patches."); + if (DEBUG) { + Slog.d(TAG, "onAudioPatchListUpdate() " + patchList.length + " patches."); + } } /** * Callback method called when the mediaserver dies */ public void onServiceDied() { - Slog.i(TAG, "onServiceDied()"); + if (DEBUG) { + Slog.i(TAG, "onServiceDied()"); + } + + broadcastDeviceListChange(); + } + } + + //--------------------------------------------------------- + // Inner classes + //-------------------- + /** + * Helper class to handle the forwarding of native events to the appropriate listener + * (potentially) handled in a different thread. + */ + private class NativeEventHandlerDelegate { + private final Handler mHandler; + + NativeEventHandlerDelegate(final OnAudioDeviceConnectionListener listener, + Handler handler) { + // find the looper for our new event handler + Looper looper; + if (handler != null) { + looper = handler.getLooper(); + } else { + // no given handler, use the looper the addListener call was called in + looper = Looper.getMainLooper(); + } + + // construct the event handler with this looper + if (looper != null) { + // implement the event handler delegate + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_DEVICES_LIST_CHANGE: + // call the OnAudioDeviceConnectionListener + if (listener != null) { + listener.onAudioDeviceConnection(); + } + break; + default: + Slog.e(TAG, "Unknown native event type: " + msg.what); + break; + } + } + }; + } else { + mHandler = null; + } + } + + Handler getHandler() { + return mHandler; } } } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index fac69eacb280..6c41a2abbb7b 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -1851,6 +1851,39 @@ public class AudioTrack return err == 0 ? SUCCESS : ERROR; } + //-------------------------------------------------------------------------- + // Explicit Routing + //-------------------- + private AudioDeviceInfo mPreferredDevice = null; + + /** + * Specifies an audio device (via and {@link AudioDeviceInfo} object) to route + * the output from this AudioTrack. + * @param deviceSpec The {@link AudioDeviceInfo} specifying the physical audio device. + * If deviceSpec is null, default routing is restored. + * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and + * does not correspond to a valid audio output device. + */ + public boolean setPreferredOutputDevice(AudioDeviceInfo deviceSpec) { + // Do some validation.... + if (deviceSpec != null && !deviceSpec.isSink()) { + return false; + } + + mPreferredDevice = deviceSpec; + int routingDeviceId = mPreferredDevice != null ? deviceSpec.getId() : 0; + + return native_setOutputDevice(routingDeviceId); + } + + /** + * Returns the selected output specified by {@link #setPreferredOutputDevice}. Note that this + * is not guarenteed to correspond to the actual device being used for playback. + */ + public AudioDeviceInfo getPreferredOutputDevice() { + return mPreferredDevice; + } + //--------------------------------------------------------- // Interface definitions //-------------------- @@ -2027,6 +2060,8 @@ public class AudioTrack private native final int native_attachAuxEffect(int effectId); private native final int native_setAuxEffectSendLevel(float level); + private native final boolean native_setOutputDevice(int deviceId); + //--------------------------------------------------------- // Utility methods //------------------ diff --git a/media/java/android/media/DataSource.java b/media/java/android/media/DataSource.java deleted file mode 100644 index 347bd5f32a25..000000000000 --- a/media/java/android/media/DataSource.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2012 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.media; - -import java.io.Closeable; - -/** - * An abstraction for a media data source, e.g. a file or an http stream - * {@hide} - */ -public interface DataSource extends Closeable { - /** - * Reads data from the data source at the requested position - * - * @param offset where in the source to read - * @param buffer the buffer to read the data into - * @param size how many bytes to read - * @return the number of bytes read, or -1 if there was an error - */ - public int readAt(long offset, byte[] buffer, int size); - - /** - * Gets the size of the data source. - * - * @return size of data source, or -1 if the length is unknown - */ - public long getSize(); -} diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 59fccda04055..d82afdf7b147 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -669,10 +669,10 @@ final public class MediaCodec { * Thrown when an internal codec error occurs. */ public final static class CodecException extends IllegalStateException { - CodecException(int errorCode, int actionCode, String detailMessage) { + CodecException(int errorCode, int actionCode, String detailMessage, int reason) { super(detailMessage); mErrorCode = errorCode; - mReason = REASON_HARDWARE; + mReason = reason; mActionCode = actionCode; // TODO get this from codec diff --git a/media/java/android/media/MediaDataSource.java b/media/java/android/media/MediaDataSource.java new file mode 100644 index 000000000000..246c0ef0a3d0 --- /dev/null +++ b/media/java/android/media/MediaDataSource.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 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.media; + +import java.io.Closeable; + +/** + * For supplying media data to the framework. Implement this if your app has + * special requirements for the way media data is obtained. + * + * <p class="note">Methods of this interface may be called on multiple different + * threads. There will be a thread synchronization point between each call to ensure that + * modifications to the state of your MediaDataSource are visible to future calls. This means + * you don't need to do your own synchronization unless you're modifying the + * MediaDataSource from another thread while it's being used by the framework.</p> + */ +public interface MediaDataSource extends Closeable { + /** + * Called to request data from the given position. + * + * Implementations should should write up to {@code size} bytes into + * {@code buffer}, and return the number of bytes written. + * + * Return {@code 0} to indicate that {@code position} is at, or beyond, the + * end of the source. + * + * Return {@code -1} to indicate that a fatal error occurred. The failed + * read will not be retried, so transient errors should be handled + * internally. + * + * Throwing an exception from this method will have the same effect as + * returning {@code -1}. + * + * @param position the position in the data source to read from. + * @param buffer the buffer to read the data into. + * @param size the number of bytes to read. + * @return the number of bytes read, or -1 if there was an error. + */ + public int readAt(long position, byte[] buffer, int size); + + /** + * Called to get the size of the data source. + * + * @return the size of data source in bytes, or -1 if the size is unknown. + */ + public long getSize(); +} diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index b23b540ebee2..b4acbc0c06fd 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -66,10 +66,11 @@ final public class MediaExtractor { } /** - * Sets the DataSource object to be used as the data source for this extractor - * {@hide} + * Sets the data source (MediaDataSource) to use. + * + * @param dataSource the MediaDataSource for the media you want to extract from */ - public native final void setDataSource(DataSource source) throws IOException; + public native final void setDataSource(MediaDataSource dataSource) throws IllegalArgumentException, IOException; /** * Sets the data source as a content Uri. diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 9aa8003e9320..a3ff080c53f0 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -203,7 +203,20 @@ public class MediaMetadataRetriever } /** - * Call this method after setDataSource(). This method retrieves the + * Sets the data source (MediaDataSource) to use. + * + * @param dataSource the MediaDataSource for the media you want to play + */ + public void setDataSource(MediaDataSource dataSource) + throws IllegalArgumentException { + _setDataSource(dataSource); + } + + private native void _setDataSource(MediaDataSource dataSource) + throws IllegalArgumentException; + + /** + * Call this method after setDataSource(). This method retrieves the * meta data value associated with the keyCode. * * The keyCode currently supported is listed below as METADATA_XXX diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 83954aec894b..cb80bc4cf49d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -181,7 +181,8 @@ import java.lang.ref.WeakReference; * {@link #setDataSource(FileDescriptor)}, or * {@link #setDataSource(String)}, or * {@link #setDataSource(Context, Uri)}, or - * {@link #setDataSource(FileDescriptor, long, long)} transfers a + * {@link #setDataSource(FileDescriptor, long, long)}, or + * {@link #setDataSource(MediaDataSource)} transfers a * MediaPlayer object in the <em>Idle</em> state to the * <em>Initialized</em> state. * <ul> @@ -1127,6 +1128,20 @@ public class MediaPlayer implements SubtitleController.Listener throws IOException, IllegalArgumentException, IllegalStateException; /** + * Sets the data source (MediaDataSource) to use. + * + * @param dataSource the MediaDataSource for the media you want to play + * @throws IllegalStateException if it is called in an invalid state + */ + public void setDataSource(MediaDataSource dataSource) + throws IllegalArgumentException, IllegalStateException { + _setDataSource(dataSource); + } + + private native void _setDataSource(MediaDataSource dataSource) + throws IllegalArgumentException, IllegalStateException; + + /** * Prepares the player for playback, synchronously. * * After setting the datasource and the display surface, you need to either diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java index 7350bd4a8242..9446c0ccb7e6 100644 --- a/media/java/android/media/MediaSync.java +++ b/media/java/android/media/MediaSync.java @@ -43,9 +43,9 @@ import java.util.List; * // MediaCodec videoDecoder = ...; * videoDecoder.configure(format, inputSurface, ...); * ... - * sync.configureAudioTrack(audioTrack, nativeSampleRateInHz); + * sync.configureAudioTrack(audioTrack); * sync.setCallback(new MediaSync.Callback() { - * \@Override + * {@literal @Override} * public void onReturnAudioBuffer(MediaSync sync, ByteBuffer audioBuffer, int bufferIndex) { * ... * } @@ -151,8 +151,6 @@ final public class MediaSync { private Handler mCallbackHandler = null; private MediaSync.Callback mCallback = null; - private int mNativeSampleRateInHz = 0; - private Thread mAudioThread = null; // Created on mAudioThread when mAudioThread is started. When used on user thread, they should // be guarded by checking mAudioThread. @@ -247,20 +245,17 @@ final public class MediaSync { * Configures the audio track for MediaSync. * * @param audioTrack Specify an AudioTrack through which to render the audio data. - * @throws IllegalArgumentException if the audioTrack has been released, or is invalid, - * or nativeSampleRateInHz is invalid. + * @throws IllegalArgumentException if the audioTrack has been released, or is invalid. * @throws IllegalStateException if not in the Initialized state, or another audio track * has already been configured. */ - public void configureAudioTrack(AudioTrack audioTrack, int nativeSampleRateInHz) { - if (audioTrack != null && nativeSampleRateInHz <= 0) { - final String msg = "Native sample rate " + nativeSampleRateInHz + " is invalid"; - throw new IllegalArgumentException(msg); - } + public void configureAudioTrack(AudioTrack audioTrack) { + // AudioTrack has sanity check for configured sample rate. + int nativeSampleRateInHz = (audioTrack == null ? 0 : audioTrack.getSampleRate()); + native_configureAudioTrack(audioTrack, nativeSampleRateInHz); mAudioTrack = audioTrack; - mNativeSampleRateInHz = nativeSampleRateInHz; - if (mAudioThread == null) { + if (audioTrack != null && mAudioThread == null) { createAudioThread(); } } @@ -349,8 +344,9 @@ final public class MediaSync { int status = AudioTrack.SUCCESS; if (mAudioTrack != null) { - int playbackSampleRate = (int)(rate * mNativeSampleRateInHz + 0.5); - rate = playbackSampleRate / (float)mNativeSampleRateInHz; + int nativeSampleRateInHz = mAudioTrack.getSampleRate(); + int playbackSampleRate = (int)(rate * nativeSampleRateInHz + 0.5); + rate = playbackSampleRate / (float)nativeSampleRateInHz; try { if (rate == 0.0) { diff --git a/media/java/android/media/OnAudioDeviceConnectionListener.java b/media/java/android/media/OnAudioDeviceConnectionListener.java index 4bdd4d0ce376..71c135a54731 100644 --- a/media/java/android/media/OnAudioDeviceConnectionListener.java +++ b/media/java/android/media/OnAudioDeviceConnectionListener.java @@ -16,13 +16,16 @@ package android.media; -import java.util.ArrayList; - /** - * @hide - * API candidate + * OnAudioDeviceConnectionListener defines the interface for notification listeners in the + * {@link AudioDevicesManager} */ -public abstract class OnAudioDeviceConnectionListener { - public void onConnect(ArrayList<AudioDevicesManager.AudioDeviceInfo> devices) {} - public void onDisconnect(ArrayList<AudioDevicesManager.AudioDeviceInfo> devices) {} +public interface OnAudioDeviceConnectionListener { + /** + * Called by the {@link AudioDevicesManager} to indicate that an audio device has been + * connected or disconnected. A listener will probably call the + * {@link AudioDevicesManager#listDevices} method to retrieve the current list of audio + * devices. + */ + public void onAudioDeviceConnection(); } diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java index be5adc8d7653..49e56bc9b507 100644 --- a/media/java/android/media/audiofx/Virtualizer.java +++ b/media/java/android/media/audiofx/Virtualizer.java @@ -17,7 +17,7 @@ package android.media.audiofx; import android.annotation.IntDef; -import android.media.AudioDevice; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.audiofx.AudioEffect; import android.util.Log; @@ -204,7 +204,7 @@ public class Virtualizer extends AudioEffect { // convert channel mask to internal native representation paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask)); // convert Java device type to internal representation - paramsConverter.putInt(AudioDevice.convertDeviceTypeToInternalDevice(deviceType)); + paramsConverter.putInt(AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType)); // allocate an array to store the results byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/]; @@ -305,9 +305,9 @@ public class Virtualizer extends AudioEffect { throws IllegalArgumentException { switch (virtualizationMode) { case VIRTUALIZATION_MODE_BINAURAL: - return AudioDevice.TYPE_WIRED_HEADPHONES; + return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; case VIRTUALIZATION_MODE_TRANSAURAL: - return AudioDevice.TYPE_BUILTIN_SPEAKER; + return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; default: throw (new IllegalArgumentException( "Virtualizer: illegal virtualization mode " + virtualizationMode)); @@ -317,7 +317,7 @@ public class Virtualizer extends AudioEffect { private static int getDeviceForModeForce(@ForceVirtualizationMode int virtualizationMode) throws IllegalArgumentException { if (virtualizationMode == VIRTUALIZATION_MODE_AUTO) { - return AudioDevice.TYPE_UNKNOWN; + return AudioDeviceInfo.TYPE_UNKNOWN; } else { return getDeviceForModeQuery(virtualizationMode); } @@ -325,24 +325,24 @@ public class Virtualizer extends AudioEffect { private static int deviceToMode(int deviceType) { switch (deviceType) { - case AudioDevice.TYPE_WIRED_HEADSET: - case AudioDevice.TYPE_WIRED_HEADPHONES: - case AudioDevice.TYPE_BLUETOOTH_SCO: - case AudioDevice.TYPE_BUILTIN_EARPIECE: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: return VIRTUALIZATION_MODE_BINAURAL; - case AudioDevice.TYPE_BUILTIN_SPEAKER: - case AudioDevice.TYPE_LINE_ANALOG: - case AudioDevice.TYPE_LINE_DIGITAL: - case AudioDevice.TYPE_BLUETOOTH_A2DP: - case AudioDevice.TYPE_HDMI: - case AudioDevice.TYPE_HDMI_ARC: - case AudioDevice.TYPE_USB_DEVICE: - case AudioDevice.TYPE_USB_ACCESSORY: - case AudioDevice.TYPE_DOCK: - case AudioDevice.TYPE_FM: - case AudioDevice.TYPE_AUX_LINE: + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + case AudioDeviceInfo.TYPE_LINE_ANALOG: + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_USB_DEVICE: + case AudioDeviceInfo.TYPE_USB_ACCESSORY: + case AudioDeviceInfo.TYPE_DOCK: + case AudioDeviceInfo.TYPE_FM: + case AudioDeviceInfo.TYPE_AUX_LINE: return VIRTUALIZATION_MODE_TRANSAURAL; - case AudioDevice.TYPE_UNKNOWN: + case AudioDeviceInfo.TYPE_UNKNOWN: default: return VIRTUALIZATION_MODE_OFF; } @@ -433,7 +433,7 @@ public class Virtualizer extends AudioEffect { throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { // convert Java device type to internal representation int deviceType = getDeviceForModeForce(virtualizationMode); - int internalDevice = AudioDevice.convertDeviceTypeToInternalDevice(deviceType); + int internalDevice = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice); @@ -470,7 +470,7 @@ public class Virtualizer extends AudioEffect { int[] value = new int[1]; int status = getParameter(PARAM_VIRTUALIZATION_MODE, value); if (status >= 0) { - return deviceToMode(AudioDevice.convertInternalDeviceToDeviceType(value[0])); + return deviceToMode(AudioDeviceInfo.convertInternalDeviceToDeviceType(value[0])); } else if (status == AudioEffect.ERROR_BAD_VALUE) { return VIRTUALIZATION_MODE_OFF; } else { diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 66d055a45aa5..c8464c7664c2 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -7,6 +7,7 @@ LOCAL_SRC_FILES:= \ android_media_MediaCrypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ + android_media_MediaDataSource.cpp \ android_media_MediaDrm.cpp \ android_media_MediaExtractor.cpp \ android_media_MediaHTTPConnection.cpp \ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 16758d05327a..5f586a92cdc5 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -70,6 +70,11 @@ static struct CodecActionCodes { jint codecActionRecoverable; } gCodecActionCodes; +static struct ExceptionReason { + jint reasonHardware; + jint reasonReclaimed; +} gExceptionReason; + struct fields_t { jfieldID context; jmethodID postEventFromNativeID; @@ -568,7 +573,7 @@ static jthrowable createCodecException( env, env->FindClass("android/media/MediaCodec$CodecException")); CHECK(clazz.get() != NULL); - const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V"); + const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;I)V"); CHECK(ctor != NULL); ScopedLocalRef<jstring> msgObj( @@ -587,7 +592,9 @@ static jthrowable createCodecException( break; } - return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get()); + // TODO: propagate reason from MediaCodec. + int reason = gExceptionReason.reasonHardware; + return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get(), reason); } void JMediaCodec::handleCallback(const sp<AMessage> &msg) { @@ -1454,6 +1461,16 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { CHECK(field != NULL); gCodecActionCodes.codecActionRecoverable = env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "REASON_HARDWARE", "I"); + CHECK(field != NULL); + gExceptionReason.reasonHardware = + env->GetStaticIntField(clazz.get(), field); + + field = env->GetStaticFieldID(clazz.get(), "REASON_RECLAIMED", "I"); + CHECK(field != NULL); + gExceptionReason.reasonReclaimed = + env->GetStaticIntField(clazz.get(), field); } static void android_media_MediaCodec_native_setup( diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp new file mode 100644 index 000000000000..1e6d2afa84b8 --- /dev/null +++ b/media/jni/android_media_MediaDataSource.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "JMediaDataSource-JNI" +#include <utils/Log.h> + +#include "android_media_MediaDataSource.h" + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <binder/MemoryDealer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <nativehelper/ScopedLocalRef.h> + +namespace android { + +JMediaDataSource::JMediaDataSource(JNIEnv* env, jobject source) + : mJavaObjStatus(OK), mSizeIsCached(false), mCachedSize(0), mMemory(NULL) { + mMediaDataSourceObj = env->NewGlobalRef(source); + CHECK(mMediaDataSourceObj != NULL); + + ScopedLocalRef<jclass> mediaDataSourceClass(env, env->GetObjectClass(mMediaDataSourceObj)); + CHECK(mediaDataSourceClass.get() != NULL); + + mReadMethod = env->GetMethodID(mediaDataSourceClass.get(), "readAt", "(J[BI)I"); + CHECK(mReadMethod != NULL); + mGetSizeMethod = env->GetMethodID(mediaDataSourceClass.get(), "getSize", "()J"); + CHECK(mGetSizeMethod != NULL); + mCloseMethod = env->GetMethodID(mediaDataSourceClass.get(), "close", "()V"); + CHECK(mCloseMethod != NULL); + + ScopedLocalRef<jbyteArray> tmp(env, env->NewByteArray(kBufferSize)); + mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get()); + CHECK(mByteArrayObj != NULL); + + sp<MemoryDealer> memoryDealer = new MemoryDealer(kBufferSize, "JMediaDataSource"); + mMemory = memoryDealer->allocate(kBufferSize); + if (mMemory == NULL) { + ALOGE("Failed to allocate memory!"); + } +} + +JMediaDataSource::~JMediaDataSource() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mMediaDataSourceObj); + env->DeleteGlobalRef(mByteArrayObj); +} + +sp<IMemory> JMediaDataSource::getIMemory() { + Mutex::Autolock lock(mLock); + return mMemory; +} + +ssize_t JMediaDataSource::readAt(off64_t offset, size_t size) { + Mutex::Autolock lock(mLock); + + if (mJavaObjStatus != OK || mMemory == NULL) { + return -1; + } + if (size > kBufferSize) { + size = kBufferSize; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jint numread = env->CallIntMethod(mMediaDataSourceObj, mReadMethod, + (jlong)offset, mByteArrayObj, (jint)size); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred in readAt()"); + LOGW_EX(env); + env->ExceptionClear(); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } + if (numread < 0) { + ALOGW("An error occurred in readAt()"); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } + if ((size_t)numread > size) { + ALOGE("readAt read too many bytes."); + mJavaObjStatus = UNKNOWN_ERROR; + return -1; + } + + ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread); + env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)mMemory->pointer()); + return numread; +} + +status_t JMediaDataSource::getSize(off64_t* size) { + Mutex::Autolock lock(mLock); + + if (mJavaObjStatus != OK) { + return UNKNOWN_ERROR; + } + if (mSizeIsCached) { + return mCachedSize; + } + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + *size = env->CallLongMethod(mMediaDataSourceObj, mGetSizeMethod); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred in getSize()"); + LOGW_EX(env); + env->ExceptionClear(); + // After returning an error, size shouldn't be used by callers. + *size = UNKNOWN_ERROR; + mJavaObjStatus = UNKNOWN_ERROR; + return UNKNOWN_ERROR; + } + + // The minimum size should be -1, which indicates unknown size. + if (*size < 0) { + *size = -1; + } + + mCachedSize = *size; + mSizeIsCached = true; + return OK; +} + +void JMediaDataSource::close() { + Mutex::Autolock lock(mLock); + + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mMediaDataSourceObj, mCloseMethod); + // The closed state is effectively the same as an error state. + mJavaObjStatus = UNKNOWN_ERROR; +} + +} // namespace android diff --git a/media/jni/android_media_MediaDataSource.h b/media/jni/android_media_MediaDataSource.h new file mode 100644 index 000000000000..2bc237e02b06 --- /dev/null +++ b/media/jni/android_media_MediaDataSource.h @@ -0,0 +1,73 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_MEDIADATASOURCE_H_ +#define _ANDROID_MEDIA_MEDIADATASOURCE_H_ + +#include "jni.h" + +#include <media/IDataSource.h> +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/Mutex.h> + +namespace android { + +// The native counterpart to a Java android.media.MediaDataSource. It inherits from +// IDataSource so that it can be accessed remotely. +// +// If the java DataSource returns an error or throws an exception it +// will be considered to be in a broken state, and the only further call this +// will make is to close(). +class JMediaDataSource : public BnDataSource { +public: + enum { + kBufferSize = 64 * 1024, + }; + + JMediaDataSource(JNIEnv *env, jobject source); + virtual ~JMediaDataSource(); + + virtual sp<IMemory> getIMemory(); + virtual ssize_t readAt(off64_t offset, size_t size); + virtual status_t getSize(off64_t* size); + virtual void close(); + +private: + // Protect all member variables with mLock because this object will be + // accessed on different binder worker threads. + Mutex mLock; + + // The status of the java DataSource. Set to OK unless an error occurred or + // close() was called. + status_t mJavaObjStatus; + // Only call the java getSize() once so the app can't change the size on us. + bool mSizeIsCached; + off64_t mCachedSize; + sp<IMemory> mMemory; + + jobject mMediaDataSourceObj; + jmethodID mReadMethod; + jmethodID mGetSizeMethod; + jmethodID mCloseMethod; + jbyteArray mByteArrayObj; + + DISALLOW_EVIL_CONSTRUCTORS(JMediaDataSource); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_MEDIADATASOURCE_H_ diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index c0795b612d72..b6b7a80e6a3f 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -25,6 +25,7 @@ #include "android_runtime/Log.h" #include "jni.h" #include "JNIHelp.h" +#include "android_media_MediaDataSource.h" #include <media/IMediaHTTPService.h> #include <media/hardware/CryptoAPI.h> @@ -50,74 +51,6 @@ struct fields_t { static fields_t gFields; -class JavaDataSourceBridge : public DataSource { - jmethodID mReadMethod; - jmethodID mGetSizeMethod; - jmethodID mCloseMethod; - jobject mDataSource; - public: - JavaDataSourceBridge(JNIEnv *env, jobject source) { - mDataSource = env->NewGlobalRef(source); - - jclass datasourceclass = env->GetObjectClass(mDataSource); - CHECK(datasourceclass != NULL); - - mReadMethod = env->GetMethodID(datasourceclass, "readAt", "(J[BI)I"); - CHECK(mReadMethod != NULL); - - mGetSizeMethod = env->GetMethodID(datasourceclass, "getSize", "()J"); - CHECK(mGetSizeMethod != NULL); - - mCloseMethod = env->GetMethodID(datasourceclass, "close", "()V"); - CHECK(mCloseMethod != NULL); - } - - ~JavaDataSourceBridge() { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod(mDataSource, mCloseMethod); - env->DeleteGlobalRef(mDataSource); - } - - virtual status_t initCheck() const { - return OK; - } - - virtual ssize_t readAt(off64_t offset, void* buffer, size_t size) { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - - // XXX could optimize this by reusing the same array - jbyteArray byteArrayObj = env->NewByteArray(size); - env->DeleteLocalRef(env->GetObjectClass(mDataSource)); - env->DeleteLocalRef(env->GetObjectClass(byteArrayObj)); - ssize_t numread = env->CallIntMethod(mDataSource, mReadMethod, offset, byteArrayObj, (jint)size); - env->GetByteArrayRegion(byteArrayObj, 0, size, (jbyte*) buffer); - env->DeleteLocalRef(byteArrayObj); - if (env->ExceptionCheck()) { - ALOGW("Exception occurred while reading %zu at %lld", size, (long long)offset); - LOGW_EX(env); - env->ExceptionClear(); - return -1; - } - return numread; - } - - virtual status_t getSize(off64_t *size) { - JNIEnv *env = AndroidRuntime::getJNIEnv(); - - CHECK(size != NULL); - - int64_t len = env->CallLongMethod(mDataSource, mGetSizeMethod); - if (len < 0) { - *size = ERROR_UNSUPPORTED; - } else { - *size = len; - } - return OK; - } -}; - -//////////////////////////////////////////////////////////////////////////////// - JMediaExtractor::JMediaExtractor(JNIEnv *env, jobject thiz) : mClass(NULL), mObject(NULL) { @@ -777,7 +710,8 @@ static void android_media_MediaExtractor_setDataSourceCallback( return; } - sp<JavaDataSourceBridge> bridge = new JavaDataSourceBridge(env, callbackObj); + sp<DataSource> bridge = + DataSource::CreateFromIDataSource(new JMediaDataSource(env, callbackObj)); status_t err = extractor->setDataSource(bridge); if (err != OK) { @@ -881,7 +815,7 @@ static JNINativeMethod gMethods[] = { { "setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaExtractor_setDataSourceFd }, - { "setDataSource", "(Landroid/media/DataSource;)V", + { "setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaExtractor_setDataSourceCallback }, { "getCachedDuration", "()J", diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp index 7226ef567cd1..393003d98462 100644 --- a/media/jni/android_media_MediaHTTPConnection.cpp +++ b/media/jni/android_media_MediaHTTPConnection.cpp @@ -134,7 +134,6 @@ static jobject android_media_MediaHTTPConnection_native_getIMemory( static jint android_media_MediaHTTPConnection_native_readAt( JNIEnv *env, jobject thiz, jlong offset, jint size) { sp<JMediaHTTPConnection> conn = getObject(env, thiz); - if (size > JMediaHTTPConnection::kBufferSize) { size = JMediaHTTPConnection::kBufferSize; } diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index c6fa3790d73a..59fb6d6f6859 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -30,6 +30,7 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "android_media_MediaDataSource.h" #include "android_media_Utils.h" #include "android_util_Binder.h" @@ -171,6 +172,23 @@ static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jo process_media_retriever_call(env, retriever->setDataSource(fd, offset, length), "java/lang/RuntimeException", "setDataSource failed"); } +static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource) +{ + ALOGV("setDataSourceCallback"); + MediaMetadataRetriever* retriever = getRetriever(env, thiz); + if (retriever == 0) { + jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); + return; + } + if (dataSource == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + sp<IDataSource> callbackDataSource = new JMediaDataSource(env, dataSource); + process_media_retriever_call(env, retriever->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed"); +} + template<typename T> static void rotate0(T* dst, const T* src, size_t width, size_t height) { @@ -457,6 +475,7 @@ static JNINativeMethod nativeMethods[] = { }, {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD}, + {"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback}, {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime}, {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata}, {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture}, diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 3e4171663de4..c2472201c751 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -36,6 +36,7 @@ #include "utils/Errors.h" // for status_t #include "utils/KeyedVector.h" #include "utils/String8.h" +#include "android_media_MediaDataSource.h" #include "android_media_Utils.h" #include "android_os_Parcel.h" @@ -251,6 +252,23 @@ android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fil process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." ); } +static void +android_media_MediaPlayer_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource) +{ + sp<MediaPlayer> mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + + if (dataSource == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + sp<IDataSource> callbackDataSource = new JMediaDataSource(env, dataSource); + process_media_player_call(env, thiz, mp->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed." ); +} + static sp<IGraphicBufferProducer> getVideoSurfaceTexture(JNIEnv* env, jobject thiz) { IGraphicBufferProducer * const p = (IGraphicBufferProducer*)env->GetLongField(thiz, fields.surface_texture); @@ -871,7 +889,8 @@ static JNINativeMethod gMethods[] = { (void *)android_media_MediaPlayer_setDataSourceAndHeaders }, - {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, + {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD}, + {"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback }, {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface}, {"_prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync}, diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp index b96c733f88f8..e167f836e844 100644 --- a/media/jni/android_media_MediaSync.cpp +++ b/media/jni/android_media_MediaSync.cpp @@ -71,8 +71,8 @@ status_t JMediaSync::createInputSurface( return mSync->createInputSurface(bufferProducer); } -void JMediaSync::setPlaybackRate(float rate) { - mSync->setPlaybackRate(rate); +status_t JMediaSync::setPlaybackRate(float rate) { + return mSync->setPlaybackRate(rate); } sp<const MediaClock> JMediaSync::getMediaClock() { @@ -115,15 +115,23 @@ static void android_media_MediaSync_release(JNIEnv *env, jobject thiz) { static void throwExceptionAsNecessary( JNIEnv *env, status_t err, const char *msg = NULL) { switch (err) { - case INVALID_OPERATION: - jniThrowException(env, "java/lang/IllegalStateException", msg); + case NO_ERROR: break; case BAD_VALUE: jniThrowException(env, "java/lang/IllegalArgumentException", msg); break; + case NO_INIT: + case INVALID_OPERATION: default: + if (err > 0) { + break; + } + AString msgWithErrorCode(msg); + msgWithErrorCode.append(" error:"); + msgWithErrorCode.append(err); + jniThrowException(env, "java/lang/IllegalStateException", msgWithErrorCode.c_str()); break; } } @@ -295,7 +303,11 @@ static void android_media_MediaSync_native_setPlaybackRate( return; } - sync->setPlaybackRate(rate); + status_t err = sync->setPlaybackRate(rate); + if (err != NO_ERROR) { + throwExceptionAsNecessary(env, err); + return; + } } static void android_media_MediaSync_native_finalize(JNIEnv *env, jobject thiz) { diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h index 976a456be6ff..9e5de7e88090 100644 --- a/media/jni/android_media_MediaSync.h +++ b/media/jni/android_media_MediaSync.h @@ -39,7 +39,7 @@ struct JMediaSync : public RefBase { status_t updateQueuedAudioData(int sizeInBytes, int64_t presentationTimeUs); - void setPlaybackRate(float rate); + status_t setPlaybackRate(float rate); sp<const MediaClock> getMediaClock(); diff --git a/obex/Android.mk b/obex/Android.mk index fbfe9bead7e9..e7c1fd34dd74 100644 --- a/obex/Android.mk +++ b/obex/Android.mk @@ -7,3 +7,14 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_MODULE:= javax.obex include $(BUILD_JAVA_LIBRARY) + + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE:= javax.obexstatic + +include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index 75278b5fd39a..cc20d391ab75 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2014 The Android Open Source Project + * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -40,6 +41,8 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; +import android.util.Log; + /** * This class implements the <code>Operation</code> interface. It will read and * write data via puts and gets. @@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream; */ public final class ClientOperation implements Operation, BaseStream { + private static final String TAG = "ClientOperation"; + + private static final boolean V = ObexHelper.VDBG; + private ClientSession mParent; private boolean mInputOpen; @@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream { private boolean mEndOfBodySent; + private boolean mSendBodyHeader = true; + // A latch - when triggered, there is not way back ;-) + private boolean mSrmActive = false; + + // Assume SRM disabled - until support is confirmed + // by the server + private boolean mSrmEnabled = false; + // keep waiting until final-bit is received in request + // to handle the case where the SRM enable header is in + // a different OBEX packet than the SRMP header. + private boolean mSrmWaitingForRemote = true; + + /** * Creates new OperationImpl to read and write data to a server * @param maxSize the maximum packet size @@ -164,7 +184,7 @@ public final class ClientOperation implements Operation, BaseStream { * Since we are not sending any headers or returning any headers then * we just need to write and read the same bytes */ - mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null, false); if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) { throw new IOException("Invalid response code from server"); @@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream { try { return (String)mReplyHeader.getHeader(HeaderSet.TYPE); } catch (IOException e) { + if(V) Log.d(TAG, "Exception occured - returning null",e); return null; } } @@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream { return temp.longValue(); } } catch (IOException e) { + if(V) Log.d(TAG,"Exception occured - returning -1",e); return -1; } } @@ -408,7 +430,9 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * Sends a request to the client of the specified type + * Sends a request to the client of the specified type. + * This function will enable SRM and set SRM active if the server + * response allows this. * @param opCode the request code to send to the client * @return <code>true</code> if there is more data to send; * <code>false</code> if there is no more data to send @@ -431,13 +455,16 @@ public final class ClientOperation implements Operation, BaseStream { * length, but it is a waste of resources if we can't send much of * the body. */ - if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) { + final int MINIMUM_BODY_LENGTH = 3; + if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length + MINIMUM_BODY_LENGTH) + > mMaxPacketSize) { int end = 0; int start = 0; // split & send the headerArray in multiple packets. while (end != headerArray.length) { //split the headerArray + end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize - ObexHelper.BASE_PACKET_LENGTH); // can not split @@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream { byte[] sendHeader = new byte[end - start]; System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); - if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput, false)) { return false; } @@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream { start = end; } + // Enable SRM if it should be enabled + checkForSrm(); + if (bodyLength > 0) { return true; } else { return false; } } else { + /* All headers will fit into a single package */ + if(mSendBodyHeader == false) { + /* As we are not to send any body data, set the FINAL_BIT */ + opCode |= ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK; + } out.write(headerArray); } @@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream { * (End of Body) otherwise, we need to send 0x48 (Body) */ if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent) - && ((opCode & 0x80) != 0)) { - out.write(0x49); + && ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) { + out.write(HeaderSet.END_OF_BODY); mEndOfBodySent = true; } else { - out.write(0x48); + out.write(HeaderSet.BODY); } bodyLength += 3; @@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream { if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) { // only 0x82 or 0x83 can send 0x49 - if ((opCode & 0x80) == 0) { - out.write(0x48); + if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) { + out.write(HeaderSet.BODY); } else { - out.write(0x49); + out.write(HeaderSet.END_OF_BODY); mEndOfBodySent = true; - } bodyLength = 3; @@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream { } if (out.size() == 0) { - if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) { return false; } + // Enable SRM if it should be enabled + checkForSrm(); return returnValue; } if ((out.size() > 0) - && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) { + && (!mParent.sendRequest(opCode, out.toByteArray(), + mReplyHeader, mPrivateInput, mSrmActive))) { return false; } + // Enable SRM if it should be enabled + checkForSrm(); // send all of the output data in 0x48, // send 0x49 with empty body @@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream { return returnValue; } + private void checkForSrm() throws IOException { + Byte srmMode = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if(mParent.isSrmSupported() == true && srmMode != null + && srmMode == ObexHelper.OBEX_SRM_ENABLE) { + mSrmEnabled = true; + } + /** + * Call this only when a complete obex packet have been received. + * (This is not optimal, but the current design is not really suited to + * the way SRM is specified.) + * The BT usage of SRM is not really safe - it assumes that the SRMP will fit + * into every OBEX packet, hence if another header occupies the entire packet, + * the scheme will not work - unlikely though. + */ + if(mSrmEnabled) { + mSrmWaitingForRemote = false; + Byte srmp = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) { + mSrmWaitingForRemote = true; + // Clear the wait header, as the absence of the header in the next packet + // indicates don't wait anymore. + mReplyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } + if((mSrmWaitingForRemote == false) && (mSrmEnabled == true)) { + mSrmActive = true; + } + } + /** * This method starts the processing thread results. It will send the * initial request. If the response takes more then one packet, a thread @@ -564,40 +632,35 @@ public final class ClientOperation implements Operation, BaseStream { if (mGetOperation) { if (!mOperationDone) { - if (!mGetFinalFlag) { - mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; - while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x03); - } - - if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); - } - if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { - mOperationDone = true; - } - } else { - more = sendRequest(0x83); - - if (more) { - throw new IOException("FINAL_GET forced but data did not fit into single packet!"); - } - + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(ObexHelper.OBEX_OPCODE_GET); + } + // For GET we need to loop until all headers have been sent, + // And then we wait for the first continue package with the + // reply. + if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); + } + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; + } else { + checkForSrm(); } } } else { - + // PUT operation if (!mOperationDone) { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x02); - + more = sendRequest(ObexHelper.OBEX_OPCODE_PUT); } } if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); } if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { @@ -617,15 +680,21 @@ public final class ClientOperation implements Operation, BaseStream { public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException { + // One path to the first put operation - the other one does not need to + // handle SRM, as all will fit into one packet. + if (mGetOperation) { if ((inStream) && (!mOperationDone)) { // to deal with inputstream in get operation - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); /* * Determine if that was not the last packet in the operation */ if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; + } else { + checkForSrm(); } return true; @@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream { if (mPrivateInput == null) { mPrivateInput = new PrivateInputStream(this); } - - if (!mGetFinalFlag) { - sendRequest(0x03); - } else { - sendRequest(0x83); - - if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { - mOperationDone = true; - } - } + sendRequest(ObexHelper.OBEX_OPCODE_GET); return true; } else if (mOperationDone) { @@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream { } } else { + // PUT operation if ((!inStream) && (!mOperationDone)) { // to deal with outputstream in put operation if (mReplyHeader.responseCode == -1) { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; } - sendRequest(0x02); + sendRequest(ObexHelper.OBEX_OPCODE_PUT); return true; } else if ((inStream) && (!mOperationDone)) { // How to deal with inputstream in put operation ? @@ -696,7 +757,7 @@ public final class ClientOperation implements Operation, BaseStream { } while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x02); + more = sendRequest(ObexHelper.OBEX_OPCODE_PUT); } /* @@ -706,7 +767,7 @@ public final class ClientOperation implements Operation, BaseStream { */ while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - sendRequest(0x82); + sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL); } mOperationDone = true; } else if ((inStream) && (mOperationDone)) { @@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream { } while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - if (!sendRequest(0x83)) { + if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) { break; } } while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null, + mReplyHeader, mPrivateInput, false); + // Regardless of the SRM state, wait for the response. } mOperationDone = true; } else if ((!inStream) && (!mOperationDone)) { @@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x03); + more = sendRequest(ObexHelper.OBEX_OPCODE_GET); } - sendRequest(0x83); + sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL); // parent.sendRequest(0x83, null, replyHeaders, privateInput); if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; @@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream { } public void noBodyHeader(){ + mSendBodyHeader = false; } } diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java index 27d8976d0702..272a920754f5 100644 --- a/obex/javax/obex/ClientSession.java +++ b/obex/javax/obex/ClientSession.java @@ -1,4 +1,6 @@ /* + * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -37,12 +39,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import android.util.Log; + /** * This class in an implementation of the OBEX ClientSession. * @hide */ public final class ClientSession extends ObexSession { + private static final String TAG = "ClientSession"; + private boolean mOpen; // Determines if an OBEX layer connection has been established @@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession { private byte[] mConnectionId = null; /* - * The max Packet size must be at least 256 according to the OBEX + * The max Packet size must be at least 255 according to the OBEX * specification. */ - private int maxPacketSize = 256; + private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE; private boolean mRequestActive; @@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession { private final OutputStream mOutput; + private final boolean mLocalSrmSupported; + + private final ObexTransport mTransport; + public ClientSession(final ObexTransport trans) throws IOException { mInput = trans.openInputStream(); mOutput = trans.openOutputStream(); mOpen = true; mRequestActive = false; + mLocalSrmSupported = trans.isSrmSupported(); + mTransport = trans; + } + + /** + * Create a ClientSession + * @param trans The transport to use for OBEX transactions + * @param supportsSrm True if Single Response Mode should be used e.g. if the + * supplied transport is a TCP or l2cap channel. + * @throws IOException if it occurs while opening the transport streams. + */ + public ClientSession(final ObexTransport trans, final boolean supportsSrm) throws IOException { + mInput = trans.openInputStream(); + mOutput = trans.openOutputStream(); + mOpen = true; + mRequestActive = false; + mLocalSrmSupported = supportsSrm; + mTransport = trans; } public HeaderSet connect(final HeaderSet header) throws IOException { @@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession { * Byte 7 to n: headers */ byte[] requestPacket = new byte[totalLength]; + int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport); // We just need to start at byte 3 since the sendRequest() method will // handle the length and 0x80. requestPacket[0] = (byte)0x10; requestPacket[1] = (byte)0x00; - requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); - requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + requestPacket[2] = (byte)(maxRxPacketSize >> 8); + requestPacket[3] = (byte)(maxRxPacketSize & 0xFF); if (head != null) { System.arraycopy(head, 0, requestPacket, 4, head.length); } - // check with local max packet size + // Since we are not yet connected, the peer max packet size is unknown, + // hence we are only guaranteed the server will use the first 7 bytes. if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { - throw new IOException("Packet size exceeds max packet size"); + throw new IOException("Packet size exceeds max packet size for connect"); } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null, false); /* * Read the response from the OBEX server. @@ -158,7 +188,18 @@ public final class ClientSession extends ObexSession { System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); } - return new ClientOperation(maxPacketSize, this, head, true); + if(mLocalSrmSupported) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE); + /* TODO: Consider creating an interface to get the wait state. + * On an android system, I cannot see when this is to be used. + * except perhaps if we are to wait for user accept on a push message. + if(getLocalWaitState()) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT); + } + */ + } + + return new ClientOperation(mMaxTxPacketSize, this, head, true); } /** @@ -202,7 +243,7 @@ public final class ClientSession extends ObexSession { } head = ObexHelper.createHeader(header, false); - if ((head.length + 3) > maxPacketSize) { + if ((head.length + 3) > mMaxTxPacketSize) { throw new IOException("Packet size exceeds max packet size"); } } else { @@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession { } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null, false); /* * An OBEX DISCONNECT reply from the server: @@ -269,7 +310,16 @@ public final class ClientSession extends ObexSession { System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); } - return new ClientOperation(maxPacketSize, this, head, false); + if(mLocalSrmSupported) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE); + /* TODO: Consider creating an interface to get the wait state. + * On an android system, I cannot see when this is to be used. + if(getLocalWaitState()) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT); + } + */ + } + return new ClientOperation(mMaxTxPacketSize, this, head, false); } public void setAuthenticator(Authenticator auth) throws IOException { @@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession { head = ObexHelper.createHeader(headset, false); totalLength += head.length; - if (totalLength > maxPacketSize) { + if (totalLength > mMaxTxPacketSize) { throw new IOException("Packet size exceeds max packet size"); } @@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession { } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null, false); /* * An OBEX SETPATH reply from the server: @@ -400,20 +450,40 @@ public final class ClientSession extends ObexSession { * @param head the headers to send to the client * @param header the header object to update with the response * @param privateInput the input stream used by the Operation object; null - * if this is called on a CONNECT, SETPATH or DISCONNECT return + * if this is called on a CONNECT, SETPATH or DISCONNECT + * @return * <code>true</code> if the operation completed successfully; * <code>false</code> if an authentication response failed to pass * @throws IOException if an IO error occurs */ public boolean sendRequest(int opCode, byte[] head, HeaderSet header, - PrivateInputStream privateInput) throws IOException { + PrivateInputStream privateInput, boolean srmActive) throws IOException { //check header length with local max size if (head != null) { if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + // TODO: This is an implementation limit - not a specification requirement. throw new IOException("header too large "); } } + boolean skipSend = false; + boolean skipReceive = false; + if (srmActive == true) { + if (opCode == ObexHelper.OBEX_OPCODE_PUT) { + // we are in the middle of a SRM PUT operation, don't expect a continue. + skipReceive = true; + } else if (opCode == ObexHelper.OBEX_OPCODE_GET) { + // We are still sending the get request, send, but don't expect continue + // until the request is transfered (the final bit is set) + skipReceive = true; + } else if (opCode == ObexHelper.OBEX_OPCODE_GET_FINAL) { + // All done sending the request, expect data from the server, without + // sending continue. + skipSend = true; + } + + } + int bytesReceived; ByteArrayOutputStream out = new ByteArrayOutputStream(); out.write((byte)opCode); @@ -428,86 +498,105 @@ public final class ClientSession extends ObexSession { out.write(head); } - // Write the request to the output stream and flush the stream - mOutput.write(out.toByteArray()); - mOutput.flush(); + if (!skipSend) { + // Write the request to the output stream and flush the stream + mOutput.write(out.toByteArray()); + // TODO: is this really needed? if this flush is implemented + // correctly, we will get a gap between each obex packet. + // which is kind of the idea behind SRM to avoid. + // Consider offloading to another thread (async action) + mOutput.flush(); + } - header.responseCode = mInput.read(); + if (!skipReceive) { + header.responseCode = mInput.read(); - int length = ((mInput.read() << 8) | (mInput.read())); + int length = ((mInput.read() << 8) | (mInput.read())); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { - throw new IOException("Packet received exceeds packet size limit"); - } - if (length > ObexHelper.BASE_PACKET_LENGTH) { - byte[] data = null; - if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) { - @SuppressWarnings("unused") - int version = mInput.read(); - @SuppressWarnings("unused") - int flags = mInput.read(); - maxPacketSize = (mInput.read() << 8) + mInput.read(); + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { + throw new IOException("Packet received exceeds packet size limit"); + } + if (length > ObexHelper.BASE_PACKET_LENGTH) { + byte[] data = null; + if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) { + @SuppressWarnings("unused") + int version = mInput.read(); + @SuppressWarnings("unused") + int flags = mInput.read(); + mMaxTxPacketSize = (mInput.read() << 8) + mInput.read(); + + //check with local max size + if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) { + mMaxTxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE; + } - //check with local max size - if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) { - maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE; - } + // check with transport maximum size + if(mMaxTxPacketSize > ObexHelper.getMaxTxPacketSize(mTransport)) { + // To increase this size, increase the buffer size in L2CAP layer + // in Bluedroid. + Log.w(TAG, "An OBEX packet size of " + mMaxTxPacketSize + "was" + + " requested. Transport only allows: " + + ObexHelper.getMaxTxPacketSize(mTransport) + + " Lowering limit to this value."); + mMaxTxPacketSize = ObexHelper.getMaxTxPacketSize(mTransport); + } - if (length > 7) { - data = new byte[length - 7]; + if (length > 7) { + data = new byte[length - 7]; - bytesReceived = mInput.read(data); - while (bytesReceived != (length - 7)) { - bytesReceived += mInput.read(data, bytesReceived, data.length - - bytesReceived); + bytesReceived = mInput.read(data); + while (bytesReceived != (length - 7)) { + bytesReceived += mInput.read(data, bytesReceived, data.length + - bytesReceived); + } + } else { + return true; } } else { - return true; - } - } else { - data = new byte[length - 3]; - bytesReceived = mInput.read(data); + data = new byte[length - 3]; + bytesReceived = mInput.read(data); - while (bytesReceived != (length - 3)) { - bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); - } - if (opCode == ObexHelper.OBEX_OPCODE_ABORT) { - return true; + while (bytesReceived != (length - 3)) { + bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); + } + if (opCode == ObexHelper.OBEX_OPCODE_ABORT) { + return true; + } } - } - byte[] body = ObexHelper.updateHeaderSet(header, data); - if ((privateInput != null) && (body != null)) { - privateInput.writeBytes(body, 1); - } + byte[] body = ObexHelper.updateHeaderSet(header, data); + if ((privateInput != null) && (body != null)) { + privateInput.writeBytes(body, 1); + } - if (header.mConnectionID != null) { - mConnectionId = new byte[4]; - System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4); - } + if (header.mConnectionID != null) { + mConnectionId = new byte[4]; + System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4); + } - if (header.mAuthResp != null) { - if (!handleAuthResp(header.mAuthResp)) { - setRequestInactive(); - throw new IOException("Authentication Failed"); + if (header.mAuthResp != null) { + if (!handleAuthResp(header.mAuthResp)) { + setRequestInactive(); + throw new IOException("Authentication Failed"); + } } - } - if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) - && (header.mAuthChall != null)) { + if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) + && (header.mAuthChall != null)) { - if (handleAuthChall(header)) { - out.write((byte)HeaderSet.AUTH_RESPONSE); - out.write((byte)((header.mAuthResp.length + 3) >> 8)); - out.write((byte)(header.mAuthResp.length + 3)); - out.write(header.mAuthResp); - header.mAuthChall = null; - header.mAuthResp = null; + if (handleAuthChall(header)) { + out.write((byte)HeaderSet.AUTH_RESPONSE); + out.write((byte)((header.mAuthResp.length + 3) >> 8)); + out.write((byte)(header.mAuthResp.length + 3)); + out.write(header.mAuthResp); + header.mAuthChall = null; + header.mAuthResp = null; - byte[] sendHeaders = new byte[out.size() - 3]; - System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); + byte[] sendHeaders = new byte[out.size() - 3]; + System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); - return sendRequest(opCode, sendHeaders, header, privateInput); + return sendRequest(opCode, sendHeaders, header, privateInput, false); + } } } } @@ -520,4 +609,8 @@ public final class ClientSession extends ObexSession { mInput.close(); mOutput.close(); } + + public boolean isSrmSupported() { + return mLocalSrmSupported; + } } diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java index 51b560a5060b..35fe1863e1fb 100644 --- a/obex/javax/obex/HeaderSet.java +++ b/obex/javax/obex/HeaderSet.java @@ -40,7 +40,7 @@ import java.security.SecureRandom; /** * This class implements the javax.obex.HeaderSet interface for OBEX over - * RFCOMM. + * RFCOMM or OBEX over l2cap. * @hide */ public final class HeaderSet { @@ -178,6 +178,22 @@ public final class HeaderSet { */ public static final int OBJECT_CLASS = 0x4F; + /** + * Represents the OBEX Single Response Mode (SRM). This header is used + * for Single response mode, introduced in OBEX 1.5. + * <P> + * The value of <code>SINGLE_RESPONSE_MODE</code> is 0x97 (151). + */ + public static final int SINGLE_RESPONSE_MODE = 0x97; + + /** + * Represents the OBEX Single Response Mode Parameters. This header is used + * for Single response mode, introduced in OBEX 1.5. + * <P> + * The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> is 0x98 (152). + */ + public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98; + private Long mCount; // 4 byte unsigned integer private String mName; // null terminated Unicode text string @@ -204,7 +220,7 @@ public final class HeaderSet { private byte[] mObjectClass; // byte sequence - private String[] mUnicodeUserDefined; //null terminated unicode string + private String[] mUnicodeUserDefined; // null terminated unicode string private byte[][] mSequenceUserDefined; // byte sequence user defined @@ -212,7 +228,12 @@ public final class HeaderSet { private Long[] mIntegerUserDefined; // 4 byte unsigned integer - private final SecureRandom mRandom; + private SecureRandom mRandom = null; + + private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM + + private Byte mSrmParam; // byte representing the SRM parameters - only "wait" + // is supported by Bluetooth /*package*/ byte[] nonce; @@ -234,7 +255,6 @@ public final class HeaderSet { mByteUserDefined = new Byte[16]; mIntegerUserDefined = new Long[16]; responseCode = -1; - mRandom = new SecureRandom(); } /** @@ -393,6 +413,30 @@ public final class HeaderSet { } } break; + case SINGLE_RESPONSE_MODE: + if (headerValue == null) { + mSingleResponseMode = null; + } else { + if (!(headerValue instanceof Byte)) { + throw new IllegalArgumentException( + "Single Response Mode must be a Byte"); + } else { + mSingleResponseMode = (Byte)headerValue; + } + } + break; + case SINGLE_RESPONSE_MODE_PARAMETER: + if (headerValue == null) { + mSrmParam = null; + } else { + if (!(headerValue instanceof Byte)) { + throw new IllegalArgumentException( + "Single Response Mode Parameter must be a Byte"); + } else { + mSrmParam = (Byte)headerValue; + } + } + break; default: // Verify that it was not a Unicode String user Defined if ((headerID >= 0x30) && (headerID <= 0x3F)) { @@ -493,6 +537,10 @@ public final class HeaderSet { return mObjectClass; case APPLICATION_PARAMETER: return mAppParam; + case SINGLE_RESPONSE_MODE: + return mSingleResponseMode; + case SINGLE_RESPONSE_MODE_PARAMETER: + return mSrmParam; default: // Verify that it was not a Unicode String user Defined if ((headerID >= 0x30) && (headerID <= 0x3F)) { @@ -564,6 +612,12 @@ public final class HeaderSet { if (mObjectClass != null) { out.write(OBJECT_CLASS); } + if(mSingleResponseMode != null) { + out.write(SINGLE_RESPONSE_MODE); + } + if(mSrmParam != null) { + out.write(SINGLE_RESPONSE_MODE_PARAMETER); + } for (int i = 0x30; i < 0x40; i++) { if (mUnicodeUserDefined[i - 0x30] != null) { @@ -625,6 +679,9 @@ public final class HeaderSet { throws IOException { nonce = new byte[16]; + if(mRandom == null) { + mRandom = new SecureRandom(); + } for (int i = 0; i < 16; i++) { nonce[i] = (byte)mRandom.nextInt(); } diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java index 0a06709548fe..fa50943343e9 100644 --- a/obex/javax/obex/ObexHelper.java +++ b/obex/javax/obex/ObexHelper.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -42,12 +43,16 @@ import java.util.Calendar; import java.util.Date; import java.util.TimeZone; +import android.util.Log; + /** * This class defines a set of helper methods for the implementation of Obex. * @hide */ public final class ObexHelper { + private static final String TAG = "ObexHelper"; + public static final boolean VDBG = false; /** * Defines the basic packet length used by OBEX. Every OBEX packet has the * same basic format:<BR> @@ -65,18 +70,24 @@ public final class ObexHelper { * should be the Max incoming MTU minus TODO: L2CAP package headers and * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO: * LocalDevice.getProperty(). + * NOTE: This value must be larger than or equal to the L2CAP SDU */ /* * android note set as 0xFFFE to match remote MPS */ public static final int MAX_PACKET_SIZE_INT = 0xFFFE; + // The minimum allowed max packet size is 255 according to the OBEX specification + public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255; + /** * Temporary workaround to be able to push files to Windows 7. * TODO: Should be removed as soon as Microsoft updates their driver. */ public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00; + public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80; + public static final int OBEX_OPCODE_CONNECT = 0x80; public static final int OBEX_OPCODE_DISCONNECT = 0x81; @@ -119,6 +130,12 @@ public final class ObexHelper { public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF; + public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable + public static final byte OBEX_SRM_DISABLE = 0x00; + public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now + + public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT + /** * Updates the HeaderSet with the headers received in the byte array * provided. Invalid headers are ignored. @@ -314,7 +331,7 @@ public final class ObexHelper { } } catch (Exception e) { // Not a valid header so ignore - throw new IOException("Header was not formatted properly"); + throw new IOException("Header was not formatted properly", e); } index += 4; break; @@ -322,7 +339,7 @@ public final class ObexHelper { } } catch (IOException e) { - throw new IOException("Header was not formatted properly"); + throw new IOException("Header was not formatted properly", e); } return body; @@ -672,6 +689,33 @@ public final class ObexHelper { } } + // TODO: + // If the SRM and SRMP header is in use, they must be send in the same OBEX packet + // But the current structure of the obex code cannot handle this, and therefore + // it makes sense to put them in the tail of the headers, since we then reduce the + // chance of enabling SRM to soon. The down side is that SRM cannot be used while + // transferring non-body headers + + // Add the SRM header + byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if (byteHeader != null) { + out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null); + } + } + + // Add the SRM parameter header + byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if (byteHeader != null) { + out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } + } catch (IOException e) { } finally { result = out.toByteArray(); @@ -702,6 +746,8 @@ public final class ObexHelper { int index = start; int length = 0; + // TODO: Ensure SRM and SRMP headers are not split into two OBEX packets + while ((fullLength < maxSize) && (index < headerArray.length)) { int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); lastLength = fullLength; @@ -1008,4 +1054,39 @@ public final class ObexHelper { return authChall; } + + /** + * Return the maximum allowed OBEX packet to transmit. + * OBEX packets transmitted must be smaller than this value. + * @param transport Reference to the ObexTransport in use. + * @return the maximum allowed OBEX packet to transmit + */ + public static int getMaxTxPacketSize(ObexTransport transport) { + int size = transport.getMaxTransmitPacketSize(); + return validateMaxPacketSize(size); + } + + /** + * Return the maximum allowed OBEX packet to receive - used in OBEX connect. + * @param transport + * @return he maximum allowed OBEX packet to receive + */ + public static int getMaxRxPacketSize(ObexTransport transport) { + int size = transport.getMaxReceivePacketSize(); + return validateMaxPacketSize(size); + } + + private static int validateMaxPacketSize(int size) { + if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG, + "The packet size supported for the connection (" + size + ") is larger" + + " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT); + if(size != -1) { + if(size < LOWER_LIMIT_MAX_PACKET_SIZE) { + throw new IllegalArgumentException(size + " is less that the lower limit: " + + LOWER_LIMIT_MAX_PACKET_SIZE); + } + return size; + } + return MAX_PACKET_SIZE_INT; + } } diff --git a/obex/javax/obex/ObexPacket.java b/obex/javax/obex/ObexPacket.java new file mode 100644 index 000000000000..bb6c96e5b4a5 --- /dev/null +++ b/obex/javax/obex/ObexPacket.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 The Android Open Source Project + * Copyright (c) 2015 Samsung LSI + * + * 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 javax.obex; + +import java.io.IOException; +import java.io.InputStream; + +public class ObexPacket { + public int mHeaderId; + public int mLength; + public byte[] mPayload = null; + + private ObexPacket(int headerId, int length) { + mHeaderId = headerId; + mLength = length; + } + + /** + * Create a complete OBEX packet by reading data from an InputStream. + * @param is the input stream to read from. + * @return the OBEX packet read. + * @throws IOException if an IO exception occurs during read. + */ + public static ObexPacket read(InputStream is) throws IOException { + int headerId = is.read(); + return read(headerId, is); + } + + /** + * Read the remainder of an OBEX packet, with a specified headerId. + * @param headerId the headerId already read from the stream. + * @param is the stream to read from, assuming 1 byte have already been read. + * @return the OBEX packet read. + * @throws IOException + */ + public static ObexPacket read(int headerId, InputStream is) throws IOException { + // Read the 2 byte length field from the stream + int length = is.read(); + length = (length << 8) + is.read(); + + ObexPacket newPacket = new ObexPacket(headerId, length); + + int bytesReceived; + byte[] temp = null; + if (length > 3) { + // First three bytes already read, compensating for this + temp = new byte[length - 3]; + bytesReceived = is.read(temp); + while (bytesReceived != temp.length) { + bytesReceived += is.read(temp, bytesReceived, temp.length - bytesReceived); + } + } + newPacket.mPayload = temp; + return newPacket; + } +} diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java index a7daeb533e16..542b9c8b2492 100644 --- a/obex/javax/obex/ObexSession.java +++ b/obex/javax/obex/ObexSession.java @@ -34,6 +34,8 @@ package javax.obex; import java.io.IOException; +import android.util.Log; + /** * The <code>ObexSession</code> interface characterizes the term * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which @@ -47,6 +49,9 @@ import java.io.IOException; */ public class ObexSession { + private static final String TAG = "ObexSession"; + private static final boolean V = ObexHelper.VDBG; + protected Authenticator mAuthenticator; protected byte[] mChallengeDigest; @@ -125,6 +130,7 @@ public class ObexSession { result = mAuthenticator .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); } catch (Exception e) { + if (V) Log.d(TAG, "Exception occured - returning false", e); return false; } diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java index 445e2675b212..a5a75f55f553 100644 --- a/obex/javax/obex/ObexTransport.java +++ b/obex/javax/obex/ObexTransport.java @@ -73,4 +73,39 @@ public interface ObexTransport { DataOutputStream openDataOutputStream() throws IOException; + /** + * Must return the maximum allowed OBEX packet that can be sent over + * the transport. For L2CAP this will be the Max SDU reported by the + * peer device. + * The returned value will be used to set the outgoing OBEX packet + * size. Therefore this value shall not change. + * For RFCOMM or other transport types where the OBEX packets size + * is unrelated to the transport packet size, return -1; + * @return the maximum allowed OBEX packet that can be send over + * the transport. Or -1 in case of don't care. + */ + int getMaxTransmitPacketSize(); + + /** + * Must return the maximum allowed OBEX packet that can be received over + * the transport. For L2CAP this will be the Max SDU configured for the + * L2CAP channel. + * The returned value will be used to validate the incoming packet size + * values. + * For RFCOMM or other transport types where the OBEX packets size + * is unrelated to the transport packet size, return -1; + * @return the maximum allowed OBEX packet that can be send over + * the transport. Or -1 in case of don't care. + */ + int getMaxReceivePacketSize(); + + /** + * Shall return true if the transport in use supports SRM. + * @return + * <code>true</code> if SRM operation is supported, and is to be enabled. + * <code>false</code> if SRM operations are not supported, or should not be used. + */ + boolean isSrmSupported(); + + } diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java index fc441e0e165c..56a675acf082 100644 --- a/obex/javax/obex/ServerOperation.java +++ b/obex/javax/obex/ServerOperation.java @@ -1,4 +1,5 @@ -/* +/* Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -39,6 +40,8 @@ import java.io.OutputStream; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; +import android.util.Log; + /** * This class implements the Operation interface for server side connections. * <P> @@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream; */ public final class ServerOperation implements Operation, BaseStream { + private static final String TAG = "ServerOperation"; + + private static final boolean V = ObexHelper.VDBG; // Verbose debugging + public boolean isAborted; public HeaderSet requestHeader; @@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream { private PrivateOutputStream mPrivateOutput; + private ObexTransport mTransport; + private boolean mPrivateOutputOpen; private String mExceptionString; @@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream { private boolean mHasBody; private boolean mSendBodyHeader = true; + // Assume SRM disabled - needs to be explicit + // enabled by client + private boolean mSrmEnabled = false; + // A latch - when triggered, there is not way back ;-) + private boolean mSrmActive = false; + // Set to true when a SRM enable response have been send + private boolean mSrmResponseSent = false; + // keep waiting until final-bit is received in request + // to handle the case where the SRM enable header is in + // a different OBEX packet than the SRMP header. + private boolean mSrmWaitingForRemote = true; + // Why should we wait? - currently not exposed to apps. + private boolean mSrmLocalWait = false; /** * Creates new ServerOperation @@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream { mRequestFinished = false; mPrivateOutputOpen = false; mHasBody = false; - int bytesReceived; + ObexPacket packet; + mTransport = p.getTransport(); /* * Determine if this is a PUT request */ - if ((request == 0x02) || (request == 0x82)) { + if ((request == ObexHelper.OBEX_OPCODE_PUT) || + (request == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { /* * It is a PUT request. */ @@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream { /* * Determine if the final bit is set */ - if ((request & 0x80) == 0) { + if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) { finalBitSet = false; } else { finalBitSet = true; mRequestFinished = true; } - } else if ((request == 0x03) || (request == 0x83)) { + } else if ((request == ObexHelper.OBEX_OPCODE_GET) || + (request == ObexHelper.OBEX_OPCODE_GET_FINAL)) { /* * It is a GET request. */ @@ -145,71 +170,32 @@ public final class ServerOperation implements Operation, BaseStream { // For Get request, final bit set is decided by server side logic finalBitSet = false; - if (request == 0x83) { + if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) { mRequestFinished = true; } } else { throw new IOException("ServerOperation can not handle such request"); } - int length = in.read(); - length = (length << 8) + in.read(); + packet = ObexPacket.read(request, mInput); /* * Determine if the packet length is larger than this device can receive */ - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) { mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); - throw new IOException("Packet received was too large"); + throw new IOException("Packet received was too large. Length: " + + packet.mLength + " maxLength: " + ObexHelper.getMaxRxPacketSize(mTransport)); } /* * Determine if any headers were sent in the initial request */ - if (length > 3) { - byte[] data = new byte[length - 3]; - bytesReceived = in.read(data); - - while (bytesReceived != data.length) { - bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); + if (packet.mLength > 3) { + if(!handleObexPacket(packet)) { + return; } - - byte[] body = ObexHelper.updateHeaderSet(requestHeader, data); - - if (body != null) { - mHasBody = true; - } - - if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { - mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID)); - } else { - mListener.setConnectionId(1); - } - - if (requestHeader.mAuthResp != null) { - if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { - mExceptionString = "Authentication Failed"; - mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); - mClosed = true; - requestHeader.mAuthResp = null; - return; - } - } - - if (requestHeader.mAuthChall != null) { - mParent.handleAuthChall(requestHeader); - // send the authResp to the client - replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; - System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, - replyHeader.mAuthResp.length); - requestHeader.mAuthResp = null; - requestHeader.mAuthChall = null; - - } - - if (body != null) { - mPrivateInput.writeBytes(body, 1); - } else { + if (!mHasBody) { while ((!mGetOperation) && (!finalBitSet)) { sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); if (mPrivateInput.available() > 0) { @@ -232,6 +218,100 @@ public final class ServerOperation implements Operation, BaseStream { } } + /** + * Parse headers and update member variables + * @param packet the received obex packet + * @return false for failing authentication - and a OBEX_HTTP_UNAUTHORIZED + * response have been send. Else true. + * @throws IOException + */ + private boolean handleObexPacket(ObexPacket packet) throws IOException { + byte[] body = updateRequestHeaders(packet); + + if (body != null) { + mHasBody = true; + } + if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { + mListener.setConnectionId(ObexHelper + .convertToLong(requestHeader.mConnectionID)); + } else { + mListener.setConnectionId(1); + } + + if (requestHeader.mAuthResp != null) { + if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { + mExceptionString = "Authentication Failed"; + mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + mClosed = true; + requestHeader.mAuthResp = null; + return false; + } + requestHeader.mAuthResp = null; + } + + if (requestHeader.mAuthChall != null) { + mParent.handleAuthChall(requestHeader); + // send the auhtResp to the client + replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; + System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, + replyHeader.mAuthResp.length); + requestHeader.mAuthResp = null; + requestHeader.mAuthChall = null; + } + + if (body != null) { + mPrivateInput.writeBytes(body, 1); + } + return true; + } + + /** + * Update the request header set, and sniff on SRM headers to update local state. + * @param data the OBEX packet data + * @return any bytes in a body/end-of-body header returned by {@link ObexHelper.updateHeaderSet} + * @throws IOException + */ + private byte[] updateRequestHeaders(ObexPacket packet) throws IOException { + byte[] body = null; + if (packet.mPayload != null) { + body = ObexHelper.updateHeaderSet(requestHeader, packet.mPayload); + } + Byte srmMode = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if(mTransport.isSrmSupported() && srmMode != null + && srmMode == ObexHelper.OBEX_SRM_ENABLE) { + mSrmEnabled = true; + if(V) Log.d(TAG,"SRM is now ENABLED (but not active) for this operation"); + } + checkForSrmWait(packet.mHeaderId); + if((!mSrmWaitingForRemote) && (mSrmEnabled)) { + if(V) Log.d(TAG,"SRM is now ACTIVE for this operation"); + mSrmActive = true; + } + return body; + } + + /** + * Call this only when a complete request have been received. + * (This is not optimal, but the current design is not really suited to + * the way SRM is specified.) + */ + private void checkForSrmWait(int headerId){ + if (mSrmEnabled && (headerId == ObexHelper.OBEX_OPCODE_GET + || headerId == ObexHelper.OBEX_OPCODE_GET_FINAL + || headerId == ObexHelper.OBEX_OPCODE_PUT)) { + try { + mSrmWaitingForRemote = false; + Byte srmp = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) { + mSrmWaitingForRemote = true; + // Clear the wait header, as the absents of the header when the final bit is set + // indicates don't wait. + requestHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } catch (IOException e) {if(V){Log.w(TAG,"Exception while extracting header",e);}} + } + } + public boolean isValidBody() { return mHasBody; } @@ -274,17 +354,19 @@ public final class ServerOperation implements Operation, BaseStream { /** * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it - * will wait for a response from the client before ending. + * will wait for a response from the client before ending unless SRM is active. * @param type the response code to send back to the client * @return <code>true</code> if the final bit was not set on the reply; * <code>false</code> if no reply was received because the operation - * ended, an abort was received, or the final bit was set in the - * reply + * ended, an abort was received, the final bit was set in the + * reply or SRM is active. * @throws IOException if an IO error occurs */ public synchronized boolean sendReply(int type) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - int bytesReceived; + boolean skipSend = false; + boolean skipReceive = false; + boolean srmRespSendPending = false; long id = mListener.getConnectionId(); if (id == -1) { @@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream { replyHeader.mConnectionID = ObexHelper.convertToByteArray(id); } - byte[] headerArray = ObexHelper.createHeader(replyHeader, true); + if(mSrmEnabled && !mSrmResponseSent) { + // As we are not ensured that the SRM enable is in the first OBEX packet + // We must check for each reply. + if(V)Log.v(TAG, "mSrmEnabled==true, sending SRM enable response."); + replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRM_ENABLE); + srmRespSendPending = true; + } + + if(mSrmEnabled && !mGetOperation && mSrmLocalWait) { + replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRMP_WAIT); + } + + byte[] headerArray = ObexHelper.createHeader(replyHeader, true); // This clears the headers int bodyLength = -1; int orginalBodyLength = -1; @@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream { finalBitSet = true; } + if(mSrmActive) { + if(!mGetOperation && type == ResponseCodes.OBEX_HTTP_CONTINUE && + mSrmResponseSent == true) { + // we are in the middle of a SRM PUT operation, don't send a continue. + skipSend = true; + } else if(mGetOperation && mRequestFinished == false && mSrmResponseSent == true) { + // We are still receiving the get request, receive, but don't send continue. + skipSend = true; + } else if(mGetOperation && mRequestFinished == true) { + // All done receiving the GET request, send data to the client, without + // expecting a continue. + skipReceive = true; + } + if(V)Log.v(TAG, "type==" + type + " skipSend==" + skipSend + + " skipReceive==" + skipReceive); + } + if(srmRespSendPending) { + if(V)Log.v(TAG, + "SRM Enabled (srmRespSendPending == true)- sending SRM Enable response"); + mSrmResponseSent = true; + } + if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) { if (bodyLength > 0) { /* @@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream { } if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { - if(mSendBodyHeader == true) { + if(mSendBodyHeader) { out.write(0x49); orginalBodyLength = 3; out.write((byte)(orginalBodyLength >> 8)); @@ -395,107 +511,66 @@ public final class ServerOperation implements Operation, BaseStream { } } - mResponseSize = 3; - mParent.sendResponse(type, out.toByteArray()); + if(skipSend == false) { + mResponseSize = 3; + mParent.sendResponse(type, out.toByteArray()); + } if (type == ResponseCodes.OBEX_HTTP_CONTINUE) { - int headerID = mInput.read(); - int length = mInput.read(); - length = (length << 8) + mInput.read(); - if ((headerID != ObexHelper.OBEX_OPCODE_PUT) - && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL) - && (headerID != ObexHelper.OBEX_OPCODE_GET) - && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) { - - if (length > 3) { - byte[] temp = new byte[length - 3]; - // First three bytes already read, compensating for this - bytesReceived = mInput.read(temp); - - while (bytesReceived != temp.length) { - bytesReceived += mInput.read(temp, bytesReceived, - temp.length - bytesReceived); - } - } - /* - * Determine if an ABORT was sent as the reply - */ - if (headerID == ObexHelper.OBEX_OPCODE_ABORT) { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); - mClosed = true; - isAborted = true; - mExceptionString = "Abort Received"; - throw new IOException("Abort Received"); - } else { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); - mClosed = true; - mExceptionString = "Bad Request Received"; - throw new IOException("Bad Request Received"); - } + if(mGetOperation && skipReceive) { + // Here we need to check for and handle abort (throw an exception). + // Any other signal received should be discarded silently (only on server side) + checkSrmRemoteAbort(); } else { - - if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { - finalBitSet = true; - } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) { - mRequestFinished = true; - } - - /* - * Determine if the packet length is larger then this device can receive - */ - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); - throw new IOException("Packet received was too large"); - } - - /* - * Determine if any headers were sent in the initial request - */ - if (length > 3) { - byte[] data = new byte[length - 3]; - bytesReceived = mInput.read(data); - - while (bytesReceived != data.length) { - bytesReceived += mInput.read(data, bytesReceived, data.length - - bytesReceived); - } - byte[] body = ObexHelper.updateHeaderSet(requestHeader, data); - if (body != null) { - mHasBody = true; - } - if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { - mListener.setConnectionId(ObexHelper - .convertToLong(requestHeader.mConnectionID)); + // Receive and handle data (only send reply if !skipSend) + // Read a complete OBEX Packet + ObexPacket packet = ObexPacket.read(mInput); + + int headerId = packet.mHeaderId; + if ((headerId != ObexHelper.OBEX_OPCODE_PUT) + && (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL) + && (headerId != ObexHelper.OBEX_OPCODE_GET) + && (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) { + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerId == ObexHelper.OBEX_OPCODE_ABORT) { + handleRemoteAbort(); } else { - mListener.setConnectionId(1); + // TODO:shall we send this if it occurs during SRM? Errata on the subject + mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + mClosed = true; + mExceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); } + } else { - if (requestHeader.mAuthResp != null) { - if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { - mExceptionString = "Authentication Failed"; - mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); - mClosed = true; - requestHeader.mAuthResp = null; - return false; - } - requestHeader.mAuthResp = null; + if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { + finalBitSet = true; + } else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) { + mRequestFinished = true; } - if (requestHeader.mAuthChall != null) { - mParent.handleAuthChall(requestHeader); - // send the auhtResp to the client - replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; - System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, - replyHeader.mAuthResp.length); - requestHeader.mAuthResp = null; - requestHeader.mAuthChall = null; + /* + * Determine if the packet length is larger than the negotiated packet size + */ + if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) { + mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); } - if (body != null) { - mPrivateInput.writeBytes(body, 1); + /* + * Determine if any headers were sent in the initial request + */ + if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) { + if(handleObexPacket(packet) == false) { + return false; + } } } + } return true; } else { @@ -504,6 +579,53 @@ public final class ServerOperation implements Operation, BaseStream { } /** + * This method will look for an abort from the peer during a SRM transfer. + * The function will not block if no data has been received from the remote device. + * If data have been received, the function will block while reading the incoming + * OBEX package. + * An Abort request will be handled, and cause an IOException("Abort Received"). + * Other messages will be discarded silently as per GOEP specification. + * @throws IOException if an abort request have been received. + * TODO: I think this is an error in the specification. If we discard other messages, + * the peer device will most likely stall, as it will not receive the expected + * response for the message... + * I'm not sure how to understand "Receipt of invalid or unexpected SRM or SRMP + * header values shall be ignored by the receiving device." + * If any signal is received during an active SRM transfer it is unexpected regardless + * whether or not it contains SRM/SRMP headers... + */ + private void checkSrmRemoteAbort() throws IOException { + if(mInput.available() > 0) { + ObexPacket packet = ObexPacket.read(mInput); + /* + * Determine if an ABORT was sent as the reply + */ + if (packet.mHeaderId == ObexHelper.OBEX_OPCODE_ABORT) { + handleRemoteAbort(); + } else { + // TODO: should we throw an exception here anyway? - don't see how to + // ignore SRM/SRMP headers without ignoring the complete signal + // (in this particular case). + Log.w(TAG, "Received unexpected request from client - discarding...\n" + + " headerId: " + packet.mHeaderId + " length: " + packet.mLength); + } + } + } + + private void handleRemoteAbort() throws IOException { + /* TODO: To increase the speed of the abort operation in SRM, we need + * to be able to flush the L2CAP queue for the PSM in use. + * This could be implemented by introducing a control + * message to be send over the socket, that in the abort case + * could carry a flush command. */ + mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + mClosed = true; + isAborted = true; + mExceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } + + /** * Sends an ABORT message to the server. By calling this method, the * corresponding input and output streams will be closed along with this * object. diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java index 08825729949e..09cbc2ca489f 100644 --- a/obex/javax/obex/ServerRequestHandler.java +++ b/obex/javax/obex/ServerRequestHandler.java @@ -275,4 +275,13 @@ public class ServerRequestHandler { */ public void onClose() { } + + /** + * Override to add Single Response Mode support - e.g. if the supplied + * transport is l2cap. + * @return True if SRM is supported, else False + */ + public boolean isSrmSupported() { + return false; + } } diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java index f1b9a0d6c4d6..acee5ddd5f8f 100644 --- a/obex/javax/obex/ServerSession.java +++ b/obex/javax/obex/ServerSession.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2015 The Android Open Source Project + * Copyright (c) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -45,6 +47,7 @@ import java.io.OutputStream; public final class ServerSession extends ObexSession implements Runnable { private static final String TAG = "Obex ServerSession"; + private static final boolean V = ObexHelper.VDBG; private ObexTransport mTransport; @@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable { boolean done = false; while (!done && !mClosed) { + if(V) Log.v(TAG, "Waiting for incoming request..."); int requestType = mInput.read(); + if(V) Log.v(TAG, "Read request: " + requestType); switch (requestType) { case ObexHelper.OBEX_OPCODE_CONNECT: handleConnectRequest(); @@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable { } } catch (NullPointerException e) { - Log.d(TAG, e.toString()); + Log.d(TAG, "Exception occured - ignoring", e); } catch (Exception e) { - Log.d(TAG, e.toString()); + Log.d(TAG, "Exception occured - ignoring", e); } close(); } @@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable { int length = mInput.read(); length = (length << 8) + mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; } else { for (int i = 3; i < length; i++) { @@ -215,6 +220,7 @@ public final class ServerSession extends ObexSession implements Runnable { *internal error should not be sent because server has already replied with *OK response in "sendReply") */ + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); if (!op.isAborted) { sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); } @@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable { op.sendReply(response); } } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); } } @@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable { data[2] = (byte)totalLength; } op.write(data); - op.flush(); + op.flush(); // TODO: Do we need to flush? } /** @@ -304,7 +311,7 @@ public final class ServerSession extends ObexSession implements Runnable { flags = mInput.read(); constants = mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 3; } else { @@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable { try { code = mListener.onSetPath(request, reply, backup, create); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); return; } @@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable { length = mInput.read(); length = (length << 8) + mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 3; } else { @@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable { try { mListener.onDisconnect(request, reply); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); return; } @@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable { HeaderSet reply = new HeaderSet(); int bytesReceived; + if(V) Log.v(TAG,"handleConnectRequest()"); + /* * Read in the length of the OBEX packet, OBEX version, flags, and max * packet length */ packetLength = mInput.read(); packetLength = (packetLength << 8) + mInput.read(); + if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength); + version = mInput.read(); flags = mInput.read(); mMaxPacketLength = mInput.read(); mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read(); + if(V) Log.v(TAG,"handleConnectRequest() - version: " + version + + " MaxLength: " + mMaxPacketLength + " flags: " + flags); + // should we check it? if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT; } - if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + if(mMaxPacketLength > ObexHelper.getMaxTxPacketSize(mTransport)) { + Log.w(TAG, "Requested MaxObexPacketSize " + mMaxPacketLength + + " is larger than the max size supported by the transport: " + + ObexHelper.getMaxTxPacketSize(mTransport) + + " Reducing to this size."); + mMaxPacketLength = ObexHelper.getMaxTxPacketSize(mTransport); + } + + if (packetLength > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 7; } else { @@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable { code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } } catch (Exception e) { - e.printStackTrace(); + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); totalLength = 7; head = null; code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; @@ -633,13 +657,14 @@ public final class ServerSession extends ObexSession implements Runnable { * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers */ byte[] sendData = new byte[totalLength]; + int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport); sendData[0] = (byte)code; sendData[1] = length[2]; sendData[2] = length[3]; sendData[3] = (byte)0x10; sendData[4] = (byte)0x00; - sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); - sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + sendData[5] = (byte)(maxRxLength >> 8); + sendData[6] = (byte)(maxRxLength & 0xFF); if (head != null) { System.arraycopy(head, 0, sendData, 7, head.length); @@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable { mListener.onClose(); } try { - mInput.close(); - mOutput.close(); - mTransport.close(); + /* Set state to closed before interrupting the thread by closing the streams */ mClosed = true; + if(mInput != null) + mInput.close(); + if(mOutput != null) + mOutput.close(); + if(mTransport != null) + mTransport.close(); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured during close() - ignore",e); } mTransport = null; mInput = null; @@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable { return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } + public ObexTransport getTransport() { + return mTransport; + } } diff --git a/packages/DocumentsUI/res/layout/dialog_create_dir.xml b/packages/DocumentsUI/res/layout/dialog_create_dir.xml index 54e26b40dfdf..5ed476fc3287 100644 --- a/packages/DocumentsUI/res/layout/dialog_create_dir.xml +++ b/packages/DocumentsUI/res/layout/dialog_create_dir.xml @@ -22,6 +22,7 @@ <EditText android:id="@android:id/text1" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:inputType="text" /> </FrameLayout> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java index d2ef3d785e05..a9f03b6b99e8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java @@ -191,7 +191,8 @@ public class CopyService extends IntentService { cancelIntent.putExtra(EXTRA_CANCEL, mJobId); mProgressBuilder.addAction(R.drawable.ic_cab_cancel, getString(android.R.string.cancel), PendingIntent.getService(this, 0, - cancelIntent, PendingIntent.FLAG_ONE_SHOT)); + cancelIntent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)); // Send an initial progress notification. mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up. diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java index 5e565bfccbeb..7ea51b970e50 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java @@ -97,7 +97,6 @@ public class PickFragment extends Fragment { CharSequence displayName) { if (mContainer != null) { if (pickTarget != null) { - mContainer.setVisibility(View.VISIBLE); final Locale locale = getResources().getConfiguration().locale; switch (action) { case BaseActivity.State.ACTION_OPEN_TREE: @@ -112,7 +111,9 @@ public class PickFragment extends Fragment { default: throw new IllegalArgumentException("Illegal action for PickFragment."); } - + } + if (pickTarget != null && pickTarget.isCreateSupported()) { + mContainer.setVisibility(View.VISIBLE); } else { mContainer.setVisibility(View.GONE); } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index a88497cdf103..be71b034f3f9 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.Canvas; import android.media.AudioManager; import android.os.SystemClock; +import android.service.trust.TrustAgentService; import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; @@ -69,14 +70,27 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { } @Override - public void onTrustInitiatedByUser(int userId) { + public void onTrustGrantedWithFlags(int flags, int userId) { if (userId != mLockPatternUtils.getCurrentUser()) return; if (!isAttachedToWindow()) return; - - if (isVisibleToUser()) { - dismiss(false /* authenticated */); - } else { - mViewMediatorCallback.playTrustedSound(); + boolean bouncerVisible = isVisibleToUser(); + boolean initiatedByUser = + (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; + boolean dismissKeyguard = + (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; + + if (initiatedByUser || dismissKeyguard) { + if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) { + if (!bouncerVisible) { + // The trust agent dismissed the keyguard without the user proving + // that they are present (by swiping up to show the bouncer). That's fine if + // the user proved presence via some other way to the trust agent. + Log.i(TAG, "TrustAgent dismissed Keyguard."); + } + dismiss(false /* authenticated */); + } else { + mViewMediatorCallback.playTrustedSound(); + } } } }; diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index a02fb4a6fd97..1eec53250a07 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -58,12 +58,12 @@ import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintUtils; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.service.trust.TrustAgentService; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; import android.util.Log; -import android.util.Slog; import android.util.SparseBooleanArray; import com.google.android.collect.Lists; @@ -245,15 +245,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray(); @Override - public void onTrustChanged(boolean enabled, int userId, boolean initiatedByUser) { + public void onTrustChanged(boolean enabled, int userId, int flags) { mUserHasTrust.put(userId, enabled); - for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onTrustChanged(userId); - if (enabled && initiatedByUser) { - cb.onTrustInitiatedByUser(userId); + if (enabled && flags != 0) { + cb.onTrustGrantedWithFlags(flags, userId); } } } @@ -783,21 +782,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { private void startListeningForFingerprint() { if (DEBUG) Log.v(TAG, "startListeningForFingerprint()"); - final int userId; - try { - userId = ActivityManagerNative.getDefault().getCurrentUser().id; - } catch (RemoteException e) { - Log.e(TAG, "Failed to get current user id: ", e); - return; - } + int userId = ActivityManager.getCurrentUser(); if (mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId) - && mFpm.getEnrolledFingerprints().size() > 0) { + && mFpm.getEnrolledFingerprints(userId).size() > 0) { if (mFingerprintCancelSignal != null) { mFingerprintCancelSignal.cancel(); } mFingerprintCancelSignal = new CancellationSignal(); - mFpm.authenticate(null, mFingerprintCancelSignal, mAuthenticationCallback, 0, - ActivityManager.getCurrentUser()); + mFpm.authenticate(null, mFingerprintCancelSignal, mAuthenticationCallback, 0, userId); setFingerprintRunningDetectionRunning(true); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 756a7a4ee656..26e69731a319 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -171,9 +171,9 @@ public class KeyguardUpdateMonitorCallback { public void onTrustManagedChanged(int userId) { } /** - * Called when the user has proved to a trust agent that they want to use the device. + * Called after trust was granted with non-zero flags. */ - public void onTrustInitiatedByUser(int userId) { } + public void onTrustGrantedWithFlags(int flags, int userId) { } /** * Called when a fingerprint is recognized. diff --git a/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java b/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java index 5bbcc8ce4335..f5c809a61db1 100644 --- a/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/ViewMediatorCallback.java @@ -76,4 +76,9 @@ public interface ViewMediatorCallback { * (legacy API) */ boolean isInputRestricted(); + + /** + * @return true if the screen is on + */ + boolean isScreenOn(); } diff --git a/packages/Keyguard/test/SampleTrustAgent/src/com/android/trustagent/test/SampleTrustAgent.java b/packages/Keyguard/test/SampleTrustAgent/src/com/android/trustagent/test/SampleTrustAgent.java index e6a0dd7af20e..b8f16e7929dc 100644 --- a/packages/Keyguard/test/SampleTrustAgent/src/com/android/trustagent/test/SampleTrustAgent.java +++ b/packages/Keyguard/test/SampleTrustAgent/src/com/android/trustagent/test/SampleTrustAgent.java @@ -38,7 +38,7 @@ public class SampleTrustAgent extends TrustAgentService * <pre> * $ adb shell am broadcast -a action.sample_trust_agent.grant_trust\ * -e extra.message SampleTrust\ - * --el extra.duration 1000 --ez extra.init_by_user false + * --el extra.duration 1000 --ez extra.init_by_user false --ez extra.dismiss_keyguard false * </pre> */ private static final boolean ALLOW_EXTERNAL_BROADCASTS = false; @@ -51,6 +51,7 @@ public class SampleTrustAgent extends TrustAgentService private static final String EXTRA_MESSAGE = "extra.message"; private static final String EXTRA_DURATION = "extra.duration"; private static final String EXTRA_INITIATED_BY_USER = "extra.init_by_user"; + private static final String EXTRA_DISMISS_KEYGUARD = "extra.dismiss_keyguard"; private static final String PREFERENCE_REPORT_UNLOCK_ATTEMPTS = "preference.report_unlock_attempts"; @@ -141,10 +142,17 @@ public class SampleTrustAgent extends TrustAgentService public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_GRANT_TRUST.equals(action)) { + int flags = 0; + if (intent.getBooleanExtra(EXTRA_INITIATED_BY_USER, false)) { + flags |= TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER; + } + if (intent.getBooleanExtra(EXTRA_DISMISS_KEYGUARD, false)) { + flags |= TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD; + } + try { grantTrust(intent.getStringExtra(EXTRA_MESSAGE), - intent.getLongExtra(EXTRA_DURATION, 0), - intent.getBooleanExtra(EXTRA_INITIATED_BY_USER, false)); + intent.getLongExtra(EXTRA_DURATION, 0), flags); } catch (IllegalStateException e) { logAndShowToast("IllegalStateException: " + e.getMessage()); } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsInfo.java new file mode 100644 index 000000000000..60b5ba581f5d --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsInfo.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2015 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.settingslib.applications; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.os.AsyncTask; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class PermissionsInfo { + + private static final String TAG = "PermissionsInfo"; + + private final PackageManager mPm; + private final ArrayList<PermissionGroup> mGroups = new ArrayList<>(); + private final Map<String, PermissionGroup> mGroupLookup = new ArrayMap<>(); + private final Callback mCallback; + private final Context mContext; + // Count of apps that request runtime permissions. + private int mRuntimePermAppsCt; + // Count of apps that are granted runtime permissions. + private int mRuntimePermAppsGrantedCt; + + public PermissionsInfo(Context context, Callback callback) { + mContext = context; + mPm = context.getPackageManager(); + mCallback = callback; + new PermissionsLoader().execute(); + } + + public List<PermissionGroup> getGroups() { + synchronized (mGroups) { + return new ArrayList<>(mGroups); + } + } + + public int getRuntimePermAppsCount() { + return mRuntimePermAppsCt; + } + + public int getRuntimePermAppsGrantedCount() { + return mRuntimePermAppsGrantedCt; + } + + private PermissionGroup getOrCreateGroup(String permission) { + PermissionGroup group = mGroupLookup.get(permission); + if (group == null) { + // Some permissions don't have a group, in that case treat them like a group + // and create their own PermissionGroup (only if they are runtime). + try { + PermissionInfo info = mPm.getPermissionInfo(permission, 0); + if (info.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + group = new PermissionGroup(); + // TODO: Add default permission icon. + group.icon = info.icon != 0 ? info.loadIcon(mPm) : new ShapeDrawable(); + group.name = info.name; + group.label = info.loadLabel(mPm).toString(); + mGroups.add(group); + mGroupLookup.put(permission, group); + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Unknown permission " + permission, e); + } + } + return group; + } + + private class PermissionsLoader extends AsyncTask<Void, Void, Void> { + + @Override + protected Void doInBackground(Void... params) { + List<PermissionGroupInfo> groups = + mPm.getAllPermissionGroups(PackageManager.GET_META_DATA); + // Get the groups. + for (PermissionGroupInfo groupInfo : groups) { + PermissionGroup group = new PermissionGroup(); + // TODO: Add default permission icon. + group.icon = groupInfo.icon != 0 ? groupInfo.loadIcon(mPm) : new ShapeDrawable(); + group.name = groupInfo.name; + group.label = groupInfo.loadLabel(mPm).toString(); + synchronized (mGroups) { + mGroups.add(group); + } + } + // Load permissions and which are runtime. + for (PermissionGroup group : mGroups) { + try { + List<PermissionInfo> permissions = + mPm.queryPermissionsByGroup(group.name, 0); + for (PermissionInfo info : permissions) { + if (info.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) continue; + mGroupLookup.put(info.name, group); + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Problem getting permissions", e); + } + } + // Load granted info. + for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { + List<PackageInfo> allApps = mPm.getInstalledPackages( + PackageManager.GET_PERMISSIONS, user.getIdentifier()); + for (PackageInfo info : allApps) { + if (info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 + || info.requestedPermissions == null) { + continue; + } + final int N = info.requestedPermissionsFlags.length; + boolean appHasRuntimePerms = false; + boolean appGrantedRuntimePerms = false; + for (int i = 0; i < N; i++) { + boolean granted = (info.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; + PermissionGroup group = getOrCreateGroup(info.requestedPermissions[i]); + String key = Integer.toString(info.applicationInfo.uid); + if (group != null && !group.possibleApps.contains(key)) { + appHasRuntimePerms = true; + group.possibleApps.add(key); + if (granted) { + appGrantedRuntimePerms = true; + group.grantedApps.add(key); + } + } + } + if (appHasRuntimePerms) { + mRuntimePermAppsCt++; + if (appGrantedRuntimePerms) { + mRuntimePermAppsGrantedCt++; + } + } + } + } + Collections.sort(mGroups); + + return null; + } + + @Override + protected void onPostExecute(Void result) { + mCallback.onPermissionLoadComplete(); + } + } + + public static class PermissionGroup implements Comparable<PermissionGroup> { + public final List<String> possibleApps = new ArrayList<>(); + public final List<String> grantedApps = new ArrayList<>(); + public String name; + public String label; + public Drawable icon; + + @Override + public int compareTo(PermissionGroup another) { + return label.compareTo(another.label); + } + } + + public interface Callback { + void onPermissionLoadComplete(); + } + +} diff --git a/packages/SystemUI/res/anim/heads_up_enter.xml b/packages/SystemUI/res/anim/heads_up_enter.xml deleted file mode 100644 index 59eef424dd92..000000000000 --- a/packages/SystemUI/res/anim/heads_up_enter.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<set xmlns:android="http://schemas.android.com/apk/res/android" - > - <translate - android:interpolator="@android:interpolator/overshoot" - android:fromYDelta="-50%" android:toYDelta="0" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha - android:interpolator="@android:interpolator/decelerate_quad" - android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="@android:integer/config_shortAnimTime" /> -</set> diff --git a/packages/SystemUI/res/anim/heads_up_exit.xml b/packages/SystemUI/res/anim/heads_up_exit.xml deleted file mode 100644 index 2cad8f6fc2a4..000000000000 --- a/packages/SystemUI/res/anim/heads_up_exit.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<set xmlns:android="http://schemas.android.com/apk/res/android" - > - <translate - android:interpolator="@android:interpolator/overshoot" - android:fromYDelta="0" android:toYDelta="-50%" - android:duration="@android:integer/config_shortAnimTime" /> - <alpha - android:interpolator="@android:interpolator/accelerate_quad" - android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="@android:integer/config_shortAnimTime" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml new file mode 100644 index 000000000000..57b61da4dfab --- /dev/null +++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="#FFFFFFFF"/> + <keyframe + android:fraction="0.32" + android:value="#FFFFFFFF"/> + <keyframe + android:fraction="0.33" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="1.0" + android:value="#4DFFFFFF"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml new file mode 100644 index 000000000000..09694c3ec308 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml @@ -0,0 +1,44 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="0.32" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="0.33" + android:value="#FFFFFFFF"/> + <keyframe + android:fraction="0.66" + android:value="#FFFFFFFF"/> + <keyframe + android:fraction="0.67" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="1.0" + android:value="#4DFFFFFF"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml new file mode 100644 index 000000000000..2270e3f9ce9d --- /dev/null +++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="0.66" + android:value="#4DFFFFFF"/> + <keyframe + android:fraction="0.67" + android:value="#FFFFFFFF"/> + <keyframe + android:fraction="1.0" + android:value="#FFFFFFFF"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_1.xml b/packages/SystemUI/res/anim/ic_signal_blink_1.xml new file mode 100644 index 000000000000..ab1905af4e76 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_blink_1.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.32" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.33" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="1.0" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_2.xml b/packages/SystemUI/res/anim/ic_signal_blink_2.xml new file mode 100644 index 000000000000..1b7ace252ea3 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_blink_2.xml @@ -0,0 +1,44 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.32" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.33" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.66" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.67" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="1.0" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_blink_3.xml b/packages/SystemUI/res/anim/ic_signal_blink_3.xml new file mode 100644 index 000000000000..cee831c0d7d3 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_blink_3.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.66" + android:value="@color/light_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.67" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="1.0" + android:value="@color/light_mode_icon_color_dual_tone_fill"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml new file mode 100644 index 000000000000..9d398faad706 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.32" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.33" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="1.0" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml new file mode 100644 index 000000000000..c6e213d2848a --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml @@ -0,0 +1,44 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.32" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.33" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.66" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="0.67" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="1.0" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml new file mode 100644 index 000000000000..dce148cf4100 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2015 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. +--> +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:duration="@integer/carrier_network_change_anim_time" + android:repeatCount="-1"> + + <propertyValuesHolder + android:propertyName="fillColor" + android:valueType="colorType"> + <keyframe + android:fraction="0.0" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.66" + android:value="@color/dark_mode_icon_color_dual_tone_background"/> + <keyframe + android:fraction="0.67" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + <keyframe + android:fraction="1.0" + android:value="@color/dark_mode_icon_color_dual_tone_fill"/> + </propertyValuesHolder> + +</objectAnimator> diff --git a/packages/SystemUI/res/drawable/ic_audio_alarm.xml b/packages/SystemUI/res/drawable/ic_audio_alarm.xml deleted file mode 100644 index 91010a32902e..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_alarm.xml +++ /dev/null @@ -1,24 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28.0dp" - android:height="28.0dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - <path - android:fillColor="#ffffffff" - android:pathData="M44.0,11.44l-9.19,-7.71 -2.57,3.06 9.19,7.71 2.57,-3.06zm-28.24,-4.66l-2.57,-3.06 -9.19,7.71 2.57,3.06 9.19,-7.71zm9.24,9.22l-3.0,0.0l0.0,12.0l9.49,5.71 1.51,-2.47 -8.0,-4.74l0.0,-10.5zm-1.01,-8.0c-9.95,0.0 -17.99,8.06 -17.99,18.0s8.04,18.0 17.99,18.0 18.01,-8.06 18.01,-18.0 -8.06,-18.0 -18.01,-18.0zm0.01,32.0c-7.73,0.0 -14.0,-6.27 -14.0,-14.0s6.27,-14.0 14.0,-14.0 14.0,6.27 14.0,14.0 -6.26,14.0 -14.0,14.0z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_alarm_mute.xml b/packages/SystemUI/res/drawable/ic_audio_alarm_mute.xml deleted file mode 100644 index dd124d714434..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_alarm_mute.xml +++ /dev/null @@ -1,24 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28.0dp" - android:height="28.0dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - <path - android:fillColor="#ffffffff" - android:pathData="M24.0,12.0c7.73,0.0 14.0,6.27 14.0,14.0 0.0,1.69 -0.31,3.3 -0.86,4.8l3.04,3.04c1.16,-2.37 1.82,-5.03 1.82,-7.84 0.0,-9.94 -8.06,-18.0 -18.01,-18.0 -2.81,0.0 -5.46,0.66 -7.84,1.81l3.05,3.05c1.5,-0.55 3.11,-0.86 4.8,-0.86zm20.0,-0.56l-9.19,-7.71 -2.57,3.06 9.19,7.71 2.57,-3.06zm-38.16,-6.85l-2.55,2.54 2.66,2.66 -2.22,1.86 2.84,2.84 2.22,-1.86 1.6,1.6c-2.73,3.16 -4.39,7.27 -4.39,11.77 0.0,9.94 8.04,18.0 17.99,18.0 4.51,0.0 8.62,-1.67 11.77,-4.4l4.4,4.4 2.54,-2.55 -34.91,-34.91 -1.95,-1.95zm27.1,32.19c-2.43,2.01 -5.54,3.22 -8.94,3.22 -7.73,0.0 -14.0,-6.27 -14.0,-14.0 0.0,-3.4 1.21,-6.51 3.22,-8.94l19.72,19.72zm-16.91,-30.23l-2.84,-2.84 -1.7,1.43 2.84,2.84 1.7,-1.43z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_bt.xml b/packages/SystemUI/res/drawable/ic_audio_bt.xml deleted file mode 100644 index c0da51909ffc..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_bt.xml +++ /dev/null @@ -1,25 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_bt_mute.xml b/packages/SystemUI/res/drawable/ic_audio_bt_mute.xml deleted file mode 100644 index 718eee55aaff..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_bt_mute.xml +++ /dev/null @@ -1,25 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_remote.xml b/packages/SystemUI/res/drawable/ic_audio_remote.xml deleted file mode 100644 index 762878b8deae..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_remote.xml +++ /dev/null @@ -1,25 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM38.0,14.0L10.0,14.0l0.0,3.3c7.9,2.6 14.2,8.8 16.7,16.7L38.0,34.0L38.0,14.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_vol.xml b/packages/SystemUI/res/drawable/ic_audio_vol.xml deleted file mode 100644 index 587ea89cb728..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_vol.xml +++ /dev/null @@ -1,24 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28.0dp" - android:height="28.0dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - <path - android:fillColor="#ffffffff" - android:pathData="M6.0,18.0l0.0,12.0l8.0,0.0l10.0,10.0L24.0,8.0L14.0,18.0L6.0,18.0zm27.0,6.0c0.0,-3.53 -2.04,-6.58 -5.0,-8.05l0.0,16.11c2.96,-1.48 5.0,-4.53 5.0,-8.06zM28.0,6.46l0.0,4.13c5.78,1.72 10.0,7.07 10.0,13.41s-4.22,11.69 -10.0,13.41l0.0,4.13c8.01,-1.82 14.0,-8.97 14.0,-17.54S36.01,8.28 28.0,6.46z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_audio_vol_mute.xml b/packages/SystemUI/res/drawable/ic_audio_vol_mute.xml deleted file mode 100644 index 8a7c7ecb2d3a..000000000000 --- a/packages/SystemUI/res/drawable/ic_audio_vol_mute.xml +++ /dev/null @@ -1,24 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28.0dp" - android:height="28.0dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - <path - android:fillColor="#ffffffff" - android:pathData="M33.0,24.0c0.0,-3.53 -2.04,-6.58 -5.0,-8.05l0.0,4.42l4.91,4.91c0.06,-0.42 0.09,-0.85 0.09,-1.28zm5.0,0.0c0.0,1.88 -0.41,3.65 -1.08,5.28l3.03,3.03C41.25,29.82 42.0,27.0 42.0,24.0c0.0,-8.56 -5.99,-15.72 -14.0,-17.54l0.0,4.13c5.78,1.72 10.0,7.07 10.0,13.41zM8.55,6.0L6.0,8.55 15.45,18.0L6.0,18.0l0.0,12.0l8.0,0.0l10.0,10.0L24.0,26.55l8.51,8.51c-1.34,1.03 -2.85,1.86 -4.51,2.36l0.0,4.13c2.75,-0.63 5.26,-1.89 7.37,-3.62L39.45,42.0 42.0,39.45l-18.0,-18.0L8.55,6.0zM24.0,8.0l-4.18,4.18L24.0,16.36L24.0,8.0z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml index 4bff96d55cee..96e2fd4e5f82 100644 --- a/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml +++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml @@ -1,7 +1,7 @@ <!-- -Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2015 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); + 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 @@ -14,12 +14,23 @@ Copyright (C) 2014 The Android Open Source Project limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" + android:width="32dp" + android:height="32dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> - <path + android:name="dot1" android:fillColor="#FFFFFFFF" - android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/> + android:pathData="M9.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:name="dot2" + android:fillColor="#4DFFFFFF" + android:pathData="M14.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:name="dot3" + android:fillColor="#4DFFFFFF" + android:pathData="M19.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:fillColor="#4DFFFFFF" + android:pathData="M2.0,22.0l6.0,0.0 0.0,-4.0 14.0,0.0 0.0,-16.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml new file mode 100644 index 000000000000..2186aa82e83f --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2015 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_qs_signal_carrier_network_change" > + <target + android:name="dot1" + android:animation="@anim/ic_qs_signal_blink_1"/> + <target + android:name="dot2" + android:animation="@anim/ic_qs_signal_blink_2"/> + <target + android:name="dot3" + android:animation="@anim/ic_qs_signal_blink_3"/> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_ringer_audible.xml deleted file mode 100644 index fd50617bd805..000000000000 --- a/packages/SystemUI/res/drawable/ic_ringer_audible.xml +++ /dev/null @@ -1,25 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:fillColor="#FFFFFFFF" - android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_mute.xml b/packages/SystemUI/res/drawable/ic_ringer_mute.xml deleted file mode 100644 index b29a139b20e3..000000000000 --- a/packages/SystemUI/res/drawable/ic_ringer_mute.xml +++ /dev/null @@ -1,24 +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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="28dp" - android:height="28dp" - android:viewportWidth="48.0" - android:viewportHeight="48.0"> - <path - android:fillColor="#FFFFFFFF" - android:pathData="M23.000000,44.000000c2.200000,0.000000 4.000000,-1.800000 4.000000,-4.000000l-8.000000,0.000000C19.000000,42.200001 20.799999,44.000000 23.000000,44.000000zM36.000000,21.000000c0.000000,-6.100000 -4.300000,-11.300000 -10.000000,-12.600000L26.000000,7.000000c0.000000,-1.700000 -1.300000,-3.000000 -3.000000,-3.000000c-1.700000,0.000000 -3.000000,1.300000 -3.000000,3.000000l0.000000,1.400000c-1.000000,0.200000 -2.000000,0.600000 -2.900000,1.100000L36.000000,28.400000L36.000000,21.000000zM35.500000,38.000000l4.000000,4.000000l2.500000,-2.500000L8.500000,6.000000L6.000000,8.500000l5.800000,5.800000C10.700000,16.299999 10.000000,18.600000 10.000000,21.000000l0.000000,11.000000l-4.000000,4.000000l0.000000,2.000000L35.500000,38.000000z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml new file mode 100644 index 000000000000..f69ffe49b138 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml @@ -0,0 +1,36 @@ +<!-- + Copyright (C) 2015 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" + android:width="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:name="dot1" + android:fillColor="?attr/fillColor" + android:pathData="M9.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:name="dot2" + android:fillColor="?attr/backgroundColor" + android:pathData="M14.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:name="dot3" + android:fillColor="?attr/backgroundColor" + android:pathData="M19.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/> + <path + android:fillColor="?attr/backgroundColor" + android:pathData="M2.0,22.0l6.0,0.0 0.0,-4.0 14.0,0.0 0.0,-16.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml new file mode 100644 index 000000000000..275f037778c0 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2015 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/stat_sys_signal_carrier_network_change" > + <target + android:name="dot1" + android:animation="@anim/ic_signal_blink_1"/> + <target + android:name="dot2" + android:animation="@anim/ic_signal_blink_2"/> + <target + android:name="dot3" + android:animation="@anim/ic_signal_blink_3"/> +</animated-vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml new file mode 100644 index 000000000000..ff49d4cfdb13 --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2015 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/stat_sys_signal_carrier_network_change" > + <target + android:name="dot1" + android:animation="@anim/ic_signal_dark_blink_1"/> + <target + android:name="dot2" + android:animation="@anim/ic_signal_dark_blink_2"/> + <target + android:name="dot3" + android:animation="@anim/ic_signal_dark_blink_3"/> +</animated-vector> diff --git a/packages/SystemUI/res/layout/heads_up.xml b/packages/SystemUI/res/layout/heads_up.xml deleted file mode 100644 index 650ee5dfc0cf..000000000000 --- a/packages/SystemUI/res/layout/heads_up.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?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. ---> -<!-- extends FrameLayout --> -<com.android.systemui.statusbar.policy.HeadsUpNotificationView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_height="match_parent" - android:layout_width="match_parent" - android:background="@drawable/heads_up_scrim"> - - <FrameLayout - android:layout_width="@dimen/notification_panel_width" - android:layout_height="wrap_content" - android:layout_gravity="@integer/notification_panel_layout_gravity" - android:paddingStart="@dimen/notification_side_padding" - android:paddingEnd="@dimen/notification_side_padding" - android:elevation="8dp" - android:id="@+id/content_holder" /> - -</com.android.systemui.statusbar.policy.HeadsUpNotificationView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 532e1b7ff17d..539aabf2c0a4 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -46,6 +46,13 @@ android:layout_height="match_parent" android:importantForAccessibility="no" /> + <com.android.systemui.statusbar.AlphaOptimizedView + android:id="@+id/heads_up_scrim" + android:layout_width="match_parent" + android:layout_height="@dimen/heads_up_scrim_height" + android:background="@drawable/heads_up_scrim" + android:importantForAccessibility="no"/> + <include layout="@layout/status_bar" android:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" /> diff --git a/packages/SystemUI/res/layout/volume_panel.xml b/packages/SystemUI/res/layout/volume_panel.xml deleted file mode 100644 index 4d8aaa7f3cda..000000000000 --- a/packages/SystemUI/res/layout/volume_panel.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2007 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/visible_panel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" > - - <FrameLayout - android:id="@+id/slider_panel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/system_secondary_color" - android:paddingTop="8dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:clipChildren="false" /> - - <include - android:layout_width="match_parent" - android:layout_height="wrap_content" - layout="@layout/zen_mode_panel" /> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml deleted file mode 100644 index dad68c31ad51..000000000000 --- a/packages/SystemUI/res/layout/volume_panel_item.xml +++ /dev/null @@ -1,74 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2011 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:clipChildren="false" - android:gravity="start|center_vertical" - android:orientation="horizontal" > - - <ImageView - android:id="@+id/stream_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:scaleType="center" - android:background="@drawable/btn_borderless_rect" - android:contentDescription="@null" /> - - <FrameLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" > - - <TextView - android:id="@+id/suppressor" - android:visibility="gone" - android:textAppearance="@style/TextAppearance.QS.VolumeSuppressor" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:singleLine="true" - android:ellipsize="end" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <SeekBar - android:id="@+id/seekbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="0dp" - android:paddingEnd="16dp" - android:paddingStart="8dp" - android:paddingTop="0dp" /> - - </FrameLayout> - - <View - android:id="@+id/divider" - android:layout_width="1dp" - android:layout_height="32dp" - android:layout_marginLeft="8dp" - android:layout_marginRight="8dp" - android:background="@color/volume_panel_divider" /> - - <ImageView - android:id="@+id/secondary_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:scaleType="center" - android:background="@drawable/btn_borderless_rect" - android:contentDescription="@null" /> -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index d829f0ecf2ac..b676bce94de9 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -126,7 +126,7 @@ android:lineSpacingMultiplier="1.20029" android:layout_toStartOf="@id/zen_introduction_confirm" android:text="@string/zen_priority_introduction" - android:textAppearance="@style/TextAppearance.QS.VolumeSuppressor" /> + android:textAppearance="@style/TextAppearance.QS.Introduction" /> <TextView android:id="@+id/zen_introduction_customize" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index bba8a6da27db..1f13404890ee 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Weier"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is die volumedialoog"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Raak om die oorspronklike terug te stel."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen-kenmerk"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 06e9fb232ef4..a631b9051540 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ከልክል"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> የድምጽ መጠን መገናኛው ነው"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"የመጀመሪያውን ወደነበረበት ለመመለስ ይንኩ።"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index ac86dbda6a3c..dfaf4087a216 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -403,7 +403,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"رفض"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> هو مربع حوار مستوى الصوت"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"المس لاستعادة الإعداد الأصلي."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 64e5afafc040..6bd996956884 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Отказване"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> изпълнява ролята на диалоговия прозорец за силата на звука"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Докоснете, за да възстановите оригинала."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml index dd83d59258ab..8f057e49e051 100644 --- a/packages/SystemUI/res/values-bn-rBD/strings.xml +++ b/packages/SystemUI/res/values-bn-rBD/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"প্রত্যাখ্যান করুন"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> হল ভলিউম ডায়লগ"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"আসলটি পুনঃস্থাপন করতে স্পর্শ করুন৷"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index eda024d74258..706086a5b0b4 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Denega"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> és el diàleg de volum"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toca per restaurar l\'original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 7abcc11d4ae8..de7c62a7c77d 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -403,7 +403,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmítnout"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je dialog hlasitosti"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Klepnutím obnovíte originál."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 3d0a0b04a730..99fc26415396 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Afvis"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er dialogboksen for lydstyrke"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Tryk for at gendanne originalen."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index f77da1220497..eb0ea2cfdc9c 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ablehnen"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> regelt die Lautstärke."</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Zum Wiederherstellen des Originals hier tippen"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index e1e8ba30a424..e4c85c188075 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Απόρριψη"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> αποτελεί το παράθυρο διαλόγου ελέγχου έντασης"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Αγγίξτε για επαναφορά αρχικού."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 9e76616121f0..aadd011e1920 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -167,8 +167,7 @@ <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string> <string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string> <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string> - <!-- no translation found for accessibility_desc_confirm (3446792278337969766) --> - <skip /> + <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string> <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string> @@ -303,10 +302,8 @@ <string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string> - <!-- no translation found for zen_priority_introduction (7253045784560169993) --> - <skip /> - <!-- no translation found for zen_priority_customize_button (7948043278226955063) --> - <skip /> + <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string> + <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string> <string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string> <string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string> <string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string> @@ -342,12 +339,9 @@ <string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string> <string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string> - <!-- no translation found for guest_notification_title (1585278533840603063) --> - <skip /> - <!-- no translation found for guest_notification_text (7513706222848825467) --> - <skip /> - <!-- no translation found for guest_notification_remove_action (8820670703892101990) --> - <skip /> + <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string> + <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string> + <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string> <string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string> <string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string> <string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string> @@ -399,7 +393,5 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> - <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> - <skip /> + <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 9e76616121f0..aadd011e1920 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -167,8 +167,7 @@ <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string> <string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string> <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string> - <!-- no translation found for accessibility_desc_confirm (3446792278337969766) --> - <skip /> + <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string> <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string> @@ -303,10 +302,8 @@ <string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string> - <!-- no translation found for zen_priority_introduction (7253045784560169993) --> - <skip /> - <!-- no translation found for zen_priority_customize_button (7948043278226955063) --> - <skip /> + <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string> + <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string> <string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string> <string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string> <string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string> @@ -342,12 +339,9 @@ <string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string> <string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string> - <!-- no translation found for guest_notification_title (1585278533840603063) --> - <skip /> - <!-- no translation found for guest_notification_text (7513706222848825467) --> - <skip /> - <!-- no translation found for guest_notification_remove_action (8820670703892101990) --> - <skip /> + <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string> + <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string> + <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string> <string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string> <string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string> <string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string> @@ -399,7 +393,5 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> - <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> - <skip /> + <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 9e76616121f0..aadd011e1920 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -167,8 +167,7 @@ <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string> <string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string> <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string> - <!-- no translation found for accessibility_desc_confirm (3446792278337969766) --> - <skip /> + <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string> <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string> @@ -303,10 +302,8 @@ <string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string> <string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string> - <!-- no translation found for zen_priority_introduction (7253045784560169993) --> - <skip /> - <!-- no translation found for zen_priority_customize_button (7948043278226955063) --> - <skip /> + <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string> + <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string> <string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string> <string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string> <string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string> @@ -342,12 +339,9 @@ <string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string> <string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string> <string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string> - <!-- no translation found for guest_notification_title (1585278533840603063) --> - <skip /> - <!-- no translation found for guest_notification_text (7513706222848825467) --> - <skip /> - <!-- no translation found for guest_notification_remove_action (8820670703892101990) --> - <skip /> + <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string> + <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string> + <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string> <string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string> <string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string> <string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string> @@ -399,7 +393,5 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> - <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> - <skip /> + <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 40400f33f667..e75492569bfc 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rechazar"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> es el cuadro de diálogo de volumen."</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar el original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index cb061a5dc847..c1311760df2e 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rechazar"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> es el cuadro de diálogo de volumen"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar la versión original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml index 7fffec7647e1..387fe82be03c 100644 --- a/packages/SystemUI/res/values-et-rEE/strings.xml +++ b/packages/SystemUI/res/values-et-rEE/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Keela"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> on helitugevuse dialoog"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Originaali taastamiseks puudutage."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml index 94dd6b169a62..d8c1789db869 100644 --- a/packages/SystemUI/res/values-eu-rES/strings.xml +++ b/packages/SystemUI/res/values-eu-rES/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ukatu"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> da bolumenaren leihoa"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Ukitu jatorrizkora leheneratzeko"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 0260d3a0b94d..7594ad9672aa 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"رد کردن"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> کنترلکننده صدا است"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"برای بازیابی کنترلکننده اصلی، لمس کنید."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 156bbb73cd89..a13170140e38 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Estä"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> on äänenvoimakkuusvalinta."</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Palauta alkuperäinen koskettamalla."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index de5b7aa0404d..9d10e48e402d 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuser"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> correspond à la boîte de dialogue du volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Touchez pour restaurer l\'original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index ed17e4af395b..2788a87b6971 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuser"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> correspond à la boîte de dialogue du volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Appuyez pour restaurer l\'interface d\'origine."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml index 89d5fc3eacc5..a4786df6d159 100644 --- a/packages/SystemUI/res/values-gl-rES/strings.xml +++ b/packages/SystemUI/res/values-gl-rES/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Denegar"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é o cadro de diálogo de volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar o orixinal."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 08fbe696d18c..7d6f51bc23c7 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"अस्वीकार करें"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> वॉल्यूम संवाद है"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"मूल वॉल्यूम को फिर से लाने के लिए स्पर्श करें."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 85bb634a8b0f..7ea2336ca22f 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -400,7 +400,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odbij"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> predstavlja dijaloški okvir za upravljanje glasnoćom"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Dodirnite da biste vratili izvorno."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 077384962541..f6743271eca4 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Elutasítás"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás kezeli a hangerőt"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Érintse meg az eredeti érték visszaállításához."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml index 3ffc0306e60f..c3d0f6d3e7cb 100644 --- a/packages/SystemUI/res/values-hy-rAM/strings.xml +++ b/packages/SystemUI/res/values-hy-rAM/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Մերժել"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ը ձայնի ուժգնության երկխոսության հավելված է"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Դիպչեք՝ սկզբնօրինակը վերականգնելու համար:"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index edee7c6f21cd..f53ceccfe72c 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tolak"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> adalah dialog volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Sentuh untuk memulihkan aslinya."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml index 660834eeac9e..8000344d178e 100644 --- a/packages/SystemUI/res/values-is-rIS/strings.xml +++ b/packages/SystemUI/res/values-is-rIS/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Hafna"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er hljóðstyrksvalmyndin"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Snertu til að færa í upprunalegt horf."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index a7ac816037ff..a0ceb62c3273 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Nega"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> rappresenta la finestra di dialogo relativa al volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Tocca per ripristinare l\'originale."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 837bba7a7422..b02ff0ae628c 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"דחה"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> הוא תיבת הדו-שיח של עוצמת הקול"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"גע כדי לשחזר את עוצמת הקול המקורית."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index e01fe535ef8b..f8f767ba44fb 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"許可しない"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>を音量ダイアログとして使用"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"タップすると元の音量ダイアログが復元されます。"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml index a41c9f52253b..4377663b38dd 100644 --- a/packages/SystemUI/res/values-ka-rGE/strings.xml +++ b/packages/SystemUI/res/values-ka-rGE/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"უარყოფა"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ხმოვან დიალოგშია"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"ორიგინალის აღდგენისათვის, შეეხეთ."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml index 0beea99ec82c..c4141e158023 100644 --- a/packages/SystemUI/res/values-kk-rKZ/strings.xml +++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Өшіру"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> — көлем диалогтық терезесі"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Түпнұсқаны қалпына келтіру үшін түртіңіз."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml index ba84a549c3fa..8087373a2058 100644 --- a/packages/SystemUI/res/values-km-rKH/strings.xml +++ b/packages/SystemUI/res/values-km-rKH/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"បដិសេធ"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> គឺជាប្រអប់សម្លេង"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"ប៉ះដើម្បីស្តារច្បាប់ដើម។"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml index 76b57073c250..fe138ce0f4ac 100644 --- a/packages/SystemUI/res/values-kn-rIN/strings.xml +++ b/packages/SystemUI/res/values-kn-rIN/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ನಿರಾಕರಿಸು"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ವಾಲ್ಯೂಮ್ ಸಂವಾದವಾಗಿದೆ"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"ಮೂಲ ಮರುಸ್ಥಾಪಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 32f751f48959..c525095058d4 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"거부"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>은(는) 볼륨 대화입니다."</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"원본을 복원하려면 터치하세요."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml index 8e2defdcdf38..eca5b2c27b8b 100644 --- a/packages/SystemUI/res/values-ky-rKG/strings.xml +++ b/packages/SystemUI/res/values-ky-rKG/strings.xml @@ -424,7 +424,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Жок"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> үндү катуулатуу диалогу"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Түпнусканы калыбына келтирүү үчүн тийип коюңуз."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml index d7bb83bace75..8ce9eda7f2ad 100644 --- a/packages/SystemUI/res/values-lo-rLA/strings.xml +++ b/packages/SystemUI/res/values-lo-rLA/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ປະຕິເສດ"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ແມ່ນໜ້າຕ່າງລະດັບສຽງ"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"ສໍາຜັດເພື່ອກູ້ຄືນຕົ້ນສະບັບ."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 2ac46367b722..7e211cd04e1b 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Atmesti"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ yra garsumo valdymo dialogo langas"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Palieskite, kad atkurtumėte originalą."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index a184664f8cd7..c6927918788b 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -400,7 +400,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Neatļaut"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ir skaļuma dialoglodziņš"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Pieskarieties, lai atjaunotu sākotnējo."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml index e2884515dd1f..b13a58a46418 100644 --- a/packages/SystemUI/res/values-mk-rMK/strings.xml +++ b/packages/SystemUI/res/values-mk-rMK/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Одбиј"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> е дијалог за јачина на звук"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Допрете за да го вратите оригиналот."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml index 3bc27a10a73d..e0d3eaf2e9a3 100644 --- a/packages/SystemUI/res/values-ml-rIN/strings.xml +++ b/packages/SystemUI/res/values-ml-rIN/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"നിരസിക്കുക"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>, വോളിയം ഡയലോഗാണ്"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"ആദ്യത്തേത് പുനഃസ്ഥാപിക്കാൻ സ്പർശിക്കുക."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml index 367de5dff3c0..b68af61bf212 100644 --- a/packages/SystemUI/res/values-mn-rMN/strings.xml +++ b/packages/SystemUI/res/values-mn-rMN/strings.xml @@ -397,7 +397,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Татгалзах"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> нь дууны диалог юм."</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Анхны хувилбарыг эргүүлэн хадгалахыг хүсвэл хүрнэ үү."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml index eb8c6adbd478..c9dd37e2f487 100644 --- a/packages/SystemUI/res/values-mr-rIN/strings.xml +++ b/packages/SystemUI/res/values-mr-rIN/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"नकार द्या"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> हा व्हॉल्यूम संवाद आहे"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"मूळ पुनर्संचयित करण्यासाठी स्पर्श करा."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml index 1b1fded604f1..f5624a0c44c2 100644 --- a/packages/SystemUI/res/values-ms-rMY/strings.xml +++ b/packages/SystemUI/res/values-ms-rMY/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tolak"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ialah dialog kelantangan"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Sentuh untuk memulihkan yang asal."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:rentetan/nama_ciri_mod_zen"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml index 52c42987bb4a..f0aa3b19af81 100644 --- a/packages/SystemUI/res/values-my-rMM/strings.xml +++ b/packages/SystemUI/res/values-my-rMM/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ငြင်းပယ်သည်"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အသံဒိုင်ယာလော့ခ်ဖြစ်သည်"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"မူရင်းအားပြန်လည်သိမ်းဆည်းရန် ထိပါ။"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 2168f146636a..ef64990b877e 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ikke tillat"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er volumdialogen"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Trykk for å gå tilbake til den opprinnelige volumdialogen."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml index 11e19e794c17..1561177d2929 100644 --- a/packages/SystemUI/res/values-ne-rNP/strings.xml +++ b/packages/SystemUI/res/values-ne-rNP/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"अस्वीकार गर्नुहोस्"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> भोल्यूम संवाद हो"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"मूल पुनर्स्थापना गर्न छुनुहोस्।"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*Android: स्ट्रिङ/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 06ed9867d228..24ace950446b 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Afwijzen"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is het volumedialoogvenster"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Tik hierop om het origineel te herstellen."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index ffeff1e50380..02c1d7488955 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmów"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> steruje głośnością"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Dotknij, by przywrócić pierwotną."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 23413be36ea9..2b802c03a035 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Recusar"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é a caixa de diálogo do volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toque para restaurar o original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index caf85ccf7dff..8d82942cc89c 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Negar"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é a caixa de diálogo referente ao volume"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Toque para restaurar o original."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 509ae926772c..06d9ac201d67 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -400,7 +400,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuzați"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> afișează caseta de dialog pentru volum"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Atingeți pentru a reveni la setarea inițială."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 1c584fe2ce46..b6c506f86dd0 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -403,7 +403,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Нет"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"Приложение <xliff:g id="APP_NAME">%1$s</xliff:g> назначено регулятором громкости"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Нажмите, чтобы восстановить приложение по умолчанию."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml index 9643102f3950..1e1d981cd72d 100644 --- a/packages/SystemUI/res/values-si-rLK/strings.xml +++ b/packages/SystemUI/res/values-si-rLK/strings.xml @@ -167,8 +167,7 @@ <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"අගුළු තිරය."</string> <string name="accessibility_desc_settings" msgid="3417884241751434521">"සැකසීම්"</string> <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"දළ විශ්ලේෂණය."</string> - <!-- no translation found for accessibility_desc_confirm (3446792278337969766) --> - <skip /> + <string name="accessibility_desc_confirm" msgid="3446792278337969766">"තහවුරු කරන්න"</string> <string name="accessibility_quick_settings_user" msgid="1104846699869476855">"පරිශීලකයා <xliff:g id="USER">%s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wifi අක්රියයි."</string> @@ -303,10 +302,8 @@ <string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා උඩට සර්පණය කරන්න."</string> <string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා වමට සර්පණය කරන්න."</string> <string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"අතුරු බිඳීම් නැත. අඩුම තරමේ අනතුරු ඇඟවීමක්වත් නැත."</string> - <!-- no translation found for zen_priority_introduction (7253045784560169993) --> - <skip /> - <!-- no translation found for zen_priority_customize_button (7948043278226955063) --> - <skip /> + <string name="zen_priority_introduction" msgid="7253045784560169993">"සීනු, සිහි කැඳවීම්, සිදුවීම් සහ ඔබ සඳහන් කරන අමතන්නන් හැර වෙනත් ශබ්ද සහ කම්පන වලින් ඔබව බාධා නොකරයි."</string> + <string name="zen_priority_customize_button" msgid="7948043278226955063">"අභිරුචිකරණය"</string> <string name="zen_no_interruptions" msgid="7970973750143632592">"අතුරු බිදුම් නැත"</string> <string name="zen_important_interruptions" msgid="3477041776609757628">"ප්රමුඛ අතුරු බිඳීම් පමණයි"</string> <string name="zen_alarms" msgid="5055668280767657759">"ඇඟවීම් පමණි"</string> @@ -342,12 +339,9 @@ <string name="guest_wipe_session_message" msgid="8476238178270112811">"ඔබගේ සැසිය දිගටම කරගෙන යෑමට ඔබට අවශ්යද?"</string> <string name="guest_wipe_session_wipe" msgid="5065558566939858884">"යළි මුල සිට අරඹන්න"</string> <string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"ඔව්, දිගටම කරගෙන යන්න"</string> - <!-- no translation found for guest_notification_title (1585278533840603063) --> - <skip /> - <!-- no translation found for guest_notification_text (7513706222848825467) --> - <skip /> - <!-- no translation found for guest_notification_remove_action (8820670703892101990) --> - <skip /> + <string name="guest_notification_title" msgid="1585278533840603063">"ආගන්තුක පරිශිලකයා"</string> + <string name="guest_notification_text" msgid="7513706222848825467">"යෙදුම් සහ දත්ත ඉවත් කිරීමට ආගන්තුකයා ඉවත් කරන්න"</string> + <string name="guest_notification_remove_action" msgid="8820670703892101990">"ආගන්තුකයා ඉවත් කරන්නද?"</string> <string name="user_add_user_title" msgid="4553596395824132638">"අලුත් පරිශීලකයෙක් එකතු කරන්නද?"</string> <string name="user_add_user_message_short" msgid="2161624834066214559">"ඔබ අලුත් පරිශීලකයෙක් එකතු කරන විට, එම පුද්ගලයා ඔහුගේ වැඩ කරන ඉඩ සකසා ගත යුතුය.\n\nසියළුම අනෙක් පරිශීලකයින් සඳහා ඕනෑම පරිශීලකයෙකුට යාවත්කාලීන කළ හැක."</string> <string name="battery_saver_notification_title" msgid="237918726750955859">"බැටරිය සුරකින්නා සක්රීයයි"</string> @@ -399,7 +393,5 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ප්රතික්ෂේප කරන්න"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ධාරිතා සංවාදයයි"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"මුල් තත්ත්වය නැවත ප්රතිසාධනය කිරීමට ස්පර්ශ කරන්න."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> - <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> - <skip /> + <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"ඔබ කාර්යාල පැතිකඩේ සිටියි"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index d82690100989..72e30b5a3a21 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -403,7 +403,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmietnuť"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je dialóg hlasitosti"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Klepnutím obnovíte originál."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index a7eca4d4f435..2c8e22147ede 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Zavrni"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je pogovorno okno glede prostornine"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Dotaknite se, če želite obnoviti izvirnik."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index edb116697596..a51f8ad226ae 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -400,7 +400,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Одбиј"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> је дијалог за јачину звука"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Додирните да бисте вратили оригинал."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index ee03be451e2b..ccf94ebdde69 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Neka"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> används som volymkontroll"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Tryck här om du vill återställa den ursprungliga appen."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 2ac3926b5bbc..c01d0acda444 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Kataa"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ni mazungumzo ya sauti"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Gusa ili urejeshe ya awali."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml index a8177defb308..2632a819cdbb 100644 --- a/packages/SystemUI/res/values-ta-rIN/strings.xml +++ b/packages/SystemUI/res/values-ta-rIN/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"நிராகரி"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"ஒலியளவு செய்தி: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"அசலை மீட்டமைக்கத் தொடவும்."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml index 009eddbb923e..8fe687eaedc7 100644 --- a/packages/SystemUI/res/values-te-rIN/strings.xml +++ b/packages/SystemUI/res/values-te-rIN/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"తిరస్కరించు"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> అనేది వాల్యూమ్ డైలాగ్"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"అసలుదాన్ని పునరుద్ధరించడానికి తాకండి."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index bd0f58ef48ad..48b0a38ec158 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"ปฏิเสธ"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> เป็นช่องโต้ตอบระดับเสียง"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"แตะเพื่อคืนค่าดั้งเดิม"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index b8858af7bcd2..9c5daa8f678f 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tanggihan"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ang volume dialog"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Pindutin upang ibalik ang orihinal."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 1b0d11f562b5..7c44bf1bcb7a 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Reddet"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ses denetimi iletişim kutusu olarak ayarlandı"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Orijinali geri yüklemek için dokunun."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index c5ebd3a0f91e..76ed71678ade 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Відхилити"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> призначено регулятором гучності"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Торкніться, щоб відновити оригінал."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml index c01a1978b1af..608c6d3184fa 100644 --- a/packages/SystemUI/res/values-ur-rPK/strings.xml +++ b/packages/SystemUI/res/values-ur-rPK/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"مسترد کریں"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> والیوم ڈائلاگ ہے"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"اصل کو بحال کرنے کیلئے ٹچ کریں۔"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml index 37e9542d39eb..63af257bf11f 100644 --- a/packages/SystemUI/res/values-uz-rUZ/strings.xml +++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rad etish"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ovoz balandligini boshqaradi"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Aslini tiklash uchun bosing."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 3c2d0ef576c9..a4341686bc2b 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Từ chối"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> là hộp thoại khối lượng"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Chạm để khôi phục bản gốc."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 32e773572fe1..4e3c26554a55 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒绝"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”已用作音量控制对话框"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"触摸即可恢复原始设置。"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index bf8dfb23c46c..d6be63f75b08 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒絕"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」為音量對話框"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"輕觸即可復原。"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index f9b33ca8a03c..ddab028b69a1 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -401,7 +401,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒絕"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」現在是預設的音量控制對話方塊。"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"輕觸這裡即可恢復原始設定。"</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 9a32c4b77925..e91577d243b1 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -399,7 +399,6 @@ <string name="volumeui_prompt_deny" msgid="5720663643411696731">"Phika"</string> <string name="volumeui_notification_title" msgid="4906770126345910955">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> yingxoxo yevolumu"</string> <string name="volumeui_notification_text" msgid="1826889705095768656">"Thinta ukuze ubuyisele kokwangempela."</string> - <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string> <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) --> <skip /> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 88d1769f1783..1f1455afd373 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -129,7 +129,6 @@ <color name="segmented_button_selected">#FFFFFFFF</color> <color name="segmented_button_unselected">#B3B0BEC5</color><!-- 70% blue grey 200 --> - <color name="volume_panel_divider">#1FFFFFFF</color><!-- 12% white --> <color name="dark_mode_icon_color_single_tone">#99000000</color> <color name="dark_mode_icon_color_dual_tone_background">#3d000000</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 2e9e9f7a560d..2e44547d03d2 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -147,7 +147,7 @@ <integer name="heads_up_default_snooze_length_ms">60000</integer> <!-- Minimum display time for a heads up notification, in milliseconds. --> - <integer name="heads_up_notification_minimum_time">3000</integer> + <integer name="heads_up_notification_minimum_time">2000</integer> <!-- milliseconds before the heads up notification accepts touches. --> <integer name="heads_up_sensitivity_delay">700</integer> @@ -276,9 +276,6 @@ <!-- Doze: alpha to apply to small icons when dozing --> <integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff --> - <!-- Volume: time to delay dismissing the volume panel after a click is performed --> - <integer name="volume_panel_dismiss_delay">200</integer> - <!-- Hotspot tile: number of days to show after feature is used. --> <integer name="days_to_show_hotspot_tile">30</integer> @@ -293,5 +290,8 @@ <!-- Enable the default volume dialog --> <bool name="enable_volume_ui">true</bool> + + <!-- Duration of the full carrier network change icon animation. --> + <integer name="carrier_network_change_anim_time">3000</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b6ff1ce30c21..7e50454fbdd8 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -330,7 +330,7 @@ keyguard_clock_height_fraction_* for the difference between min and max.--> <dimen name="keyguard_clock_notifications_margin_min">24dp</dimen> <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> - <dimen name="heads_up_window_height">250dp</dimen> + <dimen name="heads_up_scrim_height">250dp</dimen> <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> <dimen name="keyguard_min_swipe_amount">110dp</dimen> @@ -344,15 +344,6 @@ <!-- The chevron padding to the circle when hinting --> <dimen name="hint_chevron_circle_padding">16dp</dimen> - <!-- Volume panel dialog y offset --> - <dimen name="volume_panel_top">0dp</dimen> - - <!-- Volume panel dialog width --> - <dimen name="volume_panel_width">344dp</dimen> - - <!-- Volume panel z depth --> - <dimen name="volume_panel_z">3dp</dimen> - <!-- Distance between notifications and header when they are considered to be colliding. --> <dimen name="header_notifications_collide_distance">48dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 3fc75d2404b6..6d84727c2656 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -37,6 +37,8 @@ <item type="id" name="doze_saved_filter_tag"/> <item type="id" name="qs_icon_tag"/> <item type="id" name="scrim"/> + <item type="id" name="hun_scrim_alpha_start"/> + <item type="id" name="hun_scrim_alpha_end"/> <item type="id" name="notification_power"/> <item type="id" name="notification_screenshot"/> <item type="id" name="notification_hidden"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ae134c6c045b..67a0bc61c4a8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -359,6 +359,9 @@ <!-- Content description of the airplane mode icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_airplane_mode">Airplane mode.</string> + <!-- Content description of the carrier network changing icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_carrier_network_change_mode">Carrier network changing.</string> + <!-- Content description of the battery level icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string> @@ -998,7 +1001,7 @@ <string name="volumeui_notification_text">Touch to restore the original.</string> <!-- Volume dialog zen toggle switch title --> - <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string> + <string name="volume_zen_switch_text" translatable="false">@*android:string/zen_mode_feature_name</string> <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground --> <string name="managed_profile_foreground_toast">You are in the Work profile</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 974cc484febd..ef2e6f35dd11 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -138,9 +138,8 @@ <item name="android:textColor">@color/system_accent_color</item> </style> - <style name="TextAppearance.QS.VolumeSuppressor"> + <style name="TextAppearance.QS.Introduction"> <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/qs_tile_text</item> </style> <style name="TextAppearance.QS.DetailButton"> @@ -205,11 +204,6 @@ <style name="Animation.StatusBar"> </style> - <style name="Animation.StatusBar.HeadsUp"> - <item name="android:windowEnterAnimation">@anim/heads_up_enter</item> - <item name="android:windowExitAnimation">@anim/heads_up_exit</item> - </style> - <style name="systemui_theme" parent="@android:style/Theme.DeviceDefault"> <item name="android:colorPrimary">@color/system_primary_color</item> <item name="android:colorControlActivated">@color/system_accent_color</item> @@ -233,12 +227,6 @@ <item name="android:gravity">center</item> </style> - <!-- Window animations used for volume panel. --> - <style name="VolumePanelAnimation"> - <item name="android:windowEnterAnimation">@*android:anim/popup_enter_material</item> - <item name="android:windowExitAnimation">@*android:anim/popup_exit_material</item> - </style> - <style name="TextAppearance.Material.Notification.Parenthetical" parent="@*android:style/TextAppearance.Material.Notification"> <item name="android:textStyle">italic</item> diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index bc7f745241b8..fece07fa9167 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -322,6 +322,10 @@ public class ExpandHelper implements Gefingerpoken { isInside(mScrollAdapter.getHostView(), x, y) && mScrollAdapter.isScrolledToTop(); mResizedView = findView(x, y); + if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) { + mResizedView = null; + mWatchingForPull = false; + } mInitialTouchY = ev.getY(); break; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 065d62ebcb83..f16fb5cd2f34 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -505,6 +505,11 @@ public class KeyguardViewMediator extends SystemUI { public boolean isInputRestricted() { return KeyguardViewMediator.this.isInputRestricted(); } + + @Override + public boolean isScreenOn() { + return mScreenOn; + } }; public void userActivity() { @@ -867,6 +872,12 @@ public class KeyguardViewMediator extends SystemUI { */ private void handleSetOccluded(boolean isOccluded) { synchronized (KeyguardViewMediator.this) { + if (mHiding && isOccluded) { + // We're in the process of going away but WindowManager wants to show a + // SHOW_WHEN_LOCKED activity instead. + startKeyguardExitAnimation(0, 0); + } + if (mOccluded != isOccluded) { mOccluded = isOccluded; mStatusBarKeyguardViewManager.setOccluded(isOccluded); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 1790a4ec7375..5a84db547e1e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs; import android.content.Context; import android.content.Intent; +import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -325,7 +326,7 @@ public abstract class QSTile<TState extends State> implements Listenable { public static class ResourceIcon extends Icon { private static final SparseArray<Icon> ICONS = new SparseArray<Icon>(); - private final int mResId; + protected final int mResId; private ResourceIcon(int resId) { mResId = resId; @@ -342,7 +343,11 @@ public abstract class QSTile<TState extends State> implements Listenable { @Override public Drawable getDrawable(Context context) { - return context.getDrawable(mResId); + Drawable d = context.getDrawable(mResId); + if (d instanceof Animatable) { + ((Animatable) d).start(); + } + return d; } @Override @@ -370,7 +375,7 @@ public abstract class QSTile<TState extends State> implements Listenable { @Override public Drawable getDrawable(Context context) { // workaround: get a clean state for every new AVD - final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context) + final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId) .getConstantState().newDrawable(); d.start(); if (mAllowAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index ec83ca799880..af9d3a5919a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -38,6 +38,7 @@ import android.widget.TextView; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.qs.QSTile.AnimationIcon; import com.android.systemui.qs.QSTile.State; import java.util.Objects; @@ -315,8 +316,9 @@ public class QSTileView extends ViewGroup { iv.setImageDrawable(d); iv.setTag(R.id.qs_icon_tag, state.icon); if (d instanceof Animatable) { - if (!iv.isShown()) { - ((Animatable) d).stop(); // skip directly to end state + Animatable a = (Animatable) d; + if (state.icon instanceof AnimationIcon && !iv.isShown()) { + a.stop(); // skip directly to end state } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index ee607a7ac80c..9f86475fd126 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -96,7 +96,7 @@ import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.HeadsUpNotificationView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -130,9 +130,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; - protected static final int MSG_SHOW_HEADS_UP = 1028; - protected static final int MSG_HIDE_HEADS_UP = 1029; - protected static final int MSG_ESCALATE_HEADS_UP = 1030; protected static final boolean ENABLE_HEADS_UP = true; // scores above this threshold should be displayed in heads up mode. @@ -158,8 +155,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected NotificationGroupManager mGroupManager = new NotificationGroupManager(); // for heads up notifications - protected HeadsUpNotificationView mHeadsUpNotificationView; - protected int mHeadsUpNotificationDecay; + protected HeadsUpManager mHeadsUpManager; protected int mCurrentUserId = 0; final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); @@ -446,9 +442,8 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void run() { processForRemoteInput(sbn.getNotification()); - Notification n = sbn.getNotification(); - boolean isUpdate = mNotificationData.get(sbn.getKey()) != null - || isHeadsUp(sbn.getKey()); + String key = sbn.getKey(); + boolean isUpdate = mNotificationData.get(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since we're not going to show them @@ -462,7 +457,7 @@ public abstract class BaseStatusBar extends SystemUI implements // Remove existing notification to avoid stale data. if (isUpdate) { - removeNotification(sbn.getKey(), rankingMap); + removeNotification(key, rankingMap); } else { mNotificationData.updateRanking(rankingMap); } @@ -693,13 +688,11 @@ public abstract class BaseStatusBar extends SystemUI implements } private void setHeadsUpUser(int newUserId) { - if (mHeadsUpNotificationView != null) { - mHeadsUpNotificationView.setUser(newUserId); - } + mHeadsUpManager.setUser(newUserId); } public boolean isHeadsUp(String key) { - return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key); + return mHeadsUpManager.isHeadsUp(key); } @Override // NotificationData.Environment @@ -758,8 +751,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected View updateNotificationVetoButton(View row, StatusBarNotification n) { View vetoButton = row.findViewById(R.id.veto); - if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null - && mHeadsUpNotificationView.getEntry().row == row)) { + if (n.isClearable()) { final String _pkg = n.getPackageName(); final String _tag = n.getTag(); final int _id = n.getId(); @@ -1002,9 +994,6 @@ public abstract class BaseStatusBar extends SystemUI implements } } - public void onHeadsUpDismissed() { - } - @Override public void showRecentApps(boolean triggeredFromAltTab) { int msg = MSG_SHOW_RECENT_APPS; @@ -1141,13 +1130,10 @@ public abstract class BaseStatusBar extends SystemUI implements // Do nothing } - public abstract void scheduleHeadsUpDecay(long delay); - - public abstract void scheduleHeadsUpOpen(); - - public abstract void scheduleHeadsUpClose(); - - public abstract void scheduleHeadsUpEscalation(); + /** + * if the interrupting notification had a fullscreen intent, fire it now. + */ + public abstract void escalateHeadsUp(); /** * Save the current "public" (locked and secure) state of the lockscreen. @@ -1238,15 +1224,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected void workAroundBadLayerDrawableOpacity(View v) { } - protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { - return inflateViews(entry, parent, false); - } - - protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { - return inflateViews(entry, parent, true); - } - - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { + protected boolean inflateViews(Entry entry, ViewGroup parent) { PackageManager pmUser = getPackageManagerForUser( entry.notification.getUser().getIdentifier()); @@ -1254,12 +1232,7 @@ public abstract class BaseStatusBar extends SystemUI implements final StatusBarNotification sbn = entry.notification; RemoteViews contentView = sbn.getNotification().contentView; RemoteViews bigContentView = sbn.getNotification().bigContentView; - - if (isHeadsUp) { - maxHeight = - mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); - bigContentView = sbn.getNotification().headsUpContentView; - } + RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView; if (contentView == null) { return false; @@ -1284,7 +1257,6 @@ public abstract class BaseStatusBar extends SystemUI implements hasUserChangedExpansion = row.hasUserChangedExpansion(); userExpanded = row.isUserExpanded(); userLocked = row.isUserLocked(); - entry.row.setHeadsUp(isHeadsUp); entry.reset(); if (hasUserChangedExpansion) { row.setUserExpanded(userExpanded); @@ -1307,10 +1279,8 @@ public abstract class BaseStatusBar extends SystemUI implements // NB: the large icon is now handled entirely by the template // bind the click event to the content area - NotificationContentView expanded = - (NotificationContentView) row.findViewById(R.id.expanded); - NotificationContentView expandedPublic = - (NotificationContentView) row.findViewById(R.id.expandedPublic); + NotificationContentView contentContainer = row.getPrivateLayout(); + NotificationContentView contentContainerPublic = row.getPublicLayout(); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { @@ -1328,11 +1298,16 @@ public abstract class BaseStatusBar extends SystemUI implements // set up the adaptive layout View contentViewLocal = null; View bigContentViewLocal = null; + View headsUpContentViewLocal = null; try { - contentViewLocal = contentView.apply(mContext, expanded, + contentViewLocal = contentView.apply(mContext, contentContainer, mOnClickHandler); if (bigContentView != null) { - bigContentViewLocal = bigContentView.apply(mContext, expanded, + bigContentViewLocal = bigContentView.apply(mContext, contentContainer, + mOnClickHandler); + } + if (headsUpContentView != null) { + headsUpContentViewLocal = headsUpContentView.apply(mContext, contentContainer, mOnClickHandler); } } @@ -1344,23 +1319,27 @@ public abstract class BaseStatusBar extends SystemUI implements if (contentViewLocal != null) { contentViewLocal.setIsRootNamespace(true); - expanded.setContractedChild(contentViewLocal); + contentContainer.setContractedChild(contentViewLocal); } if (bigContentViewLocal != null) { bigContentViewLocal.setIsRootNamespace(true); - expanded.setExpandedChild(bigContentViewLocal); + contentContainer.setExpandedChild(bigContentViewLocal); + } + if (headsUpContentViewLocal != null) { + headsUpContentViewLocal.setIsRootNamespace(true); + contentContainer.setHeadsUpChild(headsUpContentViewLocal); } // now the public version View publicViewLocal = null; if (publicNotification != null) { try { - publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, + publicViewLocal = publicNotification.contentView.apply(mContext, contentContainerPublic, mOnClickHandler); if (publicViewLocal != null) { publicViewLocal.setIsRootNamespace(true); - expandedPublic.setContractedChild(publicViewLocal); + contentContainerPublic.setContractedChild(publicViewLocal); } } catch (RuntimeException e) { @@ -1382,9 +1361,9 @@ public abstract class BaseStatusBar extends SystemUI implements // Add a basic notification template publicViewLocal = LayoutInflater.from(mContext).inflate( R.layout.notification_public_default, - expandedPublic, false); + contentContainerPublic, false); publicViewLocal.setIsRootNamespace(true); - expandedPublic.setContractedChild(publicViewLocal); + contentContainerPublic.setContractedChild(publicViewLocal); final TextView title = (TextView) publicViewLocal.findViewById(R.id.title); try { @@ -1520,13 +1499,8 @@ public abstract class BaseStatusBar extends SystemUI implements stripped.contentView = null; stripped.extras.putBoolean("android.rebuild.bigView", true); stripped.bigContentView = null; - - // Don't create the HUN input view for now because input doesn't work there yet. - // TODO: Enable once HUNs can take remote input correctly. - if (false) { - stripped.extras.putBoolean("android.rebuild.hudView", true); - stripped.headsUpContentView = null; - } + stripped.extras.putBoolean("android.rebuild.hudView", true); + stripped.headsUpContentView = null; Notification rebuilt = Notification.Builder.rebuild(mContext, stripped); @@ -1558,16 +1532,23 @@ public abstract class BaseStatusBar extends SystemUI implements } // See if we have somewhere to put that remote input - ViewGroup actionContainer = null; - if (remoteInput != null && entry.expandedBig != null) { - View actionContainerCandidate = entry.expandedBig - .findViewById(com.android.internal.R.id.actions); - if (actionContainerCandidate instanceof ViewGroup) { - actionContainer = (ViewGroup) actionContainerCandidate; + if (remoteInput != null) { + if (entry.expandedBig != null) { + inflateRemoteInput(entry.expandedBig, remoteInput, actions); + } + View headsUpChild = entry.row.getPrivateLayout().getHeadsUpChild(); + if (headsUpChild != null) { + inflateRemoteInput(headsUpChild, remoteInput, actions); } } - if (actionContainer != null) { + } + + private void inflateRemoteInput(View view, RemoteInput remoteInput, + Notification.Action[] actions) { + View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions); + if (actionContainerCandidate instanceof ViewGroup) { + ViewGroup actionContainer = (ViewGroup) actionContainerCandidate; actionContainer.removeAllViews(); actionContainer.addView( RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput)); @@ -1597,12 +1578,12 @@ public abstract class BaseStatusBar extends SystemUI implements mCurrentUserId); dismissKeyguardThenExecute(new OnDismissAction() { public boolean onDismiss() { - if (mNotificationKey.equals(mHeadsUpNotificationView.getKey())) { + if (mHeadsUpManager.isHeadsUp(mNotificationKey)) { // Release the HUN notification to the shade. // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will // become canceled shortly by NoMan, but we can't assume that. - mHeadsUpNotificationView.releaseImmediately(); + mHeadsUpManager.releaseImmediately(mNotificationKey); } new Thread() { @Override @@ -1889,90 +1870,28 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); - boolean wasHeadsUp = isHeadsUp(key); - Entry oldEntry; - if (wasHeadsUp) { - oldEntry = mHeadsUpNotificationView.getEntry(); - } else { - oldEntry = mNotificationData.get(key); - } - if (oldEntry == null) { + Entry entry = mNotificationData.get(key); + if (entry == null) { return; } - final StatusBarNotification oldNotification = oldEntry.notification; - - // XXX: modify when we do something more intelligent with the two content views - final RemoteViews oldContentView = oldNotification.getNotification().contentView; Notification n = notification.getNotification(); - final RemoteViews contentView = n.contentView; - final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; - final RemoteViews bigContentView = n.bigContentView; - final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; - final RemoteViews headsUpContentView = n.headsUpContentView; - final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; - final RemoteViews oldPublicContentView = oldPublicNotification != null - ? oldPublicNotification.contentView : null; - final Notification publicNotification = n.publicVersion; - final RemoteViews publicContentView = publicNotification != null - ? publicNotification.contentView : null; - if (DEBUG) { - Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when - + " ongoing=" + oldNotification.isOngoing() - + " expanded=" + oldEntry.expanded - + " contentView=" + oldContentView - + " bigContentView=" + oldBigContentView - + " publicView=" + oldPublicContentView - + " rowParent=" + oldEntry.row.getParent()); - Log.d(TAG, "new notification: when=" + n.when - + " ongoing=" + oldNotification.isOngoing() - + " contentView=" + contentView - + " bigContentView=" + bigContentView - + " publicView=" + publicContentView); - } - - // Can we just reapply the RemoteViews in place? - - // 1U is never null - boolean contentsUnchanged = oldEntry.expanded != null - && contentView.getPackage() != null - && oldContentView.getPackage() != null - && oldContentView.getPackage().equals(contentView.getPackage()) - && oldContentView.getLayoutId() == contentView.getLayoutId(); - // large view may be null - boolean bigContentsUnchanged = - (oldEntry.getBigContentView() == null && bigContentView == null) - || ((oldEntry.getBigContentView() != null && bigContentView != null) - && bigContentView.getPackage() != null - && oldBigContentView.getPackage() != null - && oldBigContentView.getPackage().equals(bigContentView.getPackage()) - && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); - boolean headsUpContentsUnchanged = - (oldHeadsUpContentView == null && headsUpContentView == null) - || ((oldHeadsUpContentView != null && headsUpContentView != null) - && headsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) - && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); - boolean publicUnchanged = - (oldPublicContentView == null && publicContentView == null) - || ((oldPublicContentView != null && publicContentView != null) - && publicContentView.getPackage() != null - && oldPublicContentView.getPackage() != null - && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) - && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - + logUpdate(entry, n); + } + boolean applyInPlace = shouldApplyInPlace(entry, n); final boolean shouldInterrupt = shouldInterrupt(notification); - final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n); + final boolean alertAgain = alertAgain(entry, n); + + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, entry.notification); + boolean updateSuccessful = false; - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged - && publicUnchanged) { + if (applyInPlace) { + // We can just reapply the notifications in place if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); - oldEntry.notification = notification; - mGroupManager.onEntryUpdated(oldEntry, oldNotification); try { - if (oldEntry.icon != null) { + if (entry.icon != null) { // Update the icon final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), notification.getUser(), @@ -1980,88 +1899,39 @@ public abstract class BaseStatusBar extends SystemUI implements n.iconLevel, n.number, n.tickerText); - oldEntry.icon.setNotification(n); - if (!oldEntry.icon.set(ic)) { + entry.icon.setNotification(n); + if (!entry.icon.set(ic)) { handleNotificationError(notification, "Couldn't update icon: " + ic); return; } } - - if (wasHeadsUp) { - // Release may hang on to the views for a bit, so we should always update them. - updateHeadsUpViews(oldEntry, notification); - mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain); - if (!shouldInterrupt) { - // we updated the notification above, so release to build a new shade entry - mHeadsUpNotificationView.release(); - return; - } - } else { - if (shouldInterrupt && alertAgain) { - mStackScroller.setRemoveAnimationEnabled(false); - removeNotificationViews(key, ranking); - mStackScroller.setRemoveAnimationEnabled(true); - addNotification(notification, ranking, oldEntry); //this will pop the headsup - } else { - updateNotificationViews(oldEntry, notification); - } - } - mNotificationData.updateRanking(ranking); - updateNotifications(); + updateNotificationViews(entry, notification); updateSuccessful = true; } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. - Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); + Log.w(TAG, "Couldn't reapply views for package " + n.contentView.getPackage(), e); } } if (!updateSuccessful) { if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); - if (wasHeadsUp) { - if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key); - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(oldEntry, holder)) { - mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain); - } else { - Log.w(TAG, "Couldn't create new updated headsup for package " - + contentView.getPackage()); - } - if (!shouldInterrupt) { - if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key); - oldEntry.notification = notification; - mGroupManager.onEntryUpdated(oldEntry, oldNotification); - mHeadsUpNotificationView.release(); - return; - } - } else { - if (shouldInterrupt && alertAgain) { - if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key); - mStackScroller.setRemoveAnimationEnabled(false); - removeNotificationViews(key, ranking); - mStackScroller.setRemoveAnimationEnabled(true); - addNotification(notification, ranking, oldEntry); //this will pop the headsup - } else { - if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key); - oldEntry.notification = notification; - mGroupManager.onEntryUpdated(oldEntry, oldNotification); - final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), - notification.getUser(), - n.icon, - n.iconLevel, - n.number, - n.tickerText); - oldEntry.icon.setNotification(n); - oldEntry.icon.set(ic); - inflateViews(oldEntry, mStackScroller, wasHeadsUp); - mNotificationData.updateRanking(ranking); - updateNotifications(); - } - } + final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), + notification.getUser(), + n.icon, + n.iconLevel, + n.number, + n.tickerText); + entry.icon.setNotification(n); + entry.icon.set(ic); + inflateViews(entry, mStackScroller); } + updateHeadsUp(key, entry, shouldInterrupt, alertAgain); + mNotificationData.updateRanking(ranking); + updateNotifications(); // Update the veto button accordingly (and as a result, whether this row is // swipe-dismissable) - updateNotificationVetoButton(oldEntry.row, notification); + updateNotificationVetoButton(entry.row, notification); // Is this for you? boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); @@ -2071,22 +1941,91 @@ public abstract class BaseStatusBar extends SystemUI implements setAreThereNotifications(); } - private void updateNotificationViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, false); + private void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + mHeadsUpManager.updateNotification(entry, alertAgain); + if (!shouldInterrupt) { + // We don't want this to be interrupting anymore, lets remove it + mHeadsUpManager.removeNotification(key); + } + } else if (shouldInterrupt && alertAgain) { + // This notification was updated to be a heads-up, show it! + mHeadsUpManager.showNotification(entry); + } } - private void updateHeadsUpViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, true); + private void logUpdate(Entry oldEntry, Notification n) { + StatusBarNotification oldNotification = oldEntry.notification; + Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when + + " ongoing=" + oldNotification.isOngoing() + + " expanded=" + oldEntry.expanded + + " contentView=" + oldNotification.getNotification().contentView + + " bigContentView=" + oldNotification.getNotification().bigContentView + + " publicView=" + oldNotification.getNotification().publicVersion + + " rowParent=" + oldEntry.row.getParent()); + Log.d(TAG, "new notification: when=" + n.when + + " ongoing=" + oldNotification.isOngoing() + + " contentView=" + n.contentView + + " bigContentView=" + n.bigContentView + + " publicView=" + n.publicVersion); } - private void updateNotificationViews(NotificationData.Entry entry, - StatusBarNotification notification, boolean isHeadsUp) { + /** + * @return whether we can just reapply the RemoteViews in place when it is updated + */ + private boolean shouldApplyInPlace(Entry entry, Notification n) { + StatusBarNotification oldNotification = entry.notification; + // XXX: modify when we do something more intelligent with the two content views + final RemoteViews oldContentView = oldNotification.getNotification().contentView; + final RemoteViews contentView = n.contentView; + final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; + final RemoteViews bigContentView = n.bigContentView; + final RemoteViews oldHeadsUpContentView + = oldNotification.getNotification().headsUpContentView; + final RemoteViews headsUpContentView = n.headsUpContentView; + final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; + final RemoteViews oldPublicContentView = oldPublicNotification != null + ? oldPublicNotification.contentView : null; + final Notification publicNotification = n.publicVersion; + final RemoteViews publicContentView = publicNotification != null + ? publicNotification.contentView : null; + boolean contentsUnchanged = entry.expanded != null + && contentView.getPackage() != null + && oldContentView.getPackage() != null + && oldContentView.getPackage().equals(contentView.getPackage()) + && oldContentView.getLayoutId() == contentView.getLayoutId(); + // large view may be null + boolean bigContentsUnchanged = + (entry.getBigContentView() == null && bigContentView == null) + || ((entry.getBigContentView() != null && bigContentView != null) + && bigContentView.getPackage() != null + && oldBigContentView.getPackage() != null + && oldBigContentView.getPackage().equals(bigContentView.getPackage()) + && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); + boolean headsUpContentsUnchanged = + (oldHeadsUpContentView == null && headsUpContentView == null) + || ((oldHeadsUpContentView != null && headsUpContentView != null) + && headsUpContentView.getPackage() != null + && oldHeadsUpContentView.getPackage() != null + && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) + && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); + boolean publicUnchanged = + (oldPublicContentView == null && publicContentView == null) + || ((oldPublicContentView != null && publicContentView != null) + && publicContentView.getPackage() != null + && oldPublicContentView.getPackage() != null + && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) + && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); + return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged; + } + + private void updateNotificationViews(Entry entry, StatusBarNotification notification) { final RemoteViews contentView = notification.getNotification().contentView; - final RemoteViews bigContentView = isHeadsUp - ? notification.getNotification().headsUpContentView - : notification.getNotification().bigContentView; + final RemoteViews bigContentView = notification.getNotification().bigContentView; + final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView; final Notification publicVersion = notification.getNotification().publicVersion; final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView : null; @@ -2097,6 +2036,10 @@ public abstract class BaseStatusBar extends SystemUI implements bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler); } + View headsUpChild = entry.row.getPrivateLayout().getHeadsUpChild(); + if (headsUpContentView != null && headsUpChild != null) { + headsUpContentView.reapply(mContext, headsUpChild, mOnClickHandler); + } if (publicContentView != null && entry.getPublicContentView() != null) { publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); } @@ -2115,10 +2058,8 @@ public abstract class BaseStatusBar extends SystemUI implements applyRemoteInput(entry); } - protected void notifyHeadsUpScreenOn(boolean screenOn) { - if (!screenOn) { - scheduleHeadsUpEscalation(); - } + protected void notifyHeadsUpScreenOff() { + escalateHeadsUp(); } private boolean alertAgain(Entry oldEntry, Notification newNotification) { @@ -2134,7 +2075,7 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) { + if (mHeadsUpManager.isSnoozed(sbn.getPackageName())) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 06a174e7beb8..cb8217e26f2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -62,7 +62,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private boolean mShowingPublic; private boolean mSensitive; private boolean mShowingPublicInitialized; - private boolean mShowingPublicForIntrinsicHeight; + private boolean mHideSensitiveForIntrinsicHeight; /** * Is this notification expanded by the system. The expansion state can be overridden by the @@ -78,13 +78,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; + private int mHeadsUpHeight; private View mVetoButton; private boolean mClearable; private ExpansionLogger mLogger; private String mLoggingKey; private boolean mWasReset; - private NotificationGuts mGuts; + private NotificationGuts mGuts; private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; private View mExpandButton; @@ -108,6 +109,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { !mChildrenExpanded); } }; + private boolean mInShade; + + public NotificationContentView getPrivateLayout() { + return mPrivateLayout; + } + + public NotificationContentView getPublicLayout() { + return mPublicLayout; + } public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); @@ -118,8 +128,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (layout != null) { View contractedChild = layout.getContractedChild(); View expandedChild = layout.getExpandedChild(); + View headsUpChild = layout.getHeadsUpChild(); setIconAnimationRunningForChild(running, contractedChild); setIconAnimationRunningForChild(running, expandedChild); + setIconAnimationRunningForChild(running, headsUpChild); } } @@ -164,8 +176,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mStatusBarNotification; } + public boolean isHeadsUp() { + return mIsHeadsUp; + } + public void setHeadsUp(boolean isHeadsUp) { + int intrinsicBefore = getIntrinsicHeight(); mIsHeadsUp = isHeadsUp; + mPrivateLayout.setHeadsUp(isHeadsUp); + if (intrinsicBefore != getIntrinsicHeight()) { + notifyHeightChanged(false /* needsAnimation */); + } } public void setGroupManager(NotificationGroupManager groupManager) { @@ -263,6 +284,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return realActualHeight; } + public void setInShade(boolean inShade) { + mInShade = inShade; + } + + public boolean isInShade() { + return mInShade; + } + + public int getHeadsUpHeight() { + return mHeadsUpHeight; + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -299,6 +332,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { resetActualHeight(); } mMaxExpandHeight = 0; + mHeadsUpHeight = 0; mWasReset = true; onHeightReset(); requestLayout(); @@ -536,7 +570,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } boolean inExpansionState = isExpanded(); int maxContentHeight; - if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) { + if (mSensitive && mHideSensitiveForIntrinsicHeight) { + return mRowMinHeight; + } else if (mIsHeadsUp) { + if (inExpansionState) { + maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight); + } else { + maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight); + } + } else if ((!inExpansionState && !mChildrenExpanded)) { maxContentHeight = mRowMinHeight; } else if (mChildrenExpanded) { maxContentHeight = mChildrenContainer.getIntrinsicHeight(); @@ -583,7 +625,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset; - updateMaxExpandHeight(); + updateMaxHeights(); if (updateExpandHeight) { applyExpansionToLayout(); } @@ -599,9 +641,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return super.isChildInvisible(child) || isInvisibleChildContainer; } - private void updateMaxExpandHeight() { + private void updateMaxHeights() { int intrinsicBefore = getIntrinsicHeight(); - mMaxExpandHeight = mPrivateLayout.getMaxHeight(); + View expandedChild = mPrivateLayout.getExpandedChild(); + if (expandedChild == null) { + expandedChild = mPrivateLayout.getContractedChild(); + } + mMaxExpandHeight = expandedChild.getHeight(); + View headsUpChild = mPrivateLayout.getHeadsUpChild(); + if (headsUpChild == null) { + headsUpChild = mPrivateLayout.getContractedChild(); + } + mHeadsUpHeight = headsUpChild.getHeight(); if (intrinsicBefore != getIntrinsicHeight()) { notifyHeightChanged(false /* needsAnimation */); } @@ -612,7 +663,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { - mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive; + mHideSensitiveForIntrinsicHeight = hideSensitive; } public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 7ae0d6dd91af..e632cc837460 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -118,6 +118,7 @@ public abstract class ExpandableView extends FrameLayout { setContentHeight(initialHeight); } } + updateClipping(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 745e75d76132..964d75f525a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -23,10 +23,12 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; + import com.android.systemui.R; /** @@ -37,23 +39,28 @@ import com.android.systemui.R; public class NotificationContentView extends FrameLayout { private static final long ANIMATION_DURATION_LENGTH = 170; + private static final int CONTRACTED = 1; + private static final int EXPANDED = 2; + private static final int HEADSUP = 3; private final Rect mClipBounds = new Rect(); private View mContractedChild; private View mExpandedChild; + private View mHeadsUpChild; private NotificationViewWrapper mContractedWrapper; - private int mSmallHeight; + private final int mSmallHeight; + private final int mHeadsUpHeight; private int mClipTopAmount; + private int mContentHeight; private final Interpolator mLinearInterpolator = new LinearInterpolator(); + private int mVisibleView = CONTRACTED; - private boolean mContractedVisible = true; private boolean mDark; - private final Paint mFadePaint = new Paint(); private boolean mAnimate; private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener @@ -65,14 +72,62 @@ public class NotificationContentView extends FrameLayout { return true; } }; + private boolean mIsHeadsUp; public NotificationContentView(Context context, AttributeSet attrs) { super(context, attrs); mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); + mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height); reset(true); } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; + boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; + int maxSize = Integer.MAX_VALUE; + if (hasFixedHeight || isHeightLimited) { + maxSize = MeasureSpec.getSize(heightMeasureSpec); + } + int maxChildHeight = 0; + if (mContractedChild != null) { + int size = Math.min(maxSize, mSmallHeight); + mContractedChild.measure(widthMeasureSpec, + MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)); + maxChildHeight = Math.max(maxChildHeight, mContractedChild.getMeasuredHeight()); + } + if (mExpandedChild != null) { + int size = maxSize; + ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams(); + if (layoutParams.height >= 0) { + // An actual height is set + size = Math.min(maxSize, layoutParams.height); + } + int spec = size == Integer.MAX_VALUE ? + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) : + MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); + mExpandedChild.measure(widthMeasureSpec, spec); + maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight()); + } + if (mHeadsUpChild != null) { + int size = Math.min(maxSize, mHeadsUpHeight); + ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams(); + if (layoutParams.height >= 0) { + // An actual height is set + size = Math.min(maxSize, layoutParams.height); + } + mHeadsUpChild.measure(widthMeasureSpec, + MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)); + maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight()); + } + int ownHeight = Math.min(maxChildHeight, maxSize); + int width = MeasureSpec.getSize(widthMeasureSpec); + setMeasuredDimension(width, ownHeight); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateClipping(); @@ -91,11 +146,14 @@ public class NotificationContentView extends FrameLayout { if (mExpandedChild != null) { mExpandedChild.animate().cancel(); } + if (mHeadsUpChild != null) { + mHeadsUpChild.animate().cancel(); + } removeAllViews(); mContractedChild = null; mExpandedChild = null; - mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); - mContractedVisible = true; + mHeadsUpChild = null; + mVisibleView = CONTRACTED; if (resetActualHeight) { mContentHeight = mSmallHeight; } @@ -109,12 +167,15 @@ public class NotificationContentView extends FrameLayout { return mExpandedChild; } + public View getHeadsUpChild() { + return mHeadsUpChild; + } + public void setContractedChild(View child) { if (mContractedChild != null) { mContractedChild.animate().cancel(); removeView(mContractedChild); } - sanitizeContractedLayoutParams(child); addView(child); mContractedChild = child; mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child); @@ -132,6 +193,16 @@ public class NotificationContentView extends FrameLayout { selectLayout(false /* animate */, true /* force */); } + public void setHeadsUpChild(View child) { + if (mHeadsUpChild != null) { + mHeadsUpChild.animate().cancel(); + removeView(mHeadsUpChild); + } + addView(child); + mHeadsUpChild = child; + selectLayout(false /* animate */, true /* force */); + } + @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); @@ -166,9 +237,12 @@ public class NotificationContentView extends FrameLayout { } public int getMaxHeight() { - - // The maximum height is just the laid out height. - return getHeight(); + if (mIsHeadsUp && mHeadsUpChild != null) { + return mHeadsUpChild.getHeight(); + } else if (mExpandedChild != null) { + return mExpandedChild.getHeight(); + } + return mSmallHeight; } public int getMinHeight() { @@ -185,62 +259,91 @@ public class NotificationContentView extends FrameLayout { setClipBounds(mClipBounds); } - private void sanitizeContractedLayoutParams(View contractedChild) { - LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); - lp.height = mSmallHeight; - contractedChild.setLayoutParams(lp); - } - private void selectLayout(boolean animate, boolean force) { if (mContractedChild == null) { return; } - boolean showContractedChild = showContractedChild(); - if (showContractedChild != mContractedVisible || force) { + int visibleView = calculateVisibleView(); + if (visibleView != mVisibleView || force) { if (animate && mExpandedChild != null) { - runSwitchAnimation(showContractedChild); - } else if (mExpandedChild != null) { - mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE); - mContractedChild.setAlpha(showContractedChild ? 1f : 0f); - mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE); - mExpandedChild.setAlpha(showContractedChild ? 0f : 1f); + runSwitchAnimation(visibleView); + } else { + updateViewVisibilities(visibleView); } + mVisibleView = visibleView; } - mContractedVisible = showContractedChild; } - private void runSwitchAnimation(final boolean showContractedChild) { - mContractedChild.setVisibility(View.VISIBLE); - mExpandedChild.setVisibility(View.VISIBLE); - mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); - mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); + private void updateViewVisibilities(int visibleView) { + boolean contractedVisible = visibleView == CONTRACTED; + mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE); + mContractedChild.setAlpha(contractedVisible ? 1f : 0f); + mContractedChild.setLayerType(LAYER_TYPE_NONE, null); + if (mExpandedChild != null) { + boolean expandedVisible = visibleView == EXPANDED; + mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE); + mExpandedChild.setAlpha(expandedVisible ? 1f : 0f); + mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); + } + if (mHeadsUpChild != null) { + boolean headsUpVisible = visibleView == HEADSUP; + mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE); + mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f); + mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null); + } + setLayerType(LAYER_TYPE_NONE, null); + } + + private void runSwitchAnimation(int visibleView) { + View shownView = getViewFromFlag(visibleView); + View hiddenView = getViewFromFlag(mVisibleView); + shownView.setVisibility(View.VISIBLE); + hiddenView.setVisibility(View.VISIBLE); + shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); + hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint); setLayerType(LAYER_TYPE_HARDWARE, null); - mContractedChild.animate() - .alpha(showContractedChild ? 1f : 0f) + hiddenView.animate() + .alpha(0f) .setDuration(ANIMATION_DURATION_LENGTH) - .setInterpolator(mLinearInterpolator); - mExpandedChild.animate() - .alpha(showContractedChild ? 0f : 1f) + .setInterpolator(mLinearInterpolator) + .withEndAction(null); // In case we have multiple changes in one frame. + shownView.animate() + .alpha(1f) .setDuration(ANIMATION_DURATION_LENGTH) .setInterpolator(mLinearInterpolator) .withEndAction(new Runnable() { @Override public void run() { - mContractedChild.setLayerType(LAYER_TYPE_NONE, null); - mExpandedChild.setLayerType(LAYER_TYPE_NONE, null); - setLayerType(LAYER_TYPE_NONE, null); - mContractedChild.setVisibility(showContractedChild - ? View.VISIBLE - : View.INVISIBLE); - mExpandedChild.setVisibility(showContractedChild - ? View.INVISIBLE - : View.VISIBLE); + updateViewVisibilities(mVisibleView); } }); } - private boolean showContractedChild() { - return mContentHeight <= mSmallHeight || mExpandedChild == null; + private View getViewFromFlag(int visibleView) { + switch (visibleView) { + case EXPANDED: + return mExpandedChild; + case HEADSUP: + return mHeadsUpChild; + } + return mContractedChild; + } + + private int calculateVisibleView() { + boolean noExpandedChild = mExpandedChild == null; + if (mIsHeadsUp && mHeadsUpChild != null) { + if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) { + return HEADSUP; + } else { + return EXPANDED; + } + } else { + if (mContentHeight <= mSmallHeight || noExpandedChild) { + return CONTRACTED; + } else { + return EXPANDED; + } + } } public void notifyContentUpdated() { @@ -261,6 +364,11 @@ public class NotificationContentView extends FrameLayout { mContractedWrapper.setDark(dark, fade, delay); } + public void setHeadsUp(boolean headsUp) { + mIsHeadsUp = headsUp; + selectLayout(false /* animate */, true /* force */); + } + @Override public boolean hasOverlappingRendering() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 912f414551b1..429889d97bd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -25,6 +25,7 @@ import android.util.ArrayMap; import android.view.View; import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.io.PrintWriter; import java.util.ArrayList; @@ -37,6 +38,7 @@ import java.util.Comparator; public class NotificationData { private final Environment mEnvironment; + private HeadsUpManager mHeadsUpManager; public static final class Entry { public String key; @@ -98,43 +100,47 @@ public class NotificationData { private RankingMap mRankingMap; private final Ranking mTmpRanking = new Ranking(); + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + 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) { - // Upsort current media notification. String mediaNotification = mEnvironment.getCurrentMediaNotificationKey(); boolean aMedia = a.key.equals(mediaNotification); boolean bMedia = b.key.equals(mediaNotification); - if (aMedia != bMedia) { - return aMedia ? -1 : 1; - } final StatusBarNotification na = a.notification; final StatusBarNotification nb = b.notification; - // Upsort PRIORITY_MAX system notifications boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX && isSystemNotification(na); boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX && isSystemNotification(nb); - if (aSystemMax != bSystemMax) { - return aSystemMax ? -1 : 1; - } + int d = nb.getScore() - na.getScore(); - // RankingMap as received from NoMan. - if (mRankingMap != null) { + boolean isHeadsUp = a.row.isHeadsUp(); + if (isHeadsUp != b.row.isHeadsUp()) { + return isHeadsUp ? -1 : 1; + } else if (isHeadsUp) { + // Provide consistent ranking with headsUpManager + return mHeadsUpManager.compare(a, b); + } else if (aMedia != bMedia) { + // Upsort current media notification. + return aMedia ? -1 : 1; + } else if (aSystemMax != bSystemMax) { + // Upsort PRIORITY_MAX system notifications + return aSystemMax ? -1 : 1; + } else if (mRankingMap != null) { + // RankingMap as received from NoMan mRankingMap.getRanking(a.key, mRankingA); mRankingMap.getRanking(b.key, mRankingB); return mRankingA.getRank() - mRankingB.getRank(); - } - - int d = nb.getScore() - na.getScore(); - if (a.interruption != b.interruption) { - return a.interruption ? -1 : 1; - } else if (d != 0) { + } if (d != 0) { return d; } else { return (int) (nb.getNotification().when - na.getNotification().when); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java index a82afcf5cc06..b2bb021be323 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.PorterDuff; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; import android.telephony.SubscriptionInfo; import android.util.AttributeSet; import android.util.Log; @@ -165,12 +167,13 @@ public class SignalClusterView } @Override - public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription, boolean isTypeIconWide, - int subId) { + public void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon, + int typeIcon, String contentDescription, String typeContentDescription, + boolean isTypeIconWide, int subId) { PhoneState state = getOrInflateState(subId); state.mMobileVisible = visible; state.mMobileStrengthId = strengthIcon; + state.mMobileDarkStrengthId = darkStrengthIcon; state.mMobileTypeId = typeIcon; state.mMobileDescription = contentDescription; state.mMobileTypeDescription = typeContentDescription; @@ -360,7 +363,7 @@ public class SignalClusterView private class PhoneState { private final int mSubId; private boolean mMobileVisible = false; - private int mMobileStrengthId = 0, mMobileTypeId = 0; + private int mMobileStrengthId = 0, mMobileDarkStrengthId = 0, mMobileTypeId = 0; private boolean mIsMobileTypeIconWide; private String mMobileDescription, mMobileTypeDescription; @@ -384,7 +387,23 @@ public class SignalClusterView public boolean apply(boolean isSecondaryIcon) { if (mMobileVisible && !mIsAirplaneMode) { mMobile.setImageResource(mMobileStrengthId); + Drawable mobileDrawable = mMobile.getDrawable(); + if (mobileDrawable instanceof Animatable) { + Animatable ad = (Animatable) mobileDrawable; + if (!ad.isRunning()) { + ad.start(); + } + } + mMobileDark.setImageResource(mMobileStrengthId); + Drawable mobileDarkDrawable = mMobileDark.getDrawable(); + if (mobileDarkDrawable instanceof Animatable) { + Animatable ad = (Animatable) mobileDarkDrawable; + if (!ad.isRunning()) { + ad.start(); + } + } + mMobileType.setImageResource(mMobileTypeId); mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription); @@ -401,8 +420,9 @@ public class SignalClusterView mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 0, 0, 0); - if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d", - (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId)); + if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d dark=%d typ=%d", + (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, + mMobileDarkStrengthId, mMobileTypeId)); mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java new file mode 100644 index 000000000000..399780776f20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.systemui.Gefingerpoken; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +/** + * A Helper class to handle touches on the heads-up views + */ +public class HeadsUpTouchHelper implements Gefingerpoken { + + private HeadsUpManager mHeadsUpManager; + private NotificationStackScrollLayout mStackScroller; + private int mTrackingPointer; + private float mTouchSlop; + private float mInitialTouchX; + private float mInitialTouchY; + private boolean mMotionOnHeadsUpView; + private boolean mTrackingHeadsUp; + private boolean mCollapseSnoozes; + private NotificationPanelView mPanel; + private ExpandableNotificationRow mPickedChild; + + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (!mMotionOnHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) { + return false; + } + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mInitialTouchY = y; + mInitialTouchX = x; + setTrackingHeadsUp(false); + ExpandableView child = mStackScroller.getChildAtPosition(x, y); + mMotionOnHeadsUpView = false; + if (child instanceof ExpandableNotificationRow) { + mPickedChild = (ExpandableNotificationRow) child; + mMotionOnHeadsUpView = mPickedChild.isHeadsUp() && !mPickedChild.isInShade(); + } + break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchX = event.getX(newIndex); + mInitialTouchY = event.getY(newIndex); + } + break; + + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + setTrackingHeadsUp(true); + mCollapseSnoozes = h < 0; + mInitialTouchX = x; + mInitialTouchY = y; + int expandedHeight = mPickedChild.getActualHeight(); + mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight); + return true; + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mPickedChild != null && mMotionOnHeadsUpView) { + if (mHeadsUpManager.shouldSwallowClick( + mPickedChild.getStatusBarNotification().getKey())) { + endMotion(); + return true; + } + } + endMotion(); + break; + } + return false; + } + + private void setTrackingHeadsUp(boolean tracking) { + mTrackingHeadsUp = tracking; + mHeadsUpManager.setTrackingHeadsUp(tracking); + mPanel.setTrackingHeadsUp(tracking); + } + + public void notifyFling(boolean collapse) { + if (collapse && mCollapseSnoozes) { + mHeadsUpManager.snooze(); + } + mCollapseSnoozes = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!mTrackingHeadsUp) { + return false; + } + switch (event.getActionMasked()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + endMotion(); + setTrackingHeadsUp(false); + break; + } + return true; + } + + private void endMotion() { + mTrackingPointer = -1; + mPickedChild = null; + mMotionOnHeadsUpView = false; + } + + public ExpandableView getPickedChild() { + return mPickedChild; + } + + public void bind(HeadsUpManager headsUpManager, NotificationStackScrollLayout stackScroller, + NotificationPanelView notificationPanelView) { + mHeadsUpManager = headsUpManager; + mStackScroller = stackScroller; + mPanel = notificationPanelView; + Context context = stackScroller.getContext(); + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = configuration.getScaledTouchSlop(); + } +} 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 02b6c1995f28..c2664679d9e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; @@ -39,16 +40,19 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.android.keyguard.KeyguardStatusView; -import com.android.systemui.EventLogTags; import com.android.systemui.EventLogConstants; +import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.qs.QSContainer; import com.android.systemui.qs.QSPanel; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyguardAffordanceView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -56,7 +60,8 @@ import com.android.systemui.statusbar.stack.StackStateAnimator; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, - KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener { + KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener, + HeadsUpManager.OnHeadsUpChangedListener { private static final boolean DEBUG = false; @@ -81,7 +86,7 @@ public class NotificationPanelView extends PanelView implements private TextView mClockView; private View mReserveNotificationSpace; private View mQsNavbarScrim; - private View mNotificationContainerParent; + private NotificationsQuickSettingsContainer mNotificationContainerParent; private NotificationStackScrollLayout mNotificationStackScroller; private int mNotificationTopPadding; private boolean mAnimateNextTopPaddingChange; @@ -177,6 +182,17 @@ public class NotificationPanelView extends PanelView implements private float mKeyguardStatusBarAnimateAlpha = 1f; private int mOldLayoutDirection; + private HeadsUpTouchHelper mHeadsUpTouchHelper = new HeadsUpTouchHelper(); + private boolean mPinnedHeadsUpExist; + private boolean mExpansionIsFromHeadsUp; + private int mBottomBarHeight; + private boolean mExpandingFromHeadsUp; + private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { + @Override + public void run() { + notifyBarPanelExpansionChanged(); + } + }; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -201,7 +217,8 @@ public class NotificationPanelView extends PanelView implements mScrollView.setListener(this); mScrollView.setFocusable(false); mReserveNotificationSpace = findViewById(R.id.reserve_notification_space); - mNotificationContainerParent = findViewById(R.id.notification_container_parent); + mNotificationContainerParent = (NotificationsQuickSettingsContainer) + findViewById(R.id.notification_container_parent); mNotificationStackScroller = (NotificationStackScrollLayout) findViewById(R.id.notification_stack_scroller); mNotificationStackScroller.setOnHeightChangedListener(this); @@ -317,6 +334,7 @@ public class NotificationPanelView extends PanelView implements if (mQsSizeChangeAnimator == null) { mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); } + updateMaxHeadsUpTranslation(); } @Override @@ -493,22 +511,39 @@ public class NotificationPanelView extends PanelView implements } @Override + protected void flingToHeight(float vel, boolean expand, float target) { + mHeadsUpTouchHelper.notifyFling(!expand); + super.flingToHeight(vel, expand, target); + } + + @Override public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { event.getText().add(getKeyguardOrLockScreenString()); mLastAnnouncementWasQuickSettings = false; return true; } - return super.dispatchPopulateAccessibilityEventInternal(event); } @Override - public boolean onInterceptTouchEvent(MotionEvent event) { + public boolean + onInterceptTouchEvent(MotionEvent event) { if (mBlockTouches) { return false; } initDownStates(event); + if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { + mExpansionIsFromHeadsUp = true; + return true; + } + if (!isShadeCollapsed() && onQsIntercept(event)) { + return true; + } + return super.onInterceptTouchEvent(event); + } + + private boolean onQsIntercept(MotionEvent event) { int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -583,7 +618,7 @@ public class NotificationPanelView extends PanelView implements mIntercepting = false; break; } - return super.onInterceptTouchEvent(event); + return false; } @Override @@ -652,6 +687,15 @@ public class NotificationPanelView extends PanelView implements if (mOnlyAffordanceInThisMotion) { return true; } + mHeadsUpTouchHelper.onTouchEvent(event); + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQSTouch(event)) { + return true; + } + super.onTouchEvent(event); + return true; + } + + private boolean handleQSTouch(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) { @@ -664,7 +708,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchY = event.getX(); mInitialTouchX = event.getY(); } - if (mExpandedHeight != 0) { + if (!isShadeCollapsed()) { handleQsDown(event); } if (!mQsExpandImmediate && mQsTracking) { @@ -677,7 +721,7 @@ public class NotificationPanelView extends PanelView implements || event.getActionMasked() == MotionEvent.ACTION_UP) { mConflictingQsExpansionGesture = false; } - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0 + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isShadeCollapsed() && mQsExpansionEnabled) { mTwoFingerQsExpandPossible = true; } @@ -691,8 +735,7 @@ public class NotificationPanelView extends PanelView implements // earlier so the state is already up to date when dragging down. setListening(true); } - super.onTouchEvent(event); - return true; + return false; } private boolean isInQsArea(float x, float y) { @@ -834,13 +877,13 @@ public class NotificationPanelView extends PanelView implements setQsExpansion(mQsExpansionHeight); flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, new Runnable() { - @Override - public void run() { - mStackScrollerOverscrolling = false; - mQsExpansionFromOverscroll = false; - updateQsState(); - } - }); + @Override + public void run() { + mStackScrollerOverscrolling = false; + mQsExpansionFromOverscroll = false; + updateQsState(); + } + }); } private void onQsExpansionStarted() { @@ -869,6 +912,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setInterceptDelegateEnabled(expanded); mStatusBar.setQsExpanded(expanded); mQsPanel.setExpanded(expanded); + mNotificationContainerParent.setQsExpanded(expanded); } } @@ -1056,7 +1100,7 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea.animate() .alpha(0f) .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) - .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) + .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2) .setInterpolator(PhoneStatusBar.ALPHA_OUT) .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) .start(); @@ -1132,8 +1176,8 @@ public class NotificationPanelView extends PanelView implements updateEmptyShadeView(); mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling && mQsScrimEnabled - ? View.VISIBLE - : View.INVISIBLE); + ? View.VISIBLE + : View.INVISIBLE); if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); } @@ -1402,6 +1446,8 @@ public class NotificationPanelView extends PanelView implements updateHeader(); updateUnlockIcon(); updateNotificationTranslucency(); + mHeadsUpManager.setIsExpanded(!isShadeCollapsed()); + mNotificationStackScroller.setShadeExpanded(!isShadeCollapsed()); if (DEBUG) { invalidate(); } @@ -1471,16 +1517,24 @@ public class NotificationPanelView extends PanelView implements } } private void updateNotificationTranslucency() { - float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) - / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() - - mNotificationStackScroller.getCollapseSecondCardPadding()); - alpha = Math.max(0, Math.min(alpha, 1)); - alpha = (float) Math.pow(alpha, 0.75); - if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { - mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); - } else if (alpha == 1f - && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { - mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); + float alpha; + if (mExpandingFromHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) { + alpha = 1f; + if (mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { + mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); + } + } else { + alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight()) + / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() + - mNotificationStackScroller.getCollapseSecondCardPadding()); + alpha = Math.max(0, Math.min(alpha, 1)); + alpha = (float) Math.pow(alpha, 0.75); + if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) { + mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null); + } else if (alpha == 1f + && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) { + mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null); + } } mNotificationStackScroller.setAlpha(alpha); } @@ -1544,7 +1598,13 @@ public class NotificationPanelView extends PanelView implements return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; } } - return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; + float stackTranslation = mNotificationStackScroller.getStackTranslation(); + float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR; + if (mHeadsUpManager.hasPinnedHeadsUp() || mExpansionIsFromHeadsUp) { + translation = mNotificationStackScroller.getTopPadding() + stackTranslation + - mNotificationTopPadding - mQsMinExpansionHeight; + } + return Math.min(0, translation); } /** @@ -1605,15 +1665,19 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mHeadsUpManager.onExpandingFinished(); mIsExpanding = false; mScrollYOverride = -1; - if (mExpandedHeight == 0f) { + if (isShadeCollapsed()) { setListening(false); } else { setListening(true); } mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; + mExpansionIsFromHeadsUp = false; + mNotificationStackScroller.setTrackingHeadsUp(mHeadsUpTouchHelper.isTrackingHeadsUp()); + mExpandingFromHeadsUp = mHeadsUpTouchHelper.isTrackingHeadsUp(); } private void setListening(boolean listening) { @@ -1709,6 +1773,17 @@ public class NotificationPanelView extends PanelView implements } @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mBottomBarHeight = insets.getSystemWindowInsetBottom(); + updateMaxHeadsUpTranslation(); + return insets; + } + + private void updateMaxHeadsUpTranslation() { + mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mBottomBarHeight); + } + + @Override public void onRtlPropertiesChanged(int layoutDirection) { if (layoutDirection != mOldLayoutDirection) { mAfforanceHelper.onRtlPropertiesChanged(); @@ -2066,4 +2141,49 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.getTopPadding(), p); } } + + @Override + public void OnPinnedHeadsUpExistChanged(final boolean exist, boolean changeImmediatly) { + if (exist != mPinnedHeadsUpExist) { + mPinnedHeadsUpExist = exist; + if (exist) { + mHeadsUpExistenceChangedRunnable.run(); + updateNotificationTranslucency(); + } else { + mNotificationStackScroller.performOnAnimationFinished( + mHeadsUpExistenceChangedRunnable); + } + } + } + + @Override + public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) { + if (isHeadsUp) { + mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true); + } + } + + @Override + public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp); + } + + @Override + protected boolean isShadeCollapsed() { + return mExpandedHeight == 0; + } + + @Override + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + super.setHeadsUpManager(headsUpManager); + mHeadsUpTouchHelper.bind(headsUpManager, mNotificationStackScroller, this); + } + + public void setTrackingHeadsUp(boolean tracking) { + if (tracking) { + // otherwise we update the state when the expansion is finished + mNotificationStackScroller.setTrackingHeadsUp(true); + mExpandingFromHeadsUp = true; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index a03c297f1032..cbb71c5d97e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -37,6 +37,7 @@ public class NotificationsQuickSettingsContainer extends FrameLayout private View mStackScroller; private View mKeyguardStatusBar; private boolean mInflated; + private boolean mQsExpanded; public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -64,26 +65,29 @@ public class NotificationsQuickSettingsContainer extends FrameLayout boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE; boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE; + View stackQsTop = mQsExpanded ? mStackScroller : mScrollView; + View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView; // Invert the order of the scroll view and user switcher such that the notifications receive // touches first but the panel gets drawn above. if (child == mScrollView) { - return super.drawChild(canvas, mStackScroller, drawingTime); - } else if (child == mStackScroller) { - return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mUserSwitcher + return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher : statusBarVisible ? mKeyguardStatusBar : userSwitcherVisible ? mUserSwitcher - : mScrollView, + : stackQsBottom, drawingTime); + } else if (child == mStackScroller) { + return super.drawChild(canvas, + userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar + : statusBarVisible || userSwitcherVisible ? stackQsBottom + : stackQsTop, drawingTime); } else if (child == mUserSwitcher) { return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar - : mScrollView, + userSwitcherVisible && statusBarVisible ? stackQsBottom + : stackQsTop, drawingTime); } else if (child == mKeyguardStatusBar) { return super.drawChild(canvas, - userSwitcherVisible && statusBarVisible ? mScrollView - : mScrollView, + stackQsTop, drawingTime); }else { return super.drawChild(canvas, child, drawingTime); @@ -97,4 +101,11 @@ public class NotificationsQuickSettingsContainer extends FrameLayout mInflated = true; } } + + public void setQsExpanded(boolean expanded) { + if (mQsExpanded != expanded) { + mQsExpanded = expanded; + invalidate(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index c6e1be9fd240..240438a74394 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -157,8 +157,7 @@ public class PanelBar extends FrameLayout { if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName()); mPanelExpandedFractionSum = 0f; for (PanelView pv : mPanels) { - boolean visible = pv.getExpandedHeight() > 0; - pv.setVisibility(visible ? View.VISIBLE : View.GONE); + pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); // adjust any other panels that may be partially visible if (expanded) { if (mState == STATE_CLOSED) { @@ -167,7 +166,7 @@ public class PanelBar extends FrameLayout { } fullyClosed = false; final float thisFrac = pv.getExpandedFraction(); - mPanelExpandedFractionSum += (visible ? thisFrac : 0); + mPanelExpandedFractionSum += thisFrac; if (DEBUG) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac); if (panel == pv) { if (thisFrac == 1f) fullyOpenedPanel = panel; @@ -196,7 +195,6 @@ public class PanelBar extends FrameLayout { } else { pv.resetViews(); pv.setExpandedFraction(0); // just in case - pv.setVisibility(View.GONE); pv.cancelPeek(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 4bbf6904c05b..ddce9d51dec0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -38,6 +38,7 @@ import com.android.systemui.R; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -45,6 +46,7 @@ import java.io.PrintWriter; public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); + protected HeadsUpManager mHeadsUpManager; private final void logf(String fmt, Object... args) { Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); @@ -232,18 +234,15 @@ public abstract class PanelView extends FrameLayout { final float y = event.getY(pointerIndex); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mGestureWaitForTouchSlop = mExpandedHeight == 0f; + mGestureWaitForTouchSlop = isShadeCollapsed(); } boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - mInitialTouchY = y; - mInitialTouchX = x; - mInitialOffsetOnTouch = mExpandedHeight; - mTouchSlopExceeded = false; + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mJustPeeked = false; - mPanelClosedOnDown = mExpandedHeight == 0.0f; + mPanelClosedOnDown = isShadeCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; @@ -261,7 +260,7 @@ public abstract class PanelView extends FrameLayout { || mPeekPending || mPeekAnimator != null; onTrackingStarted(); } - if (mExpandedHeight == 0) { + if (isShadeCollapsed()) { schedulePeek(); } break; @@ -274,9 +273,7 @@ public abstract class PanelView extends FrameLayout { final float newY = event.getY(newIndex); final float newX = event.getX(newIndex); mTrackingPointer = event.getPointerId(newIndex); - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; + startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight); } break; case MotionEvent.ACTION_POINTER_DOWN: @@ -297,9 +294,7 @@ public abstract class PanelView extends FrameLayout { mTouchSlopExceeded = true; if (waitForTouchSlop && !mTracking) { if (!mJustPeeked && mInitialOffsetOnTouch != 0f) { - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchX = x; - mInitialTouchY = y; + startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); h = 0; } cancelHeightAnimator(); @@ -334,6 +329,17 @@ public abstract class PanelView extends FrameLayout { return !waitForTouchSlop || mTracking; } + protected void startExpandMotion(float newX, float newY, boolean startTracking, + float expandedHeight) { + mInitialOffsetOnTouch = expandedHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + if (startTracking) { + mTouchSlopExceeded = true; + onTrackingStarted(); + } + } + private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { mTrackingPointer = -1; if ((mTracking && mTouchSlopExceeded) @@ -442,7 +448,7 @@ public abstract class PanelView extends FrameLayout { mTouchSlopExceeded = false; mJustPeeked = false; mMotionAborted = false; - mPanelClosedOnDown = mExpandedHeight == 0.0f; + mPanelClosedOnDown = isShadeCollapsed(); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mTouchAboveFalsingThreshold = false; @@ -474,12 +480,7 @@ public abstract class PanelView extends FrameLayout { if (scrolledToBottom || mTouchStartedInEmptyArea) { if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); - mInitialOffsetOnTouch = mExpandedHeight; - mInitialTouchY = y; - mInitialTouchX = x; - mTracking = true; - mTouchSlopExceeded = true; - onTrackingStarted(); + startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); return true; } } @@ -564,7 +565,10 @@ public abstract class PanelView extends FrameLayout { protected void fling(float vel, boolean expand) { cancelPeek(); float target = expand ? getMaxPanelHeight() : 0.0f; + flingToHeight(vel, expand, target); + } + protected void flingToHeight(float vel, boolean expand, float target) { // Hack to make the expand transition look nice when clear all button is visible - we make // the animation only to the last notification, and then jump to the maximum panel height so // clear all just fades in and the decelerating motion is towards the last notification. @@ -644,7 +648,7 @@ public abstract class PanelView extends FrameLayout { mHasLayoutedSinceDown = true; if (mUpdateFlingOnLayout) { abortAnimations(); - fling(mUpdateFlingVelocity, true); + fling(mUpdateFlingVelocity, true /* expands */); mUpdateFlingOnLayout = false; } } @@ -655,7 +659,7 @@ public abstract class PanelView extends FrameLayout { // If the user isn't actively poking us, let's update the height if ((!mTracking || isTrackingBlocked()) && mHeightAnimator == null - && mExpandedHeight > 0 + && !isShadeCollapsed() && currentMaxPanelHeight != mExpandedHeight && !mPeekPending && mPeekAnimator == null @@ -805,7 +809,7 @@ public abstract class PanelView extends FrameLayout { if (mExpanding) { notifyExpandingFinished(); } - setVisibility(VISIBLE); + notifyBarPanelExpansionChanged(); // Wait for window manager to pickup the change, so we know the maximum height of the panel // then. @@ -941,9 +945,9 @@ public abstract class PanelView extends FrameLayout { return animator; } - private void notifyBarPanelExpansionChanged() { + protected void notifyBarPanelExpansionChanged() { mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending - || mPeekAnimator != null); + || mPeekAnimator != null || mInstantExpanding || mHeadsUpManager.hasPinnedHeadsUp()); } /** @@ -1014,4 +1018,10 @@ public abstract class PanelView extends FrameLayout { * @return the height of the clear all button, in pixels */ protected abstract int getClearAllHeight(); + + protected abstract boolean isShadeCollapsed(); + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } } 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 b0d61785b20d..a5dad923b776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -17,19 +17,6 @@ package com.android.systemui.statusbar.phone; -import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.app.StatusBarManager.windowStateToString; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; @@ -86,13 +73,11 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; -import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewStub; import android.view.WindowManager; @@ -115,13 +100,13 @@ import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.assist.AssistGestureManager; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.qs.QSPanel; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.statusbar.ActivatableNotificationView; -import com.android.systemui.assist.AssistGestureManager; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -137,7 +122,6 @@ import com.android.systemui.statusbar.NotificationOverflowContainer; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.SpeedBumpView; -import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.AccessibilityController; @@ -147,7 +131,7 @@ import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.CastControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.HeadsUpNotificationView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.KeyButtonView; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -172,11 +156,27 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.TreeSet; + +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static android.app.StatusBarManager.windowStateToString; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; public class PhoneStatusBar extends BaseStatusBar implements DemoMode, - DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener { + DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, + HeadsUpManager.OnHeadsUpChangedListener { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = BaseStatusBar.DEBUG; public static final boolean SPEW = false; @@ -360,11 +360,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (wasUsing != mUseHeadsUp) { if (!mUseHeadsUp) { Log.d(TAG, "dismissing any existing heads up notification on disable event"); - setHeadsUpVisibility(false); - mHeadsUpNotificationView.releaseImmediately(); - removeHeadsUpView(); - } else { - addHeadsUpView(); + mHeadsUpManager.releaseAllImmediately(); } } } @@ -528,6 +524,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap = new HashMap<>(); + private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>(); + private RankingMap mLatestRankingMap; @Override public void start() { @@ -598,7 +596,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } return mStatusBarWindow.onTouchEvent(event); - }}); + } + }); mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar); mStatusBarView.setBar(this); @@ -615,12 +614,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNotificationPanel.setBackground(new FastColorDrawable(context.getColor( R.color.notification_panel_solid_background))); } - if (ENABLE_HEADS_UP) { - mHeadsUpNotificationView = - (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null); - mHeadsUpNotificationView.setVisibility(View.GONE); - mHeadsUpNotificationView.setBar(this); - } + + mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver()); + mHeadsUpManager.setBar(this); + mHeadsUpManager.addListener(this); + mHeadsUpManager.addListener(mNotificationPanel); + mNotificationPanel.setHeadsUpManager(mHeadsUpManager); + mNotificationData.setHeadsUpManager(mHeadsUpManager); + if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById( R.id.header_debug_info); @@ -667,6 +668,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setLongPressListener(getNotificationLongClicker()); mStackScroller.setPhoneStatusBar(this); mStackScroller.setGroupManager(mGroupManager); + mStackScroller.setHeadsUpManager(mHeadsUpManager); mGroupManager.setOnGroupChangeListener(mStackScroller); mKeyguardIconOverflowContainer = @@ -699,7 +701,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind); ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front); - mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled); + View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); + mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim, + mScrimSrcModeEnabled); + mHeadsUpManager.addListener(mScrimController); + mStackScroller.setScrimController(mScrimController); mScrimController.setBackDropView(mBackdrop); mStatusBarView.setScrimController(mScrimController); mDozeScrimController = new DozeScrimController(mScrimController, context); @@ -1084,32 +1090,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return lp; } - private void addHeadsUpView() { - int headsUpHeight = mContext.getResources() - .getDimensionPixelSize(R.dimen.heads_up_window_height); - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, headsUpHeight, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar! - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - lp.gravity = Gravity.TOP; - lp.setTitle("Heads Up"); - lp.packageName = mContext.getPackageName(); - lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp; - - mWindowManager.addView(mHeadsUpNotificationView, lp); - } - - private void removeHeadsUpView() { - mWindowManager.removeView(mHeadsUpNotificationView); - } - public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { mIconController.addSystemIcon(slot, index, viewIndex, icon); } @@ -1131,32 +1111,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void addNotification(StatusBarNotification notification, RankingMap ranking, Entry oldEntry) { if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey()); - if (mUseHeadsUp && shouldInterrupt(notification)) { - if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); - Entry interruptionCandidate = oldEntry; - if (interruptionCandidate == null) { - final StatusBarIconView iconView = createIcon(notification); - if (iconView == null) { - return; - } - interruptionCandidate = new Entry(notification, iconView); - } - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { - // 1. Populate mHeadsUpNotificationView - mHeadsUpNotificationView.showNotification(interruptionCandidate); - - // do not show the notification in the shade, yet. - return; - } - } Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } + boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(notification); + if (isHeadsUped) { + mHeadsUpManager.showNotification(shadeEntry); + } - if (notification.getNotification().fullScreenIntent != null) { + if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { // Stop screensaver if the notification has a full-screen intent. // (like an incoming phone call) awakenDreams(); @@ -1175,18 +1140,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, setAreThereNotifications(); } - public void displayNotificationFromHeadsUp(Entry shadeEntry) { - - // The notification comes from the headsup, let's inflate the normal layout again - inflateViews(shadeEntry, mStackScroller); - shadeEntry.setInterruption(); - shadeEntry.row.setHeadsUp(false); - - addNotificationViews(shadeEntry, null); - // Recalculate the position of the sliding windows and the titles. - setAreThereNotifications(); - } - @Override protected void updateNotificationRanking(RankingMap ranking) { mNotificationData.updateRanking(ranking); @@ -1195,10 +1148,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void removeNotification(String key, RankingMap ranking) { - if (ENABLE_HEADS_UP) { - mHeadsUpNotificationView.removeNotification(key); + boolean defferRemoval = false; + if (mHeadsUpManager.isHeadsUp(key)) { + defferRemoval = !mHeadsUpManager.removeNotification(key); + } + if (defferRemoval) { + mLatestRankingMap = ranking; + mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); + return; } - StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); @@ -1870,6 +1828,45 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, logStateToEventlog(); } + @Override + public void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly) { + if (exist) { + mStatusBarWindowManager.setHeadsUpShowing(true); + } else { + Runnable endRunnable = new Runnable() { + @Override + public void run() { + if (!mHeadsUpManager.hasPinnedHeadsUp()) { + mStatusBarWindowManager.setHeadsUpShowing(false); + } + } + }; + if (changeImmediatly) { + endRunnable.run(); + } else { + mStackScroller.performOnAnimationFinished(endRunnable); + } + } + } + + @Override + public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) { + } + + @Override + public void OnHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, mLatestRankingMap); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + mLatestRankingMap = null; + } + } else { + updateNotificationRanking(null); + } + + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -1886,15 +1883,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, case MSG_CLOSE_PANELS: animateCollapsePanels(); break; - case MSG_SHOW_HEADS_UP: - setHeadsUpVisibility(true); - break; - case MSG_ESCALATE_HEADS_UP: - escalateHeadsUp(); - case MSG_HIDE_HEADS_UP: - mHeadsUpNotificationView.releaseImmediately(); - setHeadsUpVisibility(false); - break; case MSG_LAUNCH_TRANSITION_TIMEOUT: onLaunchTransitionTimeout(); break; @@ -1903,44 +1891,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void scheduleHeadsUpDecay(long delay) { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - if (mHeadsUpNotificationView.isClearable()) { - mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, delay); - } - } - - @Override - public void scheduleHeadsUpOpen() { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - mHandler.removeMessages(MSG_SHOW_HEADS_UP); - mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); - } - - @Override - public void scheduleHeadsUpClose() { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - if (mHeadsUpNotificationView.getVisibility() != View.GONE) { - mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); - } - } - - @Override - public void scheduleHeadsUpEscalation() { - mHandler.removeMessages(MSG_HIDE_HEADS_UP); - mHandler.removeMessages(MSG_ESCALATE_HEADS_UP); - mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); - } - - /** if the interrupting notification had a fullscreen intent, fire it now. */ - private void escalateHeadsUp() { - if (mHeadsUpNotificationView.getEntry() != null) { - final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification; - mHeadsUpNotificationView.releaseImmediately(); + public void escalateHeadsUp() { + TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries(); + for (HeadsUpManager.HeadsUpEntry entry : entries) { + final StatusBarNotification sbn = entry.entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { - if (DEBUG) + if (DEBUG) { Log.d(TAG, "converting a heads up to fullScreen"); + } try { EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); @@ -1949,6 +1908,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } } + mHeadsUpManager.releaseAllImmediately(); } boolean panelsEnabled() { @@ -2081,10 +2041,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/); - // reset things to their proper state - mStackScroller.setVisibility(View.VISIBLE); - mNotificationPanel.setVisibility(View.GONE); - mNotificationPanel.closeQs(); mExpandedVisible = false; @@ -2478,8 +2434,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, pw.println(Settings.Global.zenModeToString(mZenMode)); pw.print(" mUseHeadsUp="); pw.println(mUseHeadsUp); - pw.print(" interrupting package: "); - pw.println(hunStateToString(mHeadsUpNotificationView.getEntry())); dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { pw.print(" mNavigationBarWindowState="); @@ -2571,10 +2525,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mSecurityController != null) { mSecurityController.dump(fd, pw, args); } - if (mHeadsUpNotificationView != null) { - mHeadsUpNotificationView.dump(fd, pw, args); + if (mHeadsUpManager != null) { + mHeadsUpManager.dump(fd, pw, args); } else { - pw.println(" mHeadsUpNotificationView: null"); + pw.println(" mHeadsUpManager: null"); } pw.println("SharedPreferences:"); @@ -2672,7 +2626,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; notifyNavigationBarScreenOn(false); - notifyHeadsUpScreenOn(false); + notifyHeadsUpScreenOff(); finishBarAnimations(); resetUserExpandedStates(); } @@ -2764,15 +2718,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mUserSetupObserver, mCurrentUserId); } - private void setHeadsUpVisibility(boolean vis) { - if (!ENABLE_HEADS_UP) return; - if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window"); - EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_STATUS, - vis ? mHeadsUpNotificationView.getKey() : "", - vis ? 1 : 0); - mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE); - } - /** * Reload some of our resources when the configuration changes. * @@ -2791,9 +2736,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mNotificationPanel != null) { mNotificationPanel.updateResources(); } - if (mHeadsUpNotificationView != null) { - mHeadsUpNotificationView.updateResources(); - } if (mBrightnessMirrorController != null) { mBrightnessMirrorController.updateResources(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0e8a7942d1b3..e70178350e71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Color; @@ -29,13 +30,18 @@ import android.view.animation.Interpolator; import com.android.systemui.R; import com.android.systemui.statusbar.BackDropView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). */ -public class ScrimController implements ViewTreeObserver.OnPreDrawListener { +public class ScrimController implements ViewTreeObserver.OnPreDrawListener, + HeadsUpManager.OnHeadsUpChangedListener { public static final long ANIMATION_DURATION = 220; private static final float SCRIM_BEHIND_ALPHA = 0.62f; @@ -43,10 +49,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; private static final float SCRIM_IN_FRONT_ALPHA = 0.75f; private static final int TAG_KEY_ANIM = R.id.scrim; + private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start; + private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end; private final ScrimView mScrimBehind; private final ScrimView mScrimInFront; private final UnlockMethodCache mUnlockMethodCache; + private final View mHeadsUpScrim; private boolean mKeyguardShowing; private float mFraction; @@ -70,15 +79,22 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private float mDozeBehindAlpha; private float mCurrentInFrontAlpha; private float mCurrentBehindAlpha; + private float mCurrentHeadsUpAlpha = 1; + private int mAmountOfPinnedHeadsUps; + private float mTopHeadsUpDragAmount; + private View mDraggedHeadsUpView; - public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) { + public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, + boolean scrimSrcEnabled) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; + mHeadsUpScrim = headsUpScrim; final Context context = scrimBehind.getContext(); mUnlockMethodCache = UnlockMethodCache.getInstance(context); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); mScrimSrcEnabled = scrimSrcEnabled; + updateHeadsUpScrim(false); } public void setKeyguardShowing(boolean showing) { @@ -217,7 +233,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } } - private void setScrimColor(ScrimView scrim, float alpha) { + private void setScrimColor(View scrim, float alpha) { Object runningAnim = scrim.getTag(TAG_KEY_ANIM); if (runningAnim instanceof ValueAnimator) { ((ValueAnimator) runningAnim).cancel(); @@ -236,25 +252,34 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } private float getCurrentScrimAlpha(View scrim) { - return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha; + return scrim == mScrimBehind ? mCurrentBehindAlpha + : scrim == mScrimInFront ? mCurrentInFrontAlpha + : mCurrentHeadsUpAlpha; } private void setCurrentScrimAlpha(View scrim, float alpha) { if (scrim == mScrimBehind) { mCurrentBehindAlpha = alpha; - } else { + } else if (scrim == mScrimInFront) { mCurrentInFrontAlpha = alpha; + } else { + alpha = Math.max(0.0f, Math.min(1.0f, alpha)); + mCurrentHeadsUpAlpha = alpha; } } - private void updateScrimColor(ScrimView scrim) { + private void updateScrimColor(View scrim) { float alpha1 = getCurrentScrimAlpha(scrim); - float alpha2 = getDozeAlpha(scrim); - float alpha = 1 - (1 - alpha1) * (1 - alpha2); - scrim.setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); + if (scrim instanceof ScrimView) { + float alpha2 = getDozeAlpha(scrim); + float alpha = 1 - (1 - alpha1) * (1 - alpha2); + ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0)); + } else { + scrim.setAlpha(alpha1); + } } - private void startScrimAnimation(final ScrimView scrim, float target) { + private void startScrimAnimation(final View scrim, float target) { float current = getCurrentScrimAlpha(scrim); ValueAnimator anim = ValueAnimator.ofFloat(current, target); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -320,4 +345,84 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled; mScrimBehind.setDrawAsSrc(asSrc); } + + @Override + public void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly) { + } + + @Override + public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) { + if (isHeadsUp) { + mAmountOfPinnedHeadsUps++; + } else { + mAmountOfPinnedHeadsUps--; + if (headsUp == mDraggedHeadsUpView) { + mDraggedHeadsUpView = null; + mTopHeadsUpDragAmount = 0.0f; + } + } + updateHeadsUpScrim(true); + } + + @Override + public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + } + + private void updateHeadsUpScrim(boolean animate) { + float alpha = calculateHeadsUpAlpha(); + ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_KEY_ANIM); + float animEndValue = -1; + if (previousAnimator != null) { + if ((animate || alpha == mCurrentHeadsUpAlpha)) { + // lets cancel any running animators + previousAnimator.cancel(); + } + animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_HUN_START_ALPHA); + } + if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) { + if (animate) { + startScrimAnimation(mHeadsUpScrim, alpha); + mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha); + mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha); + } else { + if (previousAnimator != null) { + float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_HUN_START_ALPHA); + float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, + TAG_HUN_END_ALPHA); + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + float relativeDiff = alpha - previousEndValue; + float newStartValue = previousStartValue + relativeDiff; + values[0].setFloatValues(newStartValue, alpha); + mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue); + mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + } else { + // update the alpha directly + setCurrentScrimAlpha(mHeadsUpScrim, alpha); + updateScrimColor(mHeadsUpScrim); + } + } + } + } + + public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) { + mTopHeadsUpDragAmount = topHeadsUpDragAmount; + mDraggedHeadsUpView = draggedHeadsUpView; + updateHeadsUpScrim(false); + } + + private float calculateHeadsUpAlpha() { + if (mAmountOfPinnedHeadsUps >= 2) { + return 1.0f; + } else if (mAmountOfPinnedHeadsUps == 0) { + return 0.0f; + } else { + return 1.0f - mTopHeadsUpDragAmount; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index 63bbf973dcd6..84a9f64331a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -130,7 +130,7 @@ public class StatusBarWindowManager { private void applyHeight(State state) { boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded - || state.keyguardFadingAway || state.bouncerShowing; + || state.keyguardFadingAway || state.bouncerShowing || state.headsUpShowing; if (expanded) { mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { @@ -172,11 +172,20 @@ public class StatusBarWindowManager { applyUserActivityTimeout(state); applyInputFeatures(state); applyFitsSystemWindows(state); + applyModalFlag(state); if (mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } } + private void applyModalFlag(State state) { + if (state.headsUpShowing) { + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } else { + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + } + } + public void setKeyguardShowing(boolean showing) { mCurrentState.keyguardShowing = showing; apply(mCurrentState); @@ -218,6 +227,11 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setHeadsUpShowing(boolean showing) { + mCurrentState.headsUpShowing = showing; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -235,6 +249,7 @@ public class StatusBarWindowManager { boolean bouncerShowing; boolean keyguardFadingAway; boolean qsExpanded; + boolean headsUpShowing; /** * The {@link BaseStatusBar} state from the status bar. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java new file mode 100644 index 000000000000..dccf2e21fa2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pools; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; + +import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Stack; +import java.util.TreeSet; + +public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener { + private static final String TAG = "HeadsUpManager"; + private static final boolean DEBUG = false; + private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; + + private final int mHeadsUpNotificationDecay; + private final int mMinimumDisplayTime; + + private final int mTouchSensitivityDelay; + private final ArrayMap<String, Long> mSnoozedPackages; + private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + private final int mDefaultSnoozeLengthMs; + private final Handler mHandler = new Handler(); + private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + + private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntry acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntry(); + } + + @Override + public boolean release(HeadsUpEntry instance) { + instance.removeAutoCancelCallbacks(); + mPoolObjects.push(instance); + return true; + } + }; + + + private PhoneStatusBar mBar; + private int mSnoozeLengthMs; + private ContentObserver mSettingsObserver; + private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>(); + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private int mUser; + private Clock mClock; + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private boolean mIsExpanded; + private boolean mHasPinnedHeadsUp; + private int[] mTmpTwoArray = new int[2]; + + public HeadsUpManager(final Context context, ViewTreeObserver observer) { + Resources resources = context.getResources(); + mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay); + if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay); + mSnoozedPackages = new ArrayMap<>(); + mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); + mSnoozeLengthMs = mDefaultSnoozeLengthMs; + mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + mClock = new Clock(); + + mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); + mSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + final int packageSnoozeLengthMs = Settings.Global.getInt( + context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); + if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) { + mSnoozeLengthMs = packageSnoozeLengthMs; + if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); + } + } + }; + context.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, + mSettingsObserver); + if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); + observer.addOnComputeInternalInsetsListener(this); + } + + public void setBar(PhoneStatusBar bar) { + mBar = bar; + } + + public void addListener(OnHeadsUpChangedListener listener) { + mListeners.add(listener); + } + + public PhoneStatusBar getBar() { + return mBar; + } + + /** + * Called when posting a new notification to the heads up. + */ + public void showNotification(NotificationData.Entry headsUp) { + if (DEBUG) Log.v(TAG, "showNotification"); + addHeadsUpEntry(headsUp); + updateNotification(headsUp, true); + headsUp.setInterruption(); + } + + /** + * Called when updating or posting a notification to the heads up. + */ + public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + if (DEBUG) Log.v(TAG, "updateNotification"); + + headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */); + headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + + if (alert) { + HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key); + headsUpEntry.updateEntry(); + setEntryToShade(headsUpEntry, mIsExpanded, false /* justAdded */, false); + } + } + + private void addHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = mEntryPool.acquire(); + + // This will also add the entry to the sortedList + headsUpEntry.setEntry(entry); + mHeadsUpEntries.put(entry.key, headsUpEntry); + entry.row.setHeadsUp(true); + setEntryToShade(headsUpEntry, mIsExpanded /* inShade */, true /* justAdded */, false); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.OnHeadsUpStateChanged(entry, true); + } + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } + + private void setEntryToShade(HeadsUpEntry headsUpEntry, boolean inShade, boolean justAdded, + boolean forceImmediate) { + ExpandableNotificationRow row = headsUpEntry.entry.row; + if (row.isInShade() != inShade || justAdded) { + row.setInShade(inShade); + if (!justAdded || !inShade) { + updatePinnedHeadsUpState(forceImmediate); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.OnHeadsUpPinnedChanged(row, !inShade); + } + } + } + } + + private void removeHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + mSortedEntries.remove(remove); + mEntryPool.release(remove); + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.row.setHeadsUp(false); + setEntryToShade(remove, true /* inShade */, false /* justAdded */, + false /* forceImmediate */); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.OnHeadsUpStateChanged(entry, false); + } + } + + private void updatePinnedHeadsUpState(boolean forceImmediate) { + boolean hasPinnedHeadsUp = hasPinnedHeadsUpInternal(); + if (hasPinnedHeadsUp == mHasPinnedHeadsUp) { + return; + } + mHasPinnedHeadsUp = hasPinnedHeadsUp; + for (OnHeadsUpChangedListener listener :mListeners) { + listener.OnPinnedHeadsUpExistChanged(hasPinnedHeadsUp, forceImmediate); + } + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * for a bit since it wasn't shown long enough + */ + public boolean removeNotification(String key) { + if (DEBUG) Log.v(TAG, "remove"); + if (wasShownLongEnough(key)) { + releaseImmediately(key); + return true; + } else { + getHeadsUpEntry(key).hideAsSoonAsPossible(); + return false; + } + } + + private boolean wasShownLongEnough(String key) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + HeadsUpEntry topEntry = getTopEntry(); + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); + } + + public boolean isHeadsUp(String key) { + return mHeadsUpEntries.containsKey(key); + } + + + /** + * Push any current Heads Up notification down into the shade. + */ + public void releaseAllImmediately() { + if (DEBUG) Log.v(TAG, "releaseAllImmediately"); + HashSet<String> keys = new HashSet<>(mHeadsUpEntries.keySet()); + for (String key: keys) { + releaseImmediately(key); + } + } + + public void releaseImmediately(String key) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + if (headsUpEntry == null) { + return; + } + NotificationData.Entry shadeEntry = headsUpEntry.entry; + removeHeadsUpEntry(shadeEntry); + } + + public boolean isSnoozed(String packageName) { + final String key = snoozeKey(packageName, mUser); + Long snoozedUntil = mSnoozedPackages.get(key); + if (snoozedUntil != null) { + if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (DEBUG) Log.v(TAG, key + " snoozed"); + return true; + } + mSnoozedPackages.remove(packageName); + } + return false; + } + + public void snooze() { + for (String key: mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + String packageName = entry.entry.notification.getPackageName(); + mSnoozedPackages.put(snoozeKey(packageName, mUser), + SystemClock.elapsedRealtime() + mSnoozeLengthMs); + } + mReleaseOnExpandFinish = true; + } + + private static String snoozeKey(String packageName, int user) { + return user + "," + packageName; + } + + private HeadsUpEntry getHeadsUpEntry(String key) { + return mHeadsUpEntries.get(key); + } + + public NotificationData.Entry getEntry(String key) { + return mHeadsUpEntries.get(key).entry; + } + + public TreeSet<HeadsUpEntry> getSortedEntries() { + return mSortedEntries; + } + + public HeadsUpEntry getTopEntry() { + return mSortedEntries.isEmpty() ? null : mSortedEntries.first(); + } + + /** + * @param key the key of the touched notification + * @return whether the touch is valid and should not be discarded + */ + public boolean shouldSwallowClick(String key) { + if (mClock.currentTimeMillis() < mHeadsUpEntries.get(key).postTime) { + return true; + } + return false; + } + + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (!mIsExpanded && mHasPinnedHeadsUp) { + int minX = Integer.MAX_VALUE; + int maxX = 0; + int minY = Integer.MAX_VALUE; + int maxY = 0; + for (HeadsUpEntry entry: mSortedEntries) { + ExpandableNotificationRow row = entry.entry.row; + if (!row.isInShade()) { + row.getLocationOnScreen(mTmpTwoArray); + minX = Math.min(minX, mTmpTwoArray[0]); + minY = Math.min(minY, 0); + maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth()); + maxY = Math.max(maxY, row.getHeadsUpHeight()); + } + } + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, minY, maxX, maxY); + } + } + + public void setUser(int user) { + mUser = user; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManager state:"); + pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay); + pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); + pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" mUser="); pw.println(mUser); + for (HeadsUpEntry entry: mSortedEntries) { + pw.print(" HeadsUpEntry="); pw.println(entry.entry); + } + int N = mSnoozedPackages.size(); + pw.println(" snoozed packages: " + N); + for (int i = 0; i < N; i++) { + pw.print(" "); pw.print(mSnoozedPackages.valueAt(i)); + pw.print(", "); pw.println(mSnoozedPackages.keyAt(i)); + } + } + + public boolean hasPinnedHeadsUp() { + return mHasPinnedHeadsUp; + } + + private boolean hasPinnedHeadsUpInternal() { + for (String key: mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + if (!entry.entry.row.isInShade()) { + return true; + } + } + return false; + } + + public void addSwipedOutKey(String key) { + mSwipedOutKeys.add(key); + } + + public float getHighestPinnedHeadsUp() { + float max = 0; + for (HeadsUpEntry entry: mSortedEntries) { + if (!entry.entry.row.isInShade()) { + max = Math.max(max, entry.entry.row.getActualHeight()); + } + } + return max; + } + + public void releaseAllToShade() { + for (String key: mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + setEntryToShade(entry, true /* toShade */, false /* justAdded */, + true /* forceImmediate */); + } + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + removeHeadsUpEntry(entry); + } + mEntriesToRemoveAfterExpand.clear(); + } + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + public void setIsExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + releaseAllToShade(); + } + } + } + + public int getTopHeadsUpHeight() { + HeadsUpEntry topEntry = getTopEntry(); + return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0; + } + + public int compare(NotificationData.Entry a, NotificationData.Entry b) { + HeadsUpEntry aEntry = getHeadsUpEntry(a.key); + HeadsUpEntry bEntry = getHeadsUpEntry(b.key); + if (aEntry == null || bEntry == null) { + return aEntry == null ? 1 : -1; + } + return aEntry.compareTo(bEntry); + } + + public class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public NotificationData.Entry entry; + public long postTime; + public long earliestRemovaltime; + private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(final NotificationData.Entry entry) { + this.entry = entry; + + // The actual post time will be just after the heads-up really slided in + postTime = mClock.currentTimeMillis() + mTouchSensitivityDelay; + mRemoveHeadsUpRunnable = new Runnable() { + @Override + public void run() { + if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + } + }; + updateEntry(); + } + + public void updateEntry() { + long currentTime = mClock.currentTimeMillis(); + earliestRemovaltime = currentTime + mMinimumDisplayTime; + postTime = Math.max(postTime, currentTime); + removeAutoCancelCallbacks(); + if (canEntryDecay()) { + long finishTime = postTime + mHeadsUpNotificationDecay; + long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); + mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay); + } + updateSortOrder(HeadsUpEntry.this); + } + + private boolean canEntryDecay() { + return entry.notification.getNotification().fullScreenIntent == null; + } + + @Override + public int compareTo(HeadsUpEntry o) { + return postTime < o.postTime ? 1 + : postTime == o.postTime ? 0 + : -1; + } + + public void removeAutoCancelCallbacks() { + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + public void hideAsSoonAsPossible() { + removeAutoCancelCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } + } + + /** + * Update the sorted heads up order. + * + * @param headsUpEntry the headsUp that changed + */ + private void updateSortOrder(HeadsUpEntry headsUpEntry) { + mSortedEntries.remove(headsUpEntry); + mSortedEntries.add(headsUpEntry); + } + + public static class Clock { + public long currentTimeMillis() { + return SystemClock.elapsedRealtime(); + } + } + + public interface OnHeadsUpChangedListener { + void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly); + void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp); + void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java deleted file mode 100644 index 1e40babbda5d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java +++ /dev/null @@ -1,622 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.policy; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.Outline; -import android.graphics.Rect; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.ArrayMap; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.ViewTreeObserver; -import android.view.accessibility.AccessibilityEvent; -import android.widget.FrameLayout; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.ExpandHelper; -import com.android.systemui.Gefingerpoken; -import com.android.systemui.R; -import com.android.systemui.SwipeHelper; -import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback, - ViewTreeObserver.OnComputeInternalInsetsListener { - private static final String TAG = "HeadsUpNotificationView"; - private static final boolean DEBUG = false; - private static final boolean SPEW = DEBUG; - private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - - Rect mTmpRect = new Rect(); - int[] mTmpTwoArray = new int[2]; - - private final int mHeadsUpNotificationDecay; - private final int mMinimumDisplayTime; - - private final int mTouchSensitivityDelay; - private final float mMaxAlpha = 1f; - private final ArrayMap<String, Long> mSnoozedPackages; - private final int mDefaultSnoozeLengthMs; - - private SwipeHelper mSwipeHelper; - private EdgeSwipeHelper mEdgeSwipeHelper; - - private PhoneStatusBar mBar; - - private long mLingerUntilMs; - private long mStartTouchTime; - private ViewGroup mContentHolder; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - - private NotificationData.Entry mHeadsUp; - private int mUser; - private String mMostRecentPackageName; - private boolean mTouched; - private Clock mClock; - - public static class Clock { - public long currentTimeMillis() { - return SystemClock.elapsedRealtime(); - } - } - - public HeadsUpNotificationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - Resources resources = context.getResources(); - mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay); - if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; - mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); - mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); - mClock = new Clock(); - } - - @VisibleForTesting - public HeadsUpNotificationView(Context context, Clock clock, SwipeHelper swipeHelper, - EdgeSwipeHelper edgeSwipeHelper, int headsUpNotificationDecay, int minimumDisplayTime, - int touchSensitivityDelay, int snoozeLength) { - super(context, null); - mClock = clock; - mSwipeHelper = swipeHelper; - mEdgeSwipeHelper = edgeSwipeHelper; - mMinimumDisplayTime = minimumDisplayTime; - mHeadsUpNotificationDecay = headsUpNotificationDecay; - mTouchSensitivityDelay = touchSensitivityDelay; - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = snoozeLength; - } - - public void updateResources() { - if (mContentHolder != null) { - final LayoutParams lp = (LayoutParams) mContentHolder.getLayoutParams(); - lp.width = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); - lp.gravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); - mContentHolder.setLayoutParams(lp); - } - } - - public void setBar(PhoneStatusBar bar) { - mBar = bar; - } - - public PhoneStatusBar getBar() { - return mBar; - } - - public ViewGroup getHolder() { - return mContentHolder; - } - - /** - * Called when posting a new notification to the heads up. - */ - public void showNotification(NotificationData.Entry headsUp) { - if (DEBUG) Log.v(TAG, "showNotification"); - if (mHeadsUp != null) { - // bump any previous heads up back to the shade - releaseImmediately(); - } - mTouched = false; - updateNotification(headsUp, true); - mLingerUntilMs = mClock.currentTimeMillis() + mMinimumDisplayTime; - } - - /** - * Called when updating or posting a notification to the heads up. - */ - public void updateNotification(NotificationData.Entry headsUp, boolean alert) { - if (DEBUG) Log.v(TAG, "updateNotification"); - - if (mHeadsUp == headsUp) { - resetViewForHeadsup(); - // This is an in-place update. Noting more to do. - return; - } - - mHeadsUp = headsUp; - - if (mContentHolder != null) { - mContentHolder.removeAllViews(); - } - - if (mHeadsUp != null) { - mMostRecentPackageName = mHeadsUp.notification.getPackageName(); - if (mHeadsUp.row != null) { - resetViewForHeadsup(); - } - - mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay; - if (mContentHolder != null) { // only null in tests and before we are attached to a window - mContentHolder.setX(0); - mContentHolder.setVisibility(View.VISIBLE); - mContentHolder.setAlpha(mMaxAlpha); - mContentHolder.addView(mHeadsUp.row); - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - - mSwipeHelper.snapChild(mContentHolder, 1f); - } - - mHeadsUp.setInterruption(); - } - if (alert) { - // Make sure the heads up window is open. - mBar.scheduleHeadsUpOpen(); - mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay); - } - } - - private void resetViewForHeadsup() { - if (mHeadsUp.row.areChildrenExpanded()) { - mHeadsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */); - } - mHeadsUp.row.setSystemExpanded(true); - mHeadsUp.row.setSensitive(false); - mHeadsUp.row.setHeadsUp(true); - mHeadsUp.row.setTranslationY(0); - mHeadsUp.row.setTranslationZ(0); - mHeadsUp.row.setHideSensitive( - false, false /* animated */, 0 /* delay */, 0 /* duration */); - } - - /** - * Possibly enter the lingering state by delaying the closing of the window. - * - * @return true if the notification has entered the lingering state. - */ - private boolean startLingering(boolean removed) { - final long now = mClock.currentTimeMillis(); - if (!mTouched && mHeadsUp != null && now < mLingerUntilMs) { - if (removed) { - mHeadsUp = null; - } - mBar.scheduleHeadsUpDecay(mLingerUntilMs - now); - return true; - } - return false; - } - - /** - * React to the removal of the notification in the heads up. - */ - public void removeNotification(String key) { - if (DEBUG) Log.v(TAG, "remove"); - if (mHeadsUp == null || !mHeadsUp.key.equals(key)) { - return; - } - if (!startLingering(/* removed */ true)) { - mHeadsUp = null; - releaseImmediately(); - } - } - - /** - * Ask for any current Heads Up notification to be pushed down into the shade. - */ - public void release() { - if (DEBUG) Log.v(TAG, "release"); - if (!startLingering(/* removed */ false)) { - releaseImmediately(); - } - } - - /** - * Push any current Heads Up notification down into the shade. - */ - public void releaseImmediately() { - if (DEBUG) Log.v(TAG, "releaseImmediately"); - if (mHeadsUp != null) { - mContentHolder.removeView(mHeadsUp.row); - mBar.displayNotificationFromHeadsUp(mHeadsUp); - } - mHeadsUp = null; - mBar.scheduleHeadsUpClose(); - } - - @Override - protected void onVisibilityChanged(View changedView, int visibility) { - super.onVisibilityChanged(changedView, visibility); - if (DEBUG) Log.v(TAG, "onVisibilityChanged: " + visibility); - if (changedView.getVisibility() == VISIBLE) { - mStartTouchTime = mClock.currentTimeMillis() + mTouchSensitivityDelay; - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - } - } - - public boolean isSnoozed(String packageName) { - final String key = snoozeKey(packageName, mUser); - Long snoozedUntil = mSnoozedPackages.get(key); - if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { - if (DEBUG) Log.v(TAG, key + " snoozed"); - return true; - } - mSnoozedPackages.remove(packageName); - } - return false; - } - - private void snooze() { - if (mMostRecentPackageName != null) { - mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); - } - releaseImmediately(); - } - - private static String snoozeKey(String packageName, int user) { - return user + "," + packageName; - } - - public boolean isShowing(String key) { - return mHeadsUp != null && mHeadsUp.key.equals(key); - } - - public NotificationData.Entry getEntry() { - return mHeadsUp; - } - - public boolean isClearable() { - return mHeadsUp == null || mHeadsUp.notification.isClearable(); - } - - // ViewGroup methods - -private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER = - new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - int outlineLeft = view.getPaddingLeft(); - int outlineTop = view.getPaddingTop(); - - // Apply padding to shadow. - outline.setRect(outlineLeft, outlineTop, - view.getWidth() - outlineLeft - view.getPaddingRight(), - view.getHeight() - outlineTop - view.getPaddingBottom()); - } - }; - - @Override - public void onAttachedToWindow() { - final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); - float touchSlop = viewConfiguration.getScaledTouchSlop(); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); - mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); - mEdgeSwipeHelper = new EdgeSwipeHelper(this, touchSlop); - - int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); - int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); - - mContentHolder = (ViewGroup) findViewById(R.id.content_holder); - mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER); - - mSnoozeLengthMs = Settings.Global.getInt(mContext.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); - mSettingsObserver = new ContentObserver(getHandler()) { - @Override - public void onChange(boolean selfChange) { - final int packageSnoozeLengthMs = Settings.Global.getInt( - mContext.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); - if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) { - mSnoozeLengthMs = packageSnoozeLengthMs; - if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); - } - } - }; - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, - mSettingsObserver); - if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs); - - if (mHeadsUp != null) { - // whoops, we're on already! - showNotification(mHeadsUp); - } - - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - } - - - @Override - protected void onDetachedFromWindow() { - mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (mClock.currentTimeMillis() < mStartTouchTime) { - return true; - } - mTouched = true; - return mEdgeSwipeHelper.onInterceptTouchEvent(ev) - || mSwipeHelper.onInterceptTouchEvent(ev) - || mHeadsUp == null // lingering - || super.onInterceptTouchEvent(ev); - } - - // View methods - - @Override - public void onDraw(android.graphics.Canvas c) { - super.onDraw(c); - if (DEBUG) { - //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: " - // + getMeasuredHeight() + "px"); - c.save(); - c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6, - android.graphics.Region.Op.DIFFERENCE); - c.drawColor(0xFFcc00cc); - c.restore(); - } - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mClock.currentTimeMillis() < mStartTouchTime) { - return false; - } - - final boolean wasRemoved = mHeadsUp == null; - if (!wasRemoved) { - mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay); - } - return mEdgeSwipeHelper.onTouchEvent(ev) - || mSwipeHelper.onTouchEvent(ev) - || wasRemoved - || super.onTouchEvent(ev); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - float densityScale = getResources().getDisplayMetrics().density; - mSwipeHelper.setDensityScale(densityScale); - float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); - mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); - } - - // ExpandHelper.Callback methods - - @Override - public ExpandableView getChildAtRawPosition(float x, float y) { - return getChildAtPosition(x, y); - } - - @Override - public ExpandableView getChildAtPosition(float x, float y) { - return mHeadsUp == null ? null : mHeadsUp.row; - } - - @Override - public boolean canChildBeExpanded(View v) { - return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable(); - } - - @Override - public void setUserExpandedChild(View v, boolean userExpanded) { - if (mHeadsUp != null && mHeadsUp.row == v) { - mHeadsUp.row.setUserExpanded(userExpanded); - } - } - - @Override - public void setUserLockedChild(View v, boolean userLocked) { - if (mHeadsUp != null && mHeadsUp.row == v) { - mHeadsUp.row.setUserLocked(userLocked); - } - } - - @Override - public void expansionStateChanged(boolean isExpanding) { - - } - - // SwipeHelper.Callback methods - - @Override - public boolean canChildBeDismissed(View v) { - return true; - } - - @Override - public boolean isAntiFalsingNeeded() { - return false; - } - - @Override - public float getFalsingThresholdFactor() { - return 1.0f; - } - - @Override - public void onChildDismissed(View v) { - Log.v(TAG, "User swiped heads up to dismiss"); - if (mHeadsUp != null && mHeadsUp.notification.isClearable()) { - mBar.onNotificationClear(mHeadsUp.notification); - mHeadsUp = null; - } - releaseImmediately(); - } - - @Override - public void onBeginDrag(View v) { - } - - @Override - public void onDragCancelled(View v) { - mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset - } - - @Override - public void onChildSnappedBack(View animView) { - } - - @Override - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - getBackground().setAlpha((int) (255 * swipeProgress)); - return false; - } - - @Override - public View getChildAtPosition(MotionEvent ev) { - return mContentHolder; - } - - @Override - public View getChildContentView(View v) { - return mContentHolder; - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - mContentHolder.getLocationOnScreen(mTmpTwoArray); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1], - mTmpTwoArray[0] + mContentHolder.getWidth(), - mTmpTwoArray[1] + mContentHolder.getHeight()); - } - - public void escalate() { - mBar.scheduleHeadsUpEscalation(); - } - - public String getKey() { - return mHeadsUp == null ? null : mHeadsUp.notification.getKey(); - } - - public void setUser(int user) { - mUser = user; - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("HeadsUpNotificationView state:"); - pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay); - pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" mLingerUntilMs="); pw.println(mLingerUntilMs); - pw.print(" mTouched="); pw.println(mTouched); - pw.print(" mMostRecentPackageName="); pw.println(mMostRecentPackageName); - pw.print(" mStartTouchTime="); pw.println(mStartTouchTime); - pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); - pw.print(" mUser="); pw.println(mUser); - if (mHeadsUp == null) { - pw.println(" mHeadsUp=null"); - } else { - pw.print(" mHeadsUp="); pw.println(mHeadsUp.notification.getKey()); - } - int N = mSnoozedPackages.size(); - pw.println(" snoozed packages: " + N); - for (int i = 0; i < N; i++) { - pw.print(" "); pw.print(mSnoozedPackages.valueAt(i)); - pw.print(", "); pw.println(mSnoozedPackages.keyAt(i)); - } - } - - public static class EdgeSwipeHelper implements Gefingerpoken { - private static final boolean DEBUG_EDGE_SWIPE = false; - private final float mTouchSlop; - private final HeadsUpNotificationView mHeadsUpView; - private boolean mConsuming; - private float mFirstY; - private float mFirstX; - - public EdgeSwipeHelper(HeadsUpNotificationView headsUpView, float touchSlop) { - mHeadsUpView = headsUpView; - mTouchSlop = touchSlop; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY()); - mFirstX = ev.getX(); - mFirstY = ev.getY(); - mConsuming = false; - break; - - case MotionEvent.ACTION_MOVE: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY()); - final float dY = ev.getY() - mFirstY; - final float daX = Math.abs(ev.getX() - mFirstX); - final float daY = Math.abs(dY); - if (!mConsuming && daX < daY && daY > mTouchSlop) { - mHeadsUpView.snooze(); - if (dY > 0) { - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open"); - mHeadsUpView.getBar().animateExpandNotificationsPanel(); - } - mConsuming = true; - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done"); - mConsuming = false; - break; - } - return mConsuming; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mConsuming; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index ba938cc29d36..c3c6b12315f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -114,6 +114,11 @@ public class MobileSignalController extends SignalController< setInetCondition(inetCondition); } + public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) { + mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode; + notifyListenersIfNecessary(); + } + /** * Start listening for phone state changes. */ @@ -123,7 +128,8 @@ public class MobileSignalController extends SignalController< | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_DATA_ACTIVITY); + | PhoneStateListener.LISTEN_DATA_ACTIVITY + | PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE); } /** @@ -201,8 +207,12 @@ public class MobileSignalController extends SignalController< && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription, qsTypeIcon, - mCurrentState.dataConnected && mCurrentState.activityIn, - mCurrentState.dataConnected && mCurrentState.activityOut, + mCurrentState.dataConnected + && !mCurrentState.carrierNetworkChangeMode + && mCurrentState.activityIn, + mCurrentState.dataConnected + && !mCurrentState.carrierNetworkChangeMode + && mCurrentState.activityOut, dataContentDescription, mCurrentState.isEmergency ? null : mCurrentState.networkName, // Only wide if actually showing something. @@ -215,6 +225,7 @@ public class MobileSignalController extends SignalController< mSignalClusters.get(i).setMobileDataIndicators( mCurrentState.enabled && !mCurrentState.airplaneMode, getCurrentIconId(), + getCurrentDarkIconId(), typeIcon, contentDescription, dataContentDescription, @@ -224,6 +235,10 @@ public class MobileSignalController extends SignalController< } } + private int getCurrentDarkIconId() { + return getCurrentIconId(false /* light */); + } + @Override protected MobileState cleanState() { return new MobileState(); @@ -270,6 +285,10 @@ public class MobileSignalController extends SignalController< } } + private boolean isCarrierNetworkChangeActive() { + return !hasService() && mCurrentState.carrierNetworkChangeMode; + } + public void handleBroadcast(Intent intent) { String action = intent.getAction(); if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { @@ -351,7 +370,9 @@ public class MobileSignalController extends SignalController< mCurrentState.dataConnected = mCurrentState.connected && mDataState == TelephonyManager.DATA_CONNECTED; - if (isRoaming()) { + if (isCarrierNetworkChangeActive()) { + mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE; + } else if (isRoaming()) { mCurrentState.iconGroup = TelephonyIcons.ROAMING; } if (isEmergencyOnly() != mCurrentState.isEmergency) { @@ -363,6 +384,7 @@ public class MobileSignalController extends SignalController< && mServiceState.getOperatorAlphaShort() != null) { mCurrentState.networkName = mServiceState.getOperatorAlphaShort(); } + notifyListenersIfNecessary(); } @@ -428,6 +450,16 @@ public class MobileSignalController extends SignalController< } setActivity(direction); } + + @Override + public void onCarrierNetworkChange(boolean active) { + if (DEBUG) { + Log.d(mTag, "onCarrierNetworkChange: active=" + active); + } + mCurrentState.carrierNetworkChangeMode = active; + + updateTelephony(); + } }; static class MobileIconGroup extends SignalController.IconGroup { @@ -440,8 +472,17 @@ public class MobileSignalController extends SignalController< int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, int discContentDesc, int dataContentDesc, int dataType, boolean isWide, int[] qsDataType) { - super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState, - qsDiscState, discContentDesc); + this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDiscState, qsDiscState, discContentDesc, dataContentDesc, + dataType, isWide, qsDataType); + } + + public MobileIconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons, + int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, + int sbDarkDiscState, int qsDiscState, int discContentDesc, int dataContentDesc, + int dataType, boolean isWide, int[] qsDataType) { + super(name, sbIcons, sbDarkIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDarkDiscState, qsDiscState, discContentDesc); mDataContentDescription = dataContentDesc; mDataType = dataType; mIsWide = isWide; @@ -455,6 +496,7 @@ public class MobileSignalController extends SignalController< boolean dataConnected; boolean isEmergency; boolean airplaneMode; + boolean carrierNetworkChangeMode; int inetForNetwork; @Override @@ -467,6 +509,7 @@ public class MobileSignalController extends SignalController< inetForNetwork = state.inetForNetwork; isEmergency = state.isEmergency; airplaneMode = state.airplaneMode; + carrierNetworkChangeMode = state.carrierNetworkChangeMode; } @Override @@ -478,7 +521,8 @@ public class MobileSignalController extends SignalController< builder.append("dataConnected=").append(dataConnected).append(','); builder.append("inetForNetwork=").append(inetForNetwork).append(','); builder.append("isEmergency=").append(isEmergency).append(','); - builder.append("airplaneMode=").append(airplaneMode); + builder.append("airplaneMode=").append(airplaneMode).append(','); + builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode); } @Override @@ -489,6 +533,7 @@ public class MobileSignalController extends SignalController< && ((MobileState) o).dataConnected == dataConnected && ((MobileState) o).isEmergency == isEmergency && ((MobileState) o).airplaneMode == airplaneMode + && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode && ((MobileState) o).inetForNetwork == inetForNetwork; } } 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 bb3eb7ad5ab8..5cf6a6ea157b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -704,6 +704,13 @@ public class NetworkControllerImpl extends BroadcastReceiver controller.getState().enabled = show; controller.notifyListeners(); } + String carrierNetworkChange = args.getString("carriernetworkchange"); + if (carrierNetworkChange != null) { + boolean show = carrierNetworkChange.equals("show"); + for (MobileSignalController controller : mMobileSignalControllers.values()) { + controller.setCarrierNetworkChangeMode(show); + } + } } } @@ -718,9 +725,9 @@ public class NetworkControllerImpl extends BroadcastReceiver public interface SignalCluster { void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription); - void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, - String contentDescription, String typeContentDescription, boolean isTypeIconWide, - int subId); + void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon, + int typeIcon, String contentDescription, String typeContentDescription, + boolean isTypeIconWide, int subId); void setSubs(List<SubscriptionInfo> subs); void setNoSims(boolean show); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java index 1d96c6b39499..c204814c2ebb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java @@ -142,8 +142,16 @@ public abstract class SignalController<T extends SignalController.State, * Gets the signal icon for SB based on current state of connected, enabled, and level. */ public int getCurrentIconId() { + return getCurrentIconId(true /* light */); + } + + protected int getCurrentIconId(boolean light) { if (mCurrentState.connected) { - return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; + if (light) { + return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; + } else { + return getIcons().mSbDarkIcons[mCurrentState.inetCondition][mCurrentState.level]; + } } else if (mCurrentState.enabled) { return getIcons().mSbDiscState; } else { @@ -226,11 +234,13 @@ public abstract class SignalController<T extends SignalController.State, */ static class IconGroup { final int[][] mSbIcons; + final int[][] mSbDarkIcons; final int[][] mQsIcons; final int[] mContentDesc; final int mSbNullState; final int mQsNullState; final int mSbDiscState; + final int mSbDarkDiscState; final int mQsDiscState; final int mDiscContentDesc; // For logging. @@ -239,13 +249,22 @@ public abstract class SignalController<T extends SignalController.State, public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, int discContentDesc) { + this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, + sbDiscState, sbDiscState, qsDiscState, discContentDesc); + } + + public IconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons, + int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, + int sbDarkDiscState, int qsDiscState, int discContentDesc) { mName = name; mSbIcons = sbIcons; + mSbDarkIcons = sbDarkIcons; mQsIcons = qsIcons; mContentDesc = contentDesc; mSbNullState = sbNullState; mQsNullState = qsNullState; mSbDiscState = sbDiscState; + mSbDarkDiscState = sbDarkDiscState; mQsDiscState = qsDiscState; mDiscContentDesc = discContentDesc; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java index d266ed88b5e5..053feb12edff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java @@ -68,6 +68,42 @@ class TelephonyIcons { R.drawable.stat_sys_signal_4_fully } }; + //CarrierNetworkChange + static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE = { + { R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation }, + { R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation, + R.drawable.stat_sys_signal_carrier_network_change_animation } + }; + + static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE_DARK = { + { R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation }, + { R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation, + R.drawable.stat_sys_signal_dark_carrier_network_change_animation } + }; + + static final int[][] QS_TELEPHONY_CARRIER_NETWORK_CHANGE = { + { R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation }, + { R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation, + R.drawable.ic_qs_signal_carrier_network_change_animation } + }; + static final int[] QS_DATA_R = { R.drawable.ic_qs_signal_r, R.drawable.ic_qs_signal_r @@ -202,11 +238,34 @@ class TelephonyIcons { static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g; static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g; static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x; + static final int ICON_CARRIER_NETWORK_CHANGE = + R.drawable.stat_sys_signal_carrier_network_change_animation; + static final int ICON_CARRIER_NETWORK_CHANGE_DARK = + R.drawable.stat_sys_signal_dark_carrier_network_change_animation; static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte; static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g; static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g; static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x; + static final int QS_ICON_CARRIER_NETWORK_CHANGE = + R.drawable.ic_qs_signal_carrier_network_change_animation; + + static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup( + "CARRIER_NETWORK_CHANGE", + TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE, + TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE_DARK, + TelephonyIcons.QS_TELEPHONY_CARRIER_NETWORK_CHANGE, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH, + 0, 0, + TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE, + TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE_DARK, + TelephonyIcons.QS_ICON_CARRIER_NETWORK_CHANGE, + AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0], + R.string.accessibility_carrier_network_change_mode, + 0, + false, + null + ); static final MobileIconGroup THREE_G = new MobileIconGroup( "3G", diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 8e677f1e68d2..f2b971f4c6fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -17,9 +17,13 @@ package com.android.systemui.statusbar.stack; import android.view.View; + import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; +import java.util.TreeSet; /** * A global state to track all input states for the algorithm. @@ -34,6 +38,12 @@ public class AmbientState { private int mSpeedBumpIndex = -1; private boolean mDark; private boolean mHideSensitive; + private HeadsUpManager mHeadsUpManager; + private float mStackTranslation; + private int mLayoutHeight; + private int mTopPadding; + private boolean mShadeExpanded; + private float mMaxHeadsUpTranslation; public int getScrollY() { return mScrollY; @@ -115,4 +125,67 @@ public class AmbientState { public void setSpeedBumpIndex(int speedBumpIndex) { mSpeedBumpIndex = speedBumpIndex; } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + + public TreeSet<HeadsUpManager.HeadsUpEntry> getSortedHeadsUpEntries() { + return mHeadsUpManager.getSortedEntries(); + } + + public float getStackTranslation() { + return mStackTranslation; + } + + public void setStackTranslation(float stackTranslation) { + mStackTranslation = stackTranslation; + } + + public int getLayoutHeight() { + return mLayoutHeight; + } + + public void setLayoutHeight(int layoutHeight) { + mLayoutHeight = layoutHeight; + } + + public float getTopPadding() { + return mTopPadding; + } + + public void setTopPadding(int topPadding) { + mTopPadding = topPadding; + } + + public int getInnerHeight() { + return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn(); + } + + private int getTopHeadsUpPushIn() { + ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry(); + return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight() + - topHeadsUpEntry.getMinHeight(): 0; + } + + public boolean isShadeExpanded() { + return mShadeExpanded; + } + + public void setShadeExpanded(boolean shadeExpanded) { + mShadeExpanded = shadeExpanded; + } + + public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) { + mMaxHeadsUpTranslation = maxHeadsUpTranslation; + } + + public float getMaxHeadsUpTranslation() { + return mMaxHeadsUpTranslation; + } + + public ExpandableNotificationRow getTopHeadsUpEntry() { + HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry(); + return topEntry == null ? null : topEntry.entry.row; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 2eafd577aa96..88fc6020b69c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.PointF; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -47,6 +48,8 @@ import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ScrollAdapter; import java.util.ArrayList; @@ -121,15 +124,15 @@ public class NotificationStackScrollLayout extends ViewGroup private StackScrollState mCurrentStackScrollState = new StackScrollState(this); private AmbientState mAmbientState = new AmbientState(); private NotificationGroupManager mGroupManager; - private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); - private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); - private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); - private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); - private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>(); + private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>(); + private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>(); + private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>(); + private ArrayList<View> mSnappedBackChildren = new ArrayList<>(); + private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>(); + private ArrayList<View> mChildrenChangingPositions = new ArrayList<>(); private HashSet<View> mFromMoreCardAdditions = new HashSet<>(); - private ArrayList<AnimationEvent> mAnimationEvents - = new ArrayList<AnimationEvent>(); - private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); + private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); + private ArrayList<View> mSwipedOutViews = new ArrayList<>(); private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); private boolean mAnimationsEnabled; private boolean mChangePositionInProgress; @@ -143,7 +146,6 @@ public class NotificationStackScrollLayout extends ViewGroup * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private OnChildLocationsChangedListener mListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; @@ -171,7 +173,6 @@ public class NotificationStackScrollLayout extends ViewGroup * Was the scroller scrolled to the top when the down motion was observed? */ private boolean mScrolledToTopOnFirstDown; - /** * The minimal amount of over scroll which is needed in order to switch to the quick settings * when over scrolling on a expanded card. @@ -179,6 +180,7 @@ public class NotificationStackScrollLayout extends ViewGroup private float mMinTopOverScrollToEscape; private int mIntrinsicPadding; private int mNotificationTopPadding; + private float mStackTranslation; private float mTopPaddingOverflow; private boolean mDontReportNextOverScroll; private boolean mRequestViewResizeAnimationOnLayout; @@ -202,9 +204,9 @@ public class NotificationStackScrollLayout extends ViewGroup private ViewGroup mScrollView; private boolean mInterceptDelegateEnabled; private boolean mDelegateToScrollView; + private boolean mDisallowScrollingInThisMotion; private long mGoToFullShadeDelay; - private ViewTreeObserver.OnPreDrawListener mChildrenUpdater = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -218,7 +220,12 @@ public class NotificationStackScrollLayout extends ViewGroup private PhoneStatusBar mPhoneStatusBar; private int[] mTempInt2 = new int[2]; private boolean mGenerateChildOrderChangedEvent; - private boolean mRemoveAnimationEnabled; + private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); + private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations + = new HashSet<>(); + private HeadsUpManager mHeadsUpManager; + private boolean mTrackingHeadsUp; + private ScrimController mScrimController; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -404,8 +411,8 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); - mStackScrollAlgorithm.setTopPadding(mTopPadding); + mAmbientState.setLayoutHeight(getLayoutHeight()); + mAmbientState.setTopPadding(mTopPadding); } /** @@ -478,9 +485,13 @@ public class NotificationStackScrollLayout extends ViewGroup int newStackHeight = (int) height; int minStackHeight = getMinStackHeight(); int stackHeight; - if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight + float paddingOffset; + boolean trackingHeadsUp = mTrackingHeadsUp; + int normalExpandPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight() + : minStackHeight; + if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalExpandPositionStart || getNotGoneChildCount() == 0) { - setTranslationY(mTopPaddingOverflow); + paddingOffset = mTopPaddingOverflow; stackHeight = newStackHeight; } else { @@ -492,9 +503,13 @@ public class NotificationStackScrollLayout extends ViewGroup float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow) / minStackHeight; partiallyThere = Math.max(0, partiallyThere); - translationY += (1 - partiallyThere) * (mBottomStackPeekSize + - mCollapseSecondCardPadding); - setTranslationY(translationY - mTopPadding); + if (!trackingHeadsUp) { + translationY += (1 - partiallyThere) * (mBottomStackPeekSize + + mCollapseSecondCardPadding); + } else { + translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight()); + } + paddingOffset = translationY - mTopPadding; stackHeight = (int) (height - (translationY - mTopPadding)); } if (stackHeight != mCurrentStackHeight) { @@ -502,6 +517,19 @@ public class NotificationStackScrollLayout extends ViewGroup updateAlgorithmHeightAndPadding(); requestChildrenUpdate(); } + setStackTranslation(paddingOffset); + } + + public float getStackTranslation() { + return mStackTranslation; + } + + private void setStackTranslation(float stackTranslation) { + if (stackTranslation != mStackTranslation) { + mStackTranslation = stackTranslation; + mAmbientState.setStackTranslation(stackTranslation); + requestChildrenUpdate(); + } } /** @@ -543,11 +571,6 @@ public class NotificationStackScrollLayout extends ViewGroup if (mDismissAllInProgress) { return; } - if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); - final View veto = v.findViewById(R.id.veto); - if (veto != null && veto.getVisibility() != View.GONE) { - veto.performClick(); - } setSwipingInProgress(false); if (mDragAnimPendingChildren.contains(v)) { // We start the swipe and finish it in the same frame, we don't want any animation @@ -556,6 +579,17 @@ public class NotificationStackScrollLayout extends ViewGroup } mSwipedOutViews.add(v); mAmbientState.onDragFinished(v); + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.isHeadsUp()) { + mHeadsUpManager.addSwipedOutKey(row.getStatusBarNotification().getKey()); + } + } + final View veto = v.findViewById(R.id.veto); + if (veto != null && veto.getVisibility() != View.GONE) { + veto.performClick(); + } + if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); } @Override @@ -575,28 +609,48 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + if (isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) { + mScrimController.setTopHeadsUpDragAmount(animView, + Math.min(Math.abs(swipeProgress - 1.0f), 1.0f)); + } return false; } - @Override - public float getFalsingThresholdFactor() { - return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; - } - public void onBeginDrag(View v) { setSwipingInProgress(true); mAmbientState.onBeginDrag(v); - if (mAnimationsEnabled) { + if (mAnimationsEnabled && !isPinnedHeadsUp(v)) { mDragAnimPendingChildren.add(v); mNeedsAnimation = true; } requestChildrenUpdate(); } + public boolean isPinnedHeadsUp(View v) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + return row.isHeadsUp() && !row.isInShade(); + } + return false; + } + + private boolean isHeadsUp(View v) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + return row.isHeadsUp(); + } + return false; + } + public void onDragCancelled(View v) { setSwipingInProgress(false); } + @Override + public float getFalsingThresholdFactor() { + return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; + } + public View getChildAtPosition(MotionEvent ev) { return getChildAtPosition(ev.getX(), ev.getY()); } @@ -657,6 +711,10 @@ public class NotificationStackScrollLayout extends ViewGroup if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; + if (row.isHeadsUp() && !row.isInShade() + && mHeadsUpManager.getTopEntry().entry.row != row) { + continue; + } return row.getViewAtPosition(touchY - childTop); } return slidingChild; @@ -667,7 +725,8 @@ public class NotificationStackScrollLayout extends ViewGroup public boolean canChildBeExpanded(View v) { return v instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) v).isExpandable(); + && ((ExpandableNotificationRow) v).isExpandable() + && !((ExpandableNotificationRow) v).isHeadsUp(); } public void setUserExpandedChild(View v, boolean userExpanded) { @@ -1343,12 +1402,9 @@ public class NotificationStackScrollLayout extends ViewGroup // add the padding before this element height += mPaddingBetweenElements; } - if (child instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) child; - height += row.getIntrinsicHeight(); - } else if (child instanceof ExpandableView) { + if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; - height += expandableView.getActualHeight(); + height += expandableView.getIntrinsicHeight(); } } } @@ -1573,16 +1629,11 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); updateScrollStateForRemovedChild(child); - if (mRemoveAnimationEnabled) { - boolean animationGenerated = generateRemoveAnimation(child); - if (animationGenerated && !mSwipedOutViews.contains(child)) { - // Add this view to an overlay in order to ensure that it will still be temporary - // drawn when removed - getOverlay().add(child); - } - } else { - // TODO: handle this more cleanly when HEADS-up and the shade are merged - requestAnimateEverything(); + boolean animationGenerated = generateRemoveAnimation(child); + if (animationGenerated && !mSwipedOutViews.contains(child)) { + // Add this view to an overlay in order to ensure that it will still be temporary + // drawn when removed + getOverlay().add(child); } updateAnimationState(false, child); @@ -1713,16 +1764,17 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateNotificationAnimationStates() { - boolean running = mIsExpanded && mAnimationsEnabled; + boolean running = mAnimationsEnabled; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); + running &= mIsExpanded || isPinnedHeadsUp(child); updateAnimationState(running, child); } } private void updateAnimationState(View child) { - updateAnimationState(mAnimationsEnabled && mIsExpanded, child); + updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child); } @@ -1752,6 +1804,10 @@ public class NotificationStackScrollLayout extends ViewGroup } mNeedsAnimation = true; } + if (isHeadsUp(child)) { + mAddedHeadsUpChildren.add(child); + mChildrenToAddAnimated.remove(child); + } } /** @@ -1790,6 +1846,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void generateChildHierarchyEvents() { + generateHeadsUpAnimationEvents(); generateChildRemovalEvents(); generateChildAdditionEvents(); generatePositionChangeEvents(); @@ -1807,6 +1864,40 @@ public class NotificationStackScrollLayout extends ViewGroup mNeedsAnimation = false; } + private void generateHeadsUpAnimationEvents() { + for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { + ExpandableNotificationRow row = eventPair.first; + boolean isHeadsUp = eventPair.second; + int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; + boolean onBottom = false; + if (!mIsExpanded && !isHeadsUp) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + } else if (mAddedHeadsUpChildren.contains(row) || (!row.isInShade() && !mIsExpanded)) { + if (!row.isInShade() || shouldHunAppearFromBottom(row)) { + // Our custom add animation + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; + } else { + // Normal add animation + type = AnimationEvent.ANIMATION_TYPE_ADD; + } + onBottom = row.isInShade(); + } + AnimationEvent event = new AnimationEvent(row, type); + event.headsUpFromBottom = onBottom; + mAnimationEvents.add(event); + } + mHeadsUpChangeAnimations.clear(); + mAddedHeadsUpChildren.clear(); + } + + private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) { + StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row); + if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) { + return false; + } + return true; + } + private void generateGroupExpansionEvent() { // Generate a group expansion/collapsing event if there is such a group at all if (mExpandedGroupView != null) { @@ -2182,6 +2273,10 @@ public class NotificationStackScrollLayout extends ViewGroup public void onChildAnimationFinished() { requestChildrenUpdate(); + for (Runnable runnable : mAnimationFinishedRunnables) { + runnable.run(); + } + mAnimationFinishedRunnables.clear(); } /** @@ -2283,7 +2378,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return the y position of the first notification */ public float getNotificationsTopY() { - return mTopPadding + getTranslationY(); + return mTopPadding + getStackTranslation(); } @Override @@ -2470,7 +2565,7 @@ public class NotificationStackScrollLayout extends ViewGroup max = bottom; } } - return max + getTranslationY(); + return max + getStackTranslation(); } /** @@ -2530,10 +2625,6 @@ public class NotificationStackScrollLayout extends ViewGroup return touchY > mIntrinsicPadding; } - public void setRemoveAnimationEnabled(boolean enabled) { - mRemoveAnimationEnabled = enabled; - } - private void updateExpandButtons() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -2579,6 +2670,50 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void performOnAnimationFinished(Runnable runnable) { + mAnimationFinishedRunnables.add(runnable); + } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + mAmbientState.setHeadsUpManager(headsUpManager); + mStackScrollAlgorithm.setHeadsUpManager(headsUpManager); + } + + public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { + if (mAnimationsEnabled) { + mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); + mNeedsAnimation = true; + requestChildrenUpdate(); + } + } + + public void setShadeExpanded(boolean shadeExpanded) { + mAmbientState.setShadeExpanded(shadeExpanded); + mStateAnimator.setShadeExpanded(shadeExpanded); + } + + /** + * Set the boundary for the bottom heads up position. The heads up will always be above this + * position. + * + * @param height the height of the screen + * @param bottomBarHeight the height of the bar on the bottom + */ + public void setHeadsUpBoundaries(int height, int bottomBarHeight) { + mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); + mStateAnimator.setHeadsUpAppearHeightBottom(height); + requestChildrenUpdate(); + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + public void setScrimController(ScrimController scrimController) { + mScrimController = scrimController; + } + /** * A listener that is notified when some child locations might have changed. */ @@ -2723,6 +2858,30 @@ public class NotificationStackScrollLayout extends ViewGroup .animateY() .animateZ(), + // ANIMATION_TYPE_HEADS_UP_APPEAR + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_DISAPPEAR + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_OTHER + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ(), + // ANIMATION_TYPE_EVERYTHING new AnimationFilter() .animateAlpha() @@ -2780,6 +2939,15 @@ public class NotificationStackScrollLayout extends ViewGroup // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED, + // ANIMATION_TYPE_HEADS_UP_APPEAR + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR, + + // ANIMATION_TYPE_HEADS_UP_DISAPPEAR + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR, + + // ANIMATION_TYPE_HEADS_UP_OTHER + StackStateAnimator.ANIMATION_DURATION_STANDARD, + // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, }; @@ -2798,7 +2966,10 @@ public class NotificationStackScrollLayout extends ViewGroup static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11; static final int ANIMATION_TYPE_VIEW_RESIZE = 12; static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13; - static final int ANIMATION_TYPE_EVERYTHING = 14; + static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14; + static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15; + static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16; + static final int ANIMATION_TYPE_EVERYTHING = 17; static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; @@ -2810,6 +2981,7 @@ public class NotificationStackScrollLayout extends ViewGroup final long length; View viewAfterChangingView; int darkAnimationOriginIndex; + boolean headsUpFromBottom; AnimationEvent(View view, int type) { this(view, type, LENGTHS[type]); 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 e7bf47b02782..d98bcfe25815 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -25,9 +25,11 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; import java.util.List; +import java.util.TreeSet; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -54,11 +56,6 @@ public class StackScrollAlgorithm { private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; - private int mLayoutHeight; - - /** mLayoutHeight - mTopPadding */ - private int mInnerHeight; - private int mTopPadding; private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpansionChanging; private int mFirstChildMaxHeight; @@ -74,6 +71,7 @@ public class StackScrollAlgorithm { private boolean mIsSmallScreen; private int mMaxNotificationHeight; private boolean mScaleDimmed; + private HeadsUpManager mHeadsUpManager; public StackScrollAlgorithm(Context context) { initConstants(context); @@ -157,20 +155,20 @@ public class StackScrollAlgorithm { scrollY = Math.max(0, scrollY); algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll); - updateVisibleChildren(resultState, algorithmState); + updateVisibleChildren(resultState, algorithmState, ambientState); // Phase 1: - findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); + findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState); // Phase 2: - updatePositionsForState(resultState, algorithmState); + updatePositionsForState(resultState, algorithmState, ambientState); // Phase 3: updateZValuesForState(resultState, algorithmState); handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState); - updateClipping(resultState, algorithmState); + updateClipping(resultState, algorithmState, ambientState); updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex()); getNotificationChildrenStates(resultState, algorithmState); } @@ -201,7 +199,7 @@ public class StackScrollAlgorithm { } private void updateClipping(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { float previousNotificationEnd = 0; float previousNotificationStart = 0; boolean previousNotificationIsSwiped = false; @@ -242,7 +240,7 @@ public class StackScrollAlgorithm { // otherwise we would clip to a transparent view. previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale; previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child); } } } @@ -314,7 +312,9 @@ public class StackScrollAlgorithm { StackViewState viewState = resultState.getViewStateForView( nextChild); // The child below the dragged one must be fully visible - viewState.alpha = 1; + if (!isPinnedHeadsUpView(draggedView) || isPinnedHeadsUpView(nextChild)) { + viewState.alpha = 1; + } } // Lets set the alpha to the one it currently has, as its currently being dragged @@ -325,27 +325,41 @@ public class StackScrollAlgorithm { } } + private boolean isPinnedHeadsUpView(View view) { + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + return row.isHeadsUp() && !row.isInShade(); + } + return false; + } + /** * Update the visible children on the state. */ private void updateVisibleChildren(StackScrollState resultState, - StackScrollAlgorithmState state) { + StackScrollAlgorithmState state, AmbientState ambientState) { ViewGroup hostView = resultState.getHostView(); int childCount = hostView.getChildCount(); state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); int notGoneIndex = 0; + TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries + = ambientState.getSortedHeadsUpEntries(); + for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) { + ExpandableView v = entry.entry.row; + notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); + } for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { - StackViewState viewState = resultState.getViewStateForView(v); - viewState.notGoneIndex = notGoneIndex; - state.visibleChildren.add(v); - notGoneIndex++; - - // handle the notgoneIndex for the children as well if (v instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.isHeadsUp()) { + continue; + } + notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); + + // handle the notgoneIndex for the children as well List<ExpandableNotificationRow> children = row.getNotificationChildren(); if (row.areChildrenExpanded() && children != null) { @@ -358,22 +372,35 @@ public class StackScrollAlgorithm { } } } + } else { + notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v); } } } } + private int updateNotGoneIndex(StackScrollState resultState, + StackScrollAlgorithmState state, int notGoneIndex, + ExpandableView v) { + StackViewState viewState = resultState.getViewStateForView(v); + viewState.notGoneIndex = notGoneIndex; + state.visibleChildren.add(v); + notGoneIndex++; + return notGoneIndex; + } + /** * Determine the positions for the views. This is the main part of the algorithm. * - * @param resultState The result state to update if a change to the properties of a child occurs + * @param resultState The result state to update if a change to the properties of a child occurs * @param algorithmState The state in which the current pass of the algorithm is currently in + * @param ambientState The current ambient state */ private void updatePositionsForState(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { // The starting position of the bottom stack peek - float bottomPeekStart = mInnerHeight - mBottomStackPeekSize; + float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize; // The position where the bottom stack starts. float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength; @@ -384,13 +411,17 @@ public class StackScrollAlgorithm { // How far in is the element currently transitioning into the bottom stack. float yPositionInScrollView = 0.0f; + // If we have a heads-up higher than the collapsed height we need to add the difference to + // the padding of all other elements, i.e push in the top stack slightly. + ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry(); + int childCount = algorithmState.visibleChildren.size(); int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); StackViewState childViewState = resultState.getViewStateForView(child); childViewState.location = StackViewState.LOCATION_UNKNOWN; - int childHeight = getMaxAllowedChildHeight(child); + int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -427,7 +458,8 @@ public class StackScrollAlgorithm { bottomPeekStart, childViewState.yTranslation, childViewState, childHeight); } - clampPositionToBottomStackStart(childViewState, childViewState.height); + clampPositionToBottomStackStart(childViewState, childViewState.height, + ambientState); } else if (nextYPosition >= bottomStackStart) { // Case 2: // We are in the bottom stack. @@ -435,7 +467,7 @@ public class StackScrollAlgorithm { // According to the regular scroll view we are fully translated out of the // bottom of the screen so we are fully in the bottom stack updateStateForChildFullyInBottomStack(algorithmState, - bottomStackStart, childViewState, childHeight); + bottomStackStart, childViewState, childHeight, ambientState); } else { // According to the regular scroll view we are currently translating out of / // into the bottom of the screen @@ -447,7 +479,7 @@ public class StackScrollAlgorithm { // Case 3: // We are in the regular scroll area. childViewState.location = StackViewState.LOCATION_MAIN_AREA; - clampYTranslation(childViewState, childHeight); + clampYTranslation(childViewState, childHeight, ambientState); } // The first card is always rendered. @@ -468,7 +500,44 @@ public class StackScrollAlgorithm { currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; yPositionInScrollView = yPositionInScrollViewAfterElement; - childViewState.yTranslation += mTopPadding; + if (ambientState.isShadeExpanded() && topHeadsUpEntry != null + && child != topHeadsUpEntry) { + childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize; + } + childViewState.yTranslation += ambientState.getTopPadding() + + ambientState.getStackTranslation(); + } + updateHeadsUpStates(resultState, ambientState); + } + + private void updateHeadsUpStates(StackScrollState resultState, AmbientState ambientState) { + TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries = ambientState.getSortedHeadsUpEntries(); + for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) { + ExpandableNotificationRow row = entry.entry.row; + StackViewState childState = resultState.getViewStateForView(row); + ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry(); + boolean isTopEntry = topHeadsUpEntry == row; + if (!row.isInShade()) { + childState.yTranslation = 0; + childState.height = row.getHeadsUpHeight(); + if (!isTopEntry) { + // Ensure that a headsUp is never below the topmost headsUp + StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry); + childState.height = row.getHeadsUpHeight(); + childState.yTranslation = topState.yTranslation + topState.height + - childState.height; + } + } else if (mIsExpanded) { + if (isTopEntry) { + childState.height += row.getHeadsUpHeight() - mCollapsedSize; + } + childState.height = Math.max(childState.height, row.getHeadsUpHeight()); + // Ensure that the heads up is always visible even when scrolled of from the bottom + float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height; + childState.yTranslation = Math.min(childState.yTranslation, + bottomPosition); + } + } } @@ -478,8 +547,9 @@ public class StackScrollAlgorithm { * @param childViewState the view state of the child * @param childHeight the height of this child */ - private void clampYTranslation(StackViewState childViewState, int childHeight) { - clampPositionToBottomStackStart(childViewState, childHeight); + private void clampYTranslation(StackViewState childViewState, int childHeight, + AmbientState ambientState) { + clampPositionToBottomStackStart(childViewState, childHeight, ambientState); clampPositionToTopStackEnd(childViewState, childHeight); } @@ -491,14 +561,15 @@ public class StackScrollAlgorithm { * @param childHeight the height of this child */ private void clampPositionToBottomStackStart(StackViewState childViewState, - int childHeight) { + int childHeight, AmbientState ambientState) { childViewState.yTranslation = Math.min(childViewState.yTranslation, - mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight); + ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding + - childHeight); } /** * Clamp the yTranslation of the child up such that its end is at lest on the end of the top - * stack.get + * stack. * * @param childViewState the view state of the child * @param childHeight the height of this child @@ -509,9 +580,14 @@ public class StackScrollAlgorithm { mCollapsedSize - childHeight); } - private int getMaxAllowedChildHeight(View child) { + private int getMaxAllowedChildHeight(View child, AmbientState ambientState) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (ambientState == null && row.isHeadsUp() + || ambientState != null && ambientState.getTopHeadsUpEntry() == child) { + int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight(); + return mCollapsedSize + extraSize; + } return row.getIntrinsicHeight(); } else if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; @@ -548,8 +624,7 @@ public class StackScrollAlgorithm { private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState, float transitioningPositionStart, StackViewState childViewState, - int childHeight) { - + int childHeight, AmbientState ambientState) { float currentYPosition; algorithmState.itemsInBottomStack += 1.0f; if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) { @@ -567,7 +642,7 @@ public class StackScrollAlgorithm { childViewState.alpha = 1.0f - algorithmState.partialInBottom; } childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN; - currentYPosition = mInnerHeight; + currentYPosition = ambientState.getInnerHeight(); } childViewState.yTranslation = currentYPosition - childHeight; clampPositionToTopStackEnd(childViewState, childHeight); @@ -629,7 +704,7 @@ public class StackScrollAlgorithm { * @param algorithmState The state in which the current pass of the algorithm is currently in */ private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { // The y Position if the element would be in a regular scrollView float yPositionInScrollView = 0.0f; @@ -639,7 +714,7 @@ public class StackScrollAlgorithm { for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); StackViewState childViewState = resultState.getViewStateForView(child); - int childHeight = getMaxAllowedChildHeight(child); + int childHeight = getMaxAllowedChildHeight(child, ambientState); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -647,7 +722,7 @@ public class StackScrollAlgorithm { if (i == 0 && algorithmState.scrollY <= mCollapsedSize) { // The starting position of the bottom stack peek - int bottomPeekStart = mInnerHeight - mBottomStackPeekSize - + int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding; // Collapse and expand the first child while the shade is being expanded float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding @@ -744,21 +819,6 @@ public class StackScrollAlgorithm { } } - public void setLayoutHeight(int layoutHeight) { - this.mLayoutHeight = layoutHeight; - updateInnerHeight(); - } - - public void setTopPadding(int topPadding) { - mTopPadding = topPadding; - updateInnerHeight(); - } - - private void updateInnerHeight() { - mInnerHeight = mLayoutHeight - mTopPadding; - } - - /** * Update whether the device is very small, i.e. Notifications can be in both the top and the * bottom stack at the same time @@ -788,6 +848,13 @@ public class StackScrollAlgorithm { // current height or the end value of the animation. mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight( mFirstChildWhileExpanding); + if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = + (ExpandableNotificationRow) mFirstChildWhileExpanding; + if (row.isHeadsUp()) { + mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight(); + } + } } else { updateFirstChildMaxSizeToMaxHeight(); } @@ -809,7 +876,7 @@ public class StackScrollAlgorithm { int oldBottom) { if (mFirstChildWhileExpanding != null) { mFirstChildMaxHeight = getMaxAllowedChildHeight( - mFirstChildWhileExpanding); + mFirstChildWhileExpanding, null); } else { mFirstChildMaxHeight = 0; } @@ -817,7 +884,7 @@ public class StackScrollAlgorithm { } }); } else { - mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); + mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null); } } @@ -830,6 +897,9 @@ public class StackScrollAlgorithm { } private View findFirstVisibleChild(ViewGroup container) { + if (mHeadsUpManager != null && mHeadsUpManager.getTopEntry() != null) { + return mHeadsUpManager.getTopEntry().entry.row; + } int childCount = container.getChildCount(); for (int i = 0; i < childCount; i++) { View child = container.getChildAt(i); @@ -870,6 +940,10 @@ public class StackScrollAlgorithm { } } + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + class StackScrollAlgorithmState { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index b249fbfd8d61..f5d94c887c82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -21,9 +21,11 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.graphics.Path; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; @@ -32,7 +34,6 @@ import com.android.systemui.statusbar.SpeedBumpView; import java.util.ArrayList; import java.util.HashSet; -import java.util.Set; import java.util.Stack; /** @@ -45,6 +46,8 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; + public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650; + public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230; public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54; public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; @@ -73,12 +76,15 @@ public class StackStateAnimator { private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag; private final Interpolator mFastOutSlowInInterpolator; + private final Interpolator mHeadsUpAppearInterpolator; private final int mGoToFullShadeAppearingTranslation; public NotificationStackScrollLayout mHostLayout; private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents = new ArrayList<>(); private ArrayList<View> mNewAddChildren = new ArrayList<>(); - private Set<Animator> mAnimatorSet = new HashSet<>(); + private HashSet<View> mHeadsUpAppearChildren = new HashSet<>(); + private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>(); + private HashSet<Animator> mAnimatorSet = new HashSet<>(); private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>(); private AnimationFilter mAnimationFilter = new AnimationFilter(); private long mCurrentLength; @@ -86,10 +92,12 @@ public class StackStateAnimator { /** The current index for the last child which was not added in this event set. */ private int mCurrentLastNotAddedIndex; - private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; private ExpandableNotificationRow mChildExpandingView; + private StackViewState mTmpState = new StackViewState(); + private int mHeadsUpAppearHeightBottom; + private boolean mShadeExpanded; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -98,6 +106,25 @@ public class StackStateAnimator { mGoToFullShadeAppearingTranslation = hostLayout.getContext().getResources().getDimensionPixelSize( R.dimen.go_to_full_shade_appearing_translation); + Path path = new Path(); + path.moveTo(0, 0); + float x1 = 250f; + float x2 = 150f; + float x3 = 100f; + float y1 = 90f; + float y2 = 78f; + float y3 = 80f; + float xTot = (x1 + x2 + x3); + path.cubicTo(x1 * 0.9f / xTot, 0f, + x1 * 0.8f / xTot, y1 / y3, + x1 / xTot , y1 / y3); + path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3, + (x1 + x2 * 0.2f) / xTot, y2 / y3, + (x1 + x2) / xTot, y2 / y3); + path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3, + (x1 + x2 + x3 * 0.2f) / xTot, 1f, + 1f, 1f); + mHeadsUpAppearInterpolator = new PathInterpolator(path); } public boolean isRunning() { @@ -119,7 +146,8 @@ public class StackStateAnimator { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); StackViewState viewState = finalState.getViewStateForView(child); - if (viewState == null || child.getVisibility() == View.GONE) { + if (viewState == null || child.getVisibility() == View.GONE + || applyWithoutAnimation(child, viewState, finalState)) { continue; } @@ -130,11 +158,39 @@ public class StackStateAnimator { // no child has preformed any animation, lets finish onAnimationFinished(); } + mHeadsUpAppearChildren.clear(); + mHeadsUpDisappearChildren.clear(); mNewEvents.clear(); mNewAddChildren.clear(); mChildExpandingView = null; } + /** + * Determines if a view should not perform an animation and applies it directly. + * + * @return true if no animation should be performed + */ + private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState, + StackScrollState finalState) { + if (mShadeExpanded) { + return false; + } + if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) { + // A Y translation animation is running + return false; + } + if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) { + // This is a heads up animation + return false; + } + if (mHostLayout.isPinnedHeadsUp(child)) { + // This is another headsUp which might move. Let's animate! + return false; + } + finalState.applyState(child, viewState); + return true; + } + private int findLastNotAddedIndex(StackScrollState finalState) { int childCount = mHostLayout.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { @@ -616,7 +672,9 @@ public class StackStateAnimator { ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, child.getTranslationY(), newEndValue); - animator.setInterpolator(mFastOutSlowInInterpolator); + Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ? + mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator; + animator.setInterpolator(interpolator); long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator); animator.setDuration(newDuration); if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { @@ -731,7 +789,7 @@ public class StackStateAnimator { }; } - private static <T> T getChildTag(View child, int tag) { + public static <T> T getChildTag(View child, int tag) { return (T) child.getTag(tag); } @@ -828,6 +886,22 @@ public class StackStateAnimator { ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView; row.prepareExpansionChanged(finalState); mChildExpandingView = row; + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { + // This item is added, initialize it's properties. + StackViewState viewState = finalState.getViewStateForView(changingView); + mTmpState.copyFrom(viewState); + if (event.headsUpFromBottom) { + mTmpState.yTranslation = mHeadsUpAppearHeightBottom; + } else { + mTmpState.yTranslation = -mTmpState.height; + } + mHeadsUpAppearChildren.add(changingView); + finalState.applyState(changingView, mTmpState); + } else if (event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { + // This item is added, initialize it's properties. + mHeadsUpDisappearChildren.add(changingView); } mNewEvents.add(event); } @@ -893,4 +967,12 @@ public class StackStateAnimator { return getChildTag(view, TAG_END_HEIGHT); } } + + public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) { + mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom; + } + + public void setShadeExpanded(boolean shadeExpanded) { + mShadeExpanded = shadeExpanded; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index c272e4878591..78122d6be664 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -123,19 +123,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void scheduleHeadsUpDecay(long delay) { - } - - @Override - public void scheduleHeadsUpOpen() { - } - - @Override - public void scheduleHeadsUpEscalation() { - } - - @Override - public void scheduleHeadsUpClose() { + public void escalateHeadsUp() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java deleted file mode 100644 index 45cb4a169548..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ /dev/null @@ -1,1538 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.PixelFormat; -import android.graphics.drawable.ColorDrawable; -import android.media.AudioAttributes; -import android.media.AudioManager; -import android.media.AudioSystem; -import android.media.RingtoneManager; -import android.media.ToneGenerator; -import android.media.VolumeProvider; -import android.media.session.MediaController; -import android.media.session.MediaController.PlaybackInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Debug; -import android.os.Handler; -import android.os.Message; -import android.os.Vibrator; -import android.util.Log; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.widget.ImageView; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.systemui.DemoMode; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Handles the user interface for the volume keys. - * - * @hide - */ -public class VolumePanel extends Handler implements DemoMode { - private static final String TAG = "VolumePanel"; - private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); - - private static final int PLAY_SOUND_DELAY = AudioSystem.PLAY_SOUND_DELAY; - - /** - * The delay before vibrating. This small period exists so if the user is - * moving to silent mode, it will not emit a short vibrate (it normally - * would since vibrate is between normal mode and silent mode using hardware - * keys). - */ - public static final int VIBRATE_DELAY = 300; - - private static final int VIBRATE_DURATION = 300; - private static final int BEEP_DURATION = 150; - private static final int MAX_VOLUME = 100; - private static final int FREE_DELAY = 10000; - private static final int TIMEOUT_DELAY = 3000; - private static final int TIMEOUT_DELAY_SHORT = 1500; - private static final int TIMEOUT_DELAY_COLLAPSED = 4500; - private static final int TIMEOUT_DELAY_SAFETY_WARNING = 5000; - private static final int TIMEOUT_DELAY_EXPANDED = 10000; - - private static final int MSG_VOLUME_CHANGED = 0; - private static final int MSG_FREE_RESOURCES = 1; - private static final int MSG_PLAY_SOUND = 2; - private static final int MSG_STOP_SOUNDS = 3; - private static final int MSG_VIBRATE = 4; - private static final int MSG_TIMEOUT = 5; - private static final int MSG_RINGER_MODE_CHANGED = 6; - private static final int MSG_MUTE_CHANGED = 7; - private static final int MSG_REMOTE_VOLUME_CHANGED = 8; - private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; - private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; - private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; - private static final int MSG_LAYOUT_DIRECTION = 12; - private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13; - private static final int MSG_USER_ACTIVITY = 14; - private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15; - private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16; - - // Pseudo stream type for remote volume - private static final int STREAM_REMOTE_MUSIC = -200; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - private static final int IC_AUDIO_VOL = com.android.systemui.R.drawable.ic_audio_vol; - private static final int IC_AUDIO_VOL_MUTE = com.android.systemui.R.drawable.ic_audio_vol_mute; - private static final int IC_AUDIO_BT = com.android.systemui.R.drawable.ic_audio_bt; - private static final int IC_AUDIO_BT_MUTE = com.android.systemui.R.drawable.ic_audio_bt_mute; - - private final String mTag; - protected final Context mContext; - private final AudioManager mAudioManager; - private final ZenModeController mZenController; - private boolean mRingIsSilent; - private boolean mVoiceCapable; - private boolean mZenModeAvailable; - private boolean mZenPanelExpanded; - private int mTimeoutDelay = TIMEOUT_DELAY; - private float mDisabledAlpha; - private int mLastRingerMode = AudioManager.RINGER_MODE_NORMAL; - private int mLastRingerProgress = 0; - private int mDemoIcon; - - /** Volume panel content view */ - private final View mView; - /** Dialog hosting the panel */ - private final Dialog mDialog; - - /** The visible portion of the volume overlay */ - private final ViewGroup mPanel; - /** Contains the slider and its touchable icons */ - private final ViewGroup mSliderPanel; - /** The zen mode configuration panel view */ - private ZenModePanel mZenPanel; - /** The component currently suppressing notification stream effects */ - private ComponentName mNotificationEffectsSuppressor; - - private Callback mCallback; - - /** Currently active stream that shows up at the top of the list of sliders */ - private int mActiveStreamType = -1; - /** All the slider controls mapped by stream type */ - private SparseArray<StreamControl> mStreamControls; - private final AccessibilityManager mAccessibilityManager; - private final SecondaryIconTransition mSecondaryIconTransition; - private final IconPulser mIconPulser; - - private enum StreamResources { - BluetoothSCOStream(AudioManager.STREAM_BLUETOOTH_SCO, - R.string.volume_icon_description_bluetooth, - IC_AUDIO_BT, - IC_AUDIO_BT_MUTE, - false), - RingerStream(AudioManager.STREAM_RING, - R.string.volume_icon_description_ringer, - com.android.systemui.R.drawable.ic_ringer_audible, - com.android.systemui.R.drawable.ic_ringer_mute, - false), - VoiceStream(AudioManager.STREAM_VOICE_CALL, - R.string.volume_icon_description_incall, - com.android.systemui.R.drawable.ic_audio_phone, - com.android.systemui.R.drawable.ic_audio_phone, - false), - AlarmStream(AudioManager.STREAM_ALARM, - R.string.volume_alarm, - com.android.systemui.R.drawable.ic_audio_alarm, - com.android.systemui.R.drawable.ic_audio_alarm_mute, - false), - MediaStream(AudioManager.STREAM_MUSIC, - R.string.volume_icon_description_media, - IC_AUDIO_VOL, - IC_AUDIO_VOL_MUTE, - true), - NotificationStream(AudioManager.STREAM_NOTIFICATION, - R.string.volume_icon_description_notification, - com.android.systemui.R.drawable.ic_ringer_audible, - com.android.systemui.R.drawable.ic_ringer_mute, - true), - RemoteStream(STREAM_REMOTE_MUSIC, - R.string.volume_icon_description_media, //FIXME should have its own description - com.android.systemui.R.drawable.ic_audio_remote, - com.android.systemui.R.drawable.ic_audio_remote, - false);// will be dynamically updated - - int streamType; - int descRes; - int iconRes; - int iconMuteRes; - // RING, VOICE_CALL & BLUETOOTH_SCO are hidden unless explicitly requested - boolean show; - - StreamResources(int streamType, int descRes, int iconRes, int iconMuteRes, boolean show) { - this.streamType = streamType; - this.descRes = descRes; - this.iconRes = iconRes; - this.iconMuteRes = iconMuteRes; - this.show = show; - } - } - - // List of stream types and their order - private static final StreamResources[] STREAMS = { - StreamResources.BluetoothSCOStream, - StreamResources.RingerStream, - StreamResources.VoiceStream, - StreamResources.MediaStream, - StreamResources.NotificationStream, - StreamResources.AlarmStream, - StreamResources.RemoteStream - }; - - /** Object that contains data for each slider */ - private class StreamControl { - int streamType; - MediaController controller; - ViewGroup group; - ImageView icon; - SeekBar seekbarView; - TextView suppressorView; - View divider; - ImageView secondaryIcon; - int iconRes; - int iconMuteRes; - int iconSuppressedRes; - int minVolume; - } - - // Synchronize when accessing this - private ToneGenerator mToneGenerators[]; - private Vibrator mVibrator; - private boolean mHasVibrator; - - private static AlertDialog sSafetyWarning; - private static Object sSafetyWarningLock = new Object(); - - protected LayoutParams getDialogLayoutParams(Window window, Resources res) { - final LayoutParams lp = window.getAttributes(); - lp.token = null; - lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); - lp.type = LayoutParams.TYPE_STATUS_BAR_PANEL; - lp.format = PixelFormat.TRANSLUCENT; - lp.windowAnimations = com.android.systemui.R.style.VolumePanelAnimation; - lp.setTitle(TAG); - return lp; - } - - public VolumePanel(Context context, ZenModeController zenController) { - mTag = String.format("%s.%08x", TAG, hashCode()); - mContext = context; - mZenController = zenController; - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); - mSecondaryIconTransition = new SecondaryIconTransition(); - mIconPulser = new IconPulser(context); - - if (LOGD) Log.d(mTag, "new VolumePanel"); - - mDisabledAlpha = 0.5f; - if (mContext.getTheme() != null) { - final TypedArray arr = mContext.getTheme().obtainStyledAttributes( - new int[] { android.R.attr.disabledAlpha }); - mDisabledAlpha = arr.getFloat(0, mDisabledAlpha); - arr.recycle(); - } - - mDialog = new Dialog(context) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && - sSafetyWarning == null) { - forceTimeout(0); - return true; - } - return false; - } - }; - - final Window window = mDialog.getWindow(); - window.requestFeature(Window.FEATURE_NO_TITLE); - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(com.android.systemui.R.layout.volume_panel_dialog); - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mActiveStreamType = -1; - mAudioManager.forceVolumeControlStream(mActiveStreamType); - setZenPanelVisible(false); - mDemoIcon = 0; - mSecondaryIconTransition.cancel(); - } - }); - - mDialog.create(); - - final Resources res = context.getResources(); - window.setAttributes(getDialogLayoutParams(window, res)); - - updateWidth(); - - window.setBackgroundDrawable(new ColorDrawable(0x00000000)); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE - | LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | LayoutParams.FLAG_HARDWARE_ACCELERATED); - mView = window.findViewById(R.id.content); - Interaction.register(mView, new Interaction.Callback() { - @Override - public void onInteraction() { - resetTimeout(); - } - }); - - mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); - mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); - mZenPanel = (ZenModePanel) mView.findViewById(com.android.systemui.R.id.zen_mode_panel); - initZenModePanel(); - - mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - mHasVibrator = mVibrator != null && mVibrator.hasVibrator(); - mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); - - if (mZenController != null) { - mZenModeAvailable = mZenController.isZenAvailable(); - mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); - mZenController.addCallback(mZenCallback); - } - - registerReceiver(); - } - - public void onConfigurationChanged(Configuration newConfig) { - updateWidth(); - if (mZenPanel != null) { - mZenPanel.updateLocale(); - } - } - - private void updateWidth() { - final Resources res = mContext.getResources(); - final LayoutParams lp = mDialog.getWindow().getAttributes(); - lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.notification_panel_width); - lp.gravity = - res.getInteger(com.android.systemui.R.integer.notification_panel_layout_gravity); - mDialog.getWindow().setAttributes(lp); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("VolumePanel state:"); - pw.print(" mTag="); pw.println(mTag); - pw.print(" mRingIsSilent="); pw.println(mRingIsSilent); - pw.print(" mVoiceCapable="); pw.println(mVoiceCapable); - pw.print(" mHasVibrator="); pw.println(mHasVibrator); - pw.print(" mZenModeAvailable="); pw.println(mZenModeAvailable); - pw.print(" mZenPanelExpanded="); pw.println(mZenPanelExpanded); - pw.print(" mNotificationEffectsSuppressor="); pw.println(mNotificationEffectsSuppressor); - pw.print(" mTimeoutDelay="); pw.println(mTimeoutDelay); - pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha); - pw.print(" mLastRingerMode="); pw.println(mLastRingerMode); - pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress); - pw.print(" isShowing()="); pw.println(isShowing()); - pw.print(" mCallback="); pw.println(mCallback); - pw.print(" sConfirmSafeVolumeDialog="); - pw.println(sSafetyWarning != null ? "<not null>" : null); - pw.print(" mActiveStreamType="); pw.println(mActiveStreamType); - pw.print(" mStreamControls="); - if (mStreamControls == null) { - pw.println("null"); - } else { - final int N = mStreamControls.size(); - pw.print("<size "); pw.print(N); pw.println('>'); - for (int i = 0; i < N; i++) { - final StreamControl sc = mStreamControls.valueAt(i); - pw.print(" stream "); pw.print(sc.streamType); pw.print(":"); - if (sc.seekbarView != null) { - pw.print(" progress="); pw.print(sc.seekbarView.getProgress()); - pw.print(" of "); pw.print(sc.seekbarView.getMax()); - if (!sc.seekbarView.isEnabled()) pw.print(" (disabled)"); - } - if (sc.icon != null && sc.icon.isClickable()) pw.print(" (clickable)"); - pw.println(); - } - } - if (mZenPanel != null) { - mZenPanel.dump(fd, pw, args); - } - } - - private void initZenModePanel() { - mZenPanel.init(mZenController); - mZenPanel.setCallback(new ZenModePanel.Callback() { - @Override - public void onMoreSettings() { - if (mCallback != null) { - mCallback.onZenSettings(); - } - } - - @Override - public void onPrioritySettings() { - if (mCallback != null) { - mCallback.onZenPrioritySettings(); - } - } - - @Override - public void onInteraction() { - resetTimeout(); - } - - @Override - public void onExpanded(boolean expanded) { - if (mZenPanelExpanded == expanded) return; - mZenPanelExpanded = expanded; - updateTimeoutDelay(); - resetTimeout(); - } - }); - } - - private void setLayoutDirection(int layoutDirection) { - mPanel.setLayoutDirection(layoutDirection); - updateStates(); - } - - private void registerReceiver() { - final IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); - filter.addAction(Intent.ACTION_SCREEN_OFF); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - - if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) { - removeMessages(MSG_RINGER_MODE_CHANGED); - sendEmptyMessage(MSG_RINGER_MODE_CHANGED); - } - - if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { - removeMessages(MSG_INTERNAL_RINGER_MODE_CHANGED); - sendEmptyMessage(MSG_INTERNAL_RINGER_MODE_CHANGED); - } - - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - postDismiss(0); - } - } - }, filter); - } - - private boolean isMuted(int streamType) { - if (streamType == STREAM_REMOTE_MUSIC) { - // TODO do we need to support a distinct mute property for remote? - return false; - } else { - return mAudioManager.isStreamMute(streamType); - } - } - - private int getStreamMinVolume(int streamType) { - if (streamType == STREAM_REMOTE_MUSIC) { - return 0; - } else { - return mAudioManager.getStreamMinVolume(streamType); - } - } - - private int getStreamMaxVolume(int streamType) { - if (streamType == STREAM_REMOTE_MUSIC) { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(streamType); - if (sc != null && sc.controller != null) { - PlaybackInfo ai = sc.controller.getPlaybackInfo(); - return ai.getMaxVolume(); - } - } - return -1; - } else { - return mAudioManager.getStreamMaxVolume(streamType); - } - } - - private int getStreamVolume(int streamType) { - if (streamType == STREAM_REMOTE_MUSIC) { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(streamType); - if (sc != null && sc.controller != null) { - PlaybackInfo ai = sc.controller.getPlaybackInfo(); - return ai.getCurrentVolume(); - } - } - return -1; - } else { - return mAudioManager.getLastAudibleStreamVolume(streamType); - } - } - - private void setStreamVolume(StreamControl sc, int index, int flags) { - if (sc.streamType == STREAM_REMOTE_MUSIC) { - if (sc.controller != null) { - sc.controller.setVolumeTo(index, flags); - } else { - Log.w(mTag, "Adjusting remote volume without a controller!"); - } - } else if (getStreamVolume(sc.streamType) != index) { - mAudioManager.setStreamVolume(sc.streamType, index, flags); - } - } - - private void createSliders() { - final Resources res = mContext.getResources(); - final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - - mStreamControls = new SparseArray<StreamControl>(STREAMS.length); - - final StreamResources notificationStream = StreamResources.NotificationStream; - for (int i = 0; i < STREAMS.length; i++) { - StreamResources streamRes = STREAMS[i]; - - final int streamType = streamRes.streamType; - final boolean isNotification = isNotificationOrRing(streamType); - - final StreamControl sc = new StreamControl(); - sc.streamType = streamType; - sc.group = (ViewGroup) inflater.inflate( - com.android.systemui.R.layout.volume_panel_item, null); - sc.group.setTag(sc); - sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon); - sc.icon.setTag(sc); - sc.icon.setContentDescription(res.getString(streamRes.descRes)); - sc.iconRes = streamRes.iconRes; - sc.iconMuteRes = streamRes.iconMuteRes; - sc.icon.setImageResource(sc.iconRes); - sc.icon.setClickable(isNotification && mHasVibrator); - if (isNotification) { - if (mHasVibrator) { - sc.icon.setSoundEffectsEnabled(false); - sc.iconMuteRes = com.android.systemui.R.drawable.ic_ringer_vibrate; - sc.icon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - resetTimeout(); - toggleRinger(sc); - } - }); - } - sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute; - } - sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); - sc.suppressorView = - (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor); - sc.suppressorView.setVisibility(View.GONE); - final boolean showSecondary = !isNotification && notificationStream.show; - sc.divider = sc.group.findViewById(com.android.systemui.R.id.divider); - sc.secondaryIcon = (ImageView) sc.group - .findViewById(com.android.systemui.R.id.secondary_icon); - sc.secondaryIcon.setImageResource(com.android.systemui.R.drawable.ic_ringer_audible); - sc.secondaryIcon.setContentDescription(res.getString(notificationStream.descRes)); - sc.secondaryIcon.setClickable(showSecondary); - sc.divider.setVisibility(showSecondary ? View.VISIBLE : View.GONE); - sc.secondaryIcon.setVisibility(showSecondary ? View.VISIBLE : View.GONE); - if (showSecondary) { - sc.secondaryIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mSecondaryIconTransition.start(sc); - } - }); - } - sc.minVolume = getStreamMinVolume(streamType); - sc.seekbarView.setMax(getStreamMaxVolume(streamType) - sc.minVolume); - sc.seekbarView.setOnSeekBarChangeListener(mSeekListener); - sc.seekbarView.setTag(sc); - mStreamControls.put(streamType, sc); - } - } - - private void toggleRinger(StreamControl sc) { - if (!mHasVibrator) return; - if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL) { - mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_VIBRATE); - postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); - } else { - mAudioManager.setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL); - postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND); - } - } - - private void reorderSliders(int activeStreamType) { - mSliderPanel.removeAllViews(); - - final StreamControl active = mStreamControls.get(activeStreamType); - if (active == null) { - Log.e(TAG, "Missing stream type! - " + activeStreamType); - mActiveStreamType = -1; - } else { - mSliderPanel.addView(active.group); - mActiveStreamType = activeStreamType; - active.group.setVisibility(View.VISIBLE); - updateSlider(active, true /*forceReloadIcon*/); - updateTimeoutDelay(); - updateZenPanelVisible(); - } - } - - private void updateSliderProgress(StreamControl sc, int progress) { - final boolean isRinger = isNotificationOrRing(sc.streamType); - if (isRinger && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - progress = mLastRingerProgress; - } - if (progress < 0) { - progress = getStreamVolume(sc.streamType); - } - sc.seekbarView.setProgress(progress - sc.minVolume); - if (isRinger) { - mLastRingerProgress = progress; - } - } - - private void updateSliderIcon(StreamControl sc, boolean muted) { - ComponentName suppressor = null; - if (isNotificationOrRing(sc.streamType)) { - suppressor = mNotificationEffectsSuppressor; - int ringerMode = mAudioManager.getRingerModeInternal(); - if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - ringerMode = mLastRingerMode; - } else { - mLastRingerMode = ringerMode; - } - if (mHasVibrator) { - muted = ringerMode == AudioManager.RINGER_MODE_VIBRATE; - } else { - muted = false; - } - } - sc.icon.setImageResource(mDemoIcon != 0 ? mDemoIcon - : suppressor != null ? sc.iconSuppressedRes - : muted ? sc.iconMuteRes - : sc.iconRes); - } - - private void updateSliderSuppressor(StreamControl sc) { - final ComponentName suppressor = isNotificationOrRing(sc.streamType) - ? mNotificationEffectsSuppressor : null; - if (suppressor == null) { - sc.seekbarView.setVisibility(View.VISIBLE); - sc.suppressorView.setVisibility(View.GONE); - } else { - sc.seekbarView.setVisibility(View.GONE); - sc.suppressorView.setVisibility(View.VISIBLE); - sc.suppressorView.setText(mContext.getString(R.string.muted_by, - getSuppressorCaption(suppressor))); - } - } - - private String getSuppressorCaption(ComponentName suppressor) { - final PackageManager pm = mContext.getPackageManager(); - try { - final ServiceInfo info = pm.getServiceInfo(suppressor, 0); - if (info != null) { - final CharSequence seq = info.loadLabel(pm); - if (seq != null) { - final String str = seq.toString().trim(); - if (str.length() > 0) { - return str; - } - } - } - } catch (Throwable e) { - Log.w(TAG, "Error loading suppressor caption", e); - } - return suppressor.getPackageName(); - } - - /** Update the mute and progress state of a slider */ - private void updateSlider(StreamControl sc, boolean forceReloadIcon) { - updateSliderProgress(sc, -1); - final boolean muted = isMuted(sc.streamType); - if (forceReloadIcon) { - sc.icon.setImageDrawable(null); - } - updateSliderIcon(sc, muted); - updateSliderEnabled(sc, muted, false); - updateSliderSuppressor(sc); - } - - private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) { - final boolean wasEnabled = sc.seekbarView.isEnabled(); - final boolean isRinger = isNotificationOrRing(sc.streamType); - if (sc.streamType == STREAM_REMOTE_MUSIC) { - // never disable touch interactions for remote playback, the muting is not tied to - // the state of the phone. - sc.seekbarView.setEnabled(!fixedVolume); - } else if (isRinger && mNotificationEffectsSuppressor != null) { - sc.icon.setEnabled(true); - sc.icon.setAlpha(1f); - sc.icon.setClickable(false); - } else if (isRinger - && mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) { - sc.seekbarView.setEnabled(false); - sc.icon.setEnabled(false); - sc.icon.setAlpha(mDisabledAlpha); - sc.icon.setClickable(false); - } else if (fixedVolume || - (sc.streamType != mAudioManager.getUiSoundsStreamType() && !isRinger && muted) || - (sSafetyWarning != null)) { - sc.seekbarView.setEnabled(false); - } else { - sc.seekbarView.setEnabled(true); - sc.icon.setEnabled(true); - sc.icon.setAlpha(1f); - } - // show the silent hint when the disabled slider is touched in silent mode - if (isRinger && wasEnabled != sc.seekbarView.isEnabled()) { - if (sc.seekbarView.isEnabled()) { - sc.group.setOnTouchListener(null); - sc.icon.setClickable(mHasVibrator); - } else { - final View.OnTouchListener showHintOnTouch = new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - resetTimeout(); - showSilentHint(); - return false; - } - }; - sc.group.setOnTouchListener(showHintOnTouch); - } - } - } - - private void showSilentHint() { - if (mZenPanel != null) { - mZenPanel.showSilentHint(); - } - } - - private void showVibrateHint() { - final StreamControl active = mStreamControls.get(mActiveStreamType); - if (active != null) { - mIconPulser.start(active.icon); - if (!hasMessages(MSG_VIBRATE)) { - sendEmptyMessageDelayed(MSG_VIBRATE, VIBRATE_DELAY); - } - } - } - - private static boolean isNotificationOrRing(int streamType) { - return streamType == AudioManager.STREAM_RING - || streamType == AudioManager.STREAM_NOTIFICATION; - } - - public void setCallback(Callback callback) { - mCallback = callback; - } - - private void updateTimeoutDelay() { - mTimeoutDelay = mDemoIcon != 0 ? TIMEOUT_DELAY_EXPANDED - : sSafetyWarning != null ? TIMEOUT_DELAY_SAFETY_WARNING - : mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT - : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED - : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED - : TIMEOUT_DELAY; - } - - private boolean isZenPanelVisible() { - return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE; - } - - private void setZenPanelVisible(boolean visible) { - if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel); - final boolean changing = visible != isZenPanelVisible(); - if (visible) { - mZenPanel.setHidden(false); - resetTimeout(); - } else { - mZenPanel.setHidden(true); - } - if (changing) { - updateTimeoutDelay(); - resetTimeout(); - } - } - - private void updateStates() { - final int count = mSliderPanel.getChildCount(); - for (int i = 0; i < count; i++) { - StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag(); - updateSlider(sc, true /*forceReloadIcon*/); - } - } - - private void updateActiveSlider() { - final StreamControl active = mStreamControls.get(mActiveStreamType); - if (active != null) { - updateSlider(active, false /*forceReloadIcon*/); - } - } - - private void updateZenPanelVisible() { - setZenPanelVisible(mZenModeAvailable && isNotificationOrRing(mActiveStreamType)); - } - - public void postVolumeChanged(int streamType, int flags) { - if (hasMessages(MSG_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); - } - - public void postRemoteVolumeChanged(MediaController controller, int flags) { - if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget(); - } - - public void postRemoteSliderVisibility(boolean visible) { - obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED, - STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget(); - } - - /** - * Called by AudioService when it has received new remote playback information that - * would affect the VolumePanel display (mainly volumes). The difference with - * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message - * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being - * displayed. - * This special code path is due to the fact that remote volume updates arrive to AudioService - * asynchronously. So after AudioService has sent the volume update (which should be treated - * as a request to update the volume), the application will likely set a new volume. If the UI - * is still up, we need to refresh the display to show this new value. - */ - public void postHasNewRemotePlaybackInfo() { - if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return; - // don't create or prevent resources to be freed, if they disappear, this update came too - // late and shouldn't warrant the panel to be displayed longer - obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget(); - } - - public void postMuteChanged(int streamType, int flags) { - if (hasMessages(MSG_VOLUME_CHANGED)) return; - synchronized (this) { - if (mStreamControls == null) { - createSliders(); - } - } - removeMessages(MSG_FREE_RESOURCES); - obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget(); - } - - public void postDisplaySafeVolumeWarning(int flags) { - if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return; - obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget(); - } - - public void postDismiss(long delay) { - forceTimeout(delay); - } - - public void postLayoutDirection(int layoutDirection) { - removeMessages(MSG_LAYOUT_DIRECTION); - obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget(); - } - - private static String flagsToString(int flags) { - return flags == 0 ? "0" : (flags + "=" + AudioManager.flagsToString(flags)); - } - - private static String streamToString(int stream) { - return AudioSystem.streamToString(stream); - } - - /** - * Override this if you have other work to do when the volume changes (for - * example, vibrating, playing a sound, etc.). Make sure to call through to - * the superclass implementation. - */ - protected void onVolumeChanged(int streamType, int flags) { - - if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + ")"); - - if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { - synchronized (this) { - if (mActiveStreamType != streamType) { - reorderSliders(streamType); - } - onShowVolumeChanged(streamType, flags, null); - } - } - - if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { - removeMessages(MSG_PLAY_SOUND); - sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); - } - - if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { - removeMessages(MSG_PLAY_SOUND); - removeMessages(MSG_VIBRATE); - onStopSounds(); - } - - removeMessages(MSG_FREE_RESOURCES); - sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); - } - - protected void onMuteChanged(int streamType, int flags) { - - if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + ")"); - - StreamControl sc = mStreamControls.get(streamType); - if (sc != null) { - updateSliderIcon(sc, isMuted(sc.streamType)); - } - - onVolumeChanged(streamType, flags); - } - - protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) { - int index = getStreamVolume(streamType); - - mRingIsSilent = false; - - if (LOGD) { - Log.d(mTag, "onShowVolumeChanged(streamType: " + streamToString(streamType) - + ", flags: " + flagsToString(flags) + "), index: " + index); - } - - // get max volume for progress bar - - int max = getStreamMaxVolume(streamType) - getStreamMinVolume(streamType); - StreamControl sc = mStreamControls.get(streamType); - - switch (streamType) { - - case AudioManager.STREAM_RING: { - Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( - mContext, RingtoneManager.TYPE_RINGTONE); - if (ringuri == null) { - mRingIsSilent = true; - } - break; - } - - case AudioManager.STREAM_MUSIC: { - // Special case for when Bluetooth is active for music - if ((mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC) & - (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | - AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) { - setMusicIcon(IC_AUDIO_BT, IC_AUDIO_BT_MUTE); - } else { - setMusicIcon(IC_AUDIO_VOL, IC_AUDIO_VOL_MUTE); - } - break; - } - - case AudioManager.STREAM_ALARM: { - break; - } - - case AudioManager.STREAM_NOTIFICATION: { - Uri ringuri = RingtoneManager.getActualDefaultRingtoneUri( - mContext, RingtoneManager.TYPE_NOTIFICATION); - if (ringuri == null) { - mRingIsSilent = true; - } - break; - } - - case STREAM_REMOTE_MUSIC: { - if (controller == null && sc != null) { - // If we weren't passed one try using the last one set. - controller = sc.controller; - } - if (controller == null) { - // We still don't have one, ignore the command. - Log.w(mTag, "sent remote volume change without a controller!"); - } else { - PlaybackInfo vi = controller.getPlaybackInfo(); - index = vi.getCurrentVolume(); - max = vi.getMaxVolume(); - if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) { - // if the remote volume is fixed add the flag for the UI - flags |= AudioManager.FLAG_FIXED_VOLUME; - } - } - if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); } - break; - } - } - - if (sc != null) { - if (streamType == STREAM_REMOTE_MUSIC && controller != sc.controller) { - if (sc.controller != null) { - sc.controller.unregisterCallback(mMediaControllerCb); - } - sc.controller = controller; - if (controller != null) { - sc.controller.registerCallback(mMediaControllerCb); - } - } - if (sc.seekbarView.getMax() != max) { - sc.seekbarView.setMax(max); - } - updateSliderProgress(sc, index); - final boolean muted = isMuted(streamType); - updateSliderEnabled(sc, muted, (flags & AudioManager.FLAG_FIXED_VOLUME) != 0); - if (isNotificationOrRing(streamType)) { - // check for secondary-icon transition completion - if (mSecondaryIconTransition.isRunning()) { - mSecondaryIconTransition.cancel(); // safe to reset - sc.seekbarView.setAlpha(0); sc.seekbarView.animate().alpha(1); - mZenPanel.setAlpha(0); mZenPanel.animate().alpha(1); - } - updateSliderIcon(sc, muted); - } - } - - if (!isShowing()) { - int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType; - // when the stream is for remote playback, use -1 to reset the stream type evaluation - mAudioManager.forceVolumeControlStream(stream); - mDialog.show(); - if (mCallback != null) { - mCallback.onVisible(true); - } - announceDialogShown(); - } - - // Do a little vibrate if applicable (only when going into vibrate mode) - if ((streamType != STREAM_REMOTE_MUSIC) && - ((flags & AudioManager.FLAG_VIBRATE) != 0) && - isNotificationOrRing(streamType) && - mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) { - sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); - } - - // Pulse the zen icon if an adjustment was suppressed due to silent mode. - if ((flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { - showSilentHint(); - } - - // Pulse the slider icon & vibrate if an adjustment down was suppressed due to vibrate mode. - if ((flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { - showVibrateHint(); - } - } - - private void announceDialogShown() { - mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - private boolean isShowing() { - return mDialog.isShowing(); - } - - protected void onPlaySound(int streamType, int flags) { - - if (hasMessages(MSG_STOP_SOUNDS)) { - removeMessages(MSG_STOP_SOUNDS); - // Force stop right now - onStopSounds(); - } - - synchronized (this) { - ToneGenerator toneGen = getOrCreateToneGenerator(streamType); - if (toneGen != null) { - toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); - sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); - } - } - } - - protected void onStopSounds() { - - synchronized (this) { - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int i = numStreamTypes - 1; i >= 0; i--) { - ToneGenerator toneGen = mToneGenerators[i]; - if (toneGen != null) { - toneGen.stopTone(); - } - } - } - } - - protected void onVibrate() { - - // Make sure we ended up in vibrate ringer mode - if (mAudioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_VIBRATE) { - return; - } - if (mVibrator != null) { - mVibrator.vibrate(VIBRATE_DURATION, VIBRATION_ATTRIBUTES); - } - } - - protected void onRemoteVolumeChanged(MediaController controller, int flags) { - if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " - + flagsToString(flags) + ")"); - - if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) { - synchronized (this) { - if (mActiveStreamType != STREAM_REMOTE_MUSIC) { - reorderSliders(STREAM_REMOTE_MUSIC); - } - onShowVolumeChanged(STREAM_REMOTE_MUSIC, flags, controller); - } - } else { - if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); - } - - removeMessages(MSG_FREE_RESOURCES); - sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); - resetTimeout(); - } - - protected void onRemoteVolumeUpdateIfShown() { - if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()"); - if (isShowing() - && (mActiveStreamType == STREAM_REMOTE_MUSIC) - && (mStreamControls != null)) { - onShowVolumeChanged(STREAM_REMOTE_MUSIC, 0, null); - } - } - - /** - * Clear the current remote stream controller. - */ - private void clearRemoteStreamController() { - if (mStreamControls != null) { - StreamControl sc = mStreamControls.get(STREAM_REMOTE_MUSIC); - if (sc != null) { - if (sc.controller != null) { - sc.controller.unregisterCallback(mMediaControllerCb); - sc.controller = null; - } - } - } - } - - /** - * Handler for MSG_SLIDER_VISIBILITY_CHANGED Hide or show a slider - * - * @param streamType can be a valid stream type value, or - * VolumePanel.STREAM_MASTER, or VolumePanel.STREAM_REMOTE_MUSIC - * @param visible - */ - synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { - if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); - boolean isVisible = (visible == 1); - for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { - StreamResources streamRes = STREAMS[i]; - if (streamRes.streamType == streamType) { - streamRes.show = isVisible; - if (!isVisible && (mActiveStreamType == streamType)) { - mActiveStreamType = -1; - } - break; - } - } - } - - protected void onDisplaySafeVolumeWarning(int flags) { - if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 - || isShowing()) { - synchronized (sSafetyWarningLock) { - if (sSafetyWarning != null) { - return; - } - sSafetyWarning = new SafetyWarningDialog(mContext, mAudioManager) { - @Override - protected void cleanUp() { - synchronized (sSafetyWarningLock) { - sSafetyWarning = null; - } - forceTimeout(0); - updateStates(); - } - }; - sSafetyWarning.show(); - } - updateStates(); - } - if (mAccessibilityManager.isTouchExplorationEnabled()) { - removeMessages(MSG_TIMEOUT); - } else { - updateTimeoutDelay(); - resetTimeout(); - } - } - - /** - * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. - */ - private ToneGenerator getOrCreateToneGenerator(int streamType) { - synchronized (this) { - if (mToneGenerators[streamType] == null) { - try { - mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); - } catch (RuntimeException e) { - if (LOGD) { - Log.d(mTag, "ToneGenerator constructor failed with " - + "RuntimeException: " + e); - } - } - } - return mToneGenerators[streamType]; - } - } - - - /** - * Switch between icons because Bluetooth music is same as music volume, but with - * different icons. - */ - private void setMusicIcon(int resId, int resMuteId) { - StreamControl sc = mStreamControls.get(AudioManager.STREAM_MUSIC); - if (sc != null) { - sc.iconRes = resId; - sc.iconMuteRes = resMuteId; - updateSliderIcon(sc, isMuted(sc.streamType)); - } - } - - protected void onFreeResources() { - synchronized (this) { - for (int i = mToneGenerators.length - 1; i >= 0; i--) { - if (mToneGenerators[i] != null) { - mToneGenerators[i].release(); - } - mToneGenerators[i] = null; - } - } - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MSG_VOLUME_CHANGED: { - onVolumeChanged(msg.arg1, msg.arg2); - break; - } - - case MSG_MUTE_CHANGED: { - onMuteChanged(msg.arg1, msg.arg2); - break; - } - - case MSG_FREE_RESOURCES: { - onFreeResources(); - break; - } - - case MSG_STOP_SOUNDS: { - onStopSounds(); - break; - } - - case MSG_PLAY_SOUND: { - onPlaySound(msg.arg1, msg.arg2); - break; - } - - case MSG_VIBRATE: { - onVibrate(); - break; - } - - case MSG_TIMEOUT: { - if (isShowing()) { - mDialog.dismiss(); - clearRemoteStreamController(); - mActiveStreamType = -1; - if (mCallback != null) { - mCallback.onVisible(false); - } - } - synchronized (sSafetyWarningLock) { - if (sSafetyWarning != null) { - if (LOGD) Log.d(mTag, "SafetyWarning timeout"); - sSafetyWarning.dismiss(); - } - } - break; - } - - case MSG_RINGER_MODE_CHANGED: - case MSG_INTERNAL_RINGER_MODE_CHANGED: - case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: { - if (isShowing()) { - updateActiveSlider(); - } - break; - } - - case MSG_REMOTE_VOLUME_CHANGED: { - onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1); - break; - } - - case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN: - onRemoteVolumeUpdateIfShown(); - break; - - case MSG_SLIDER_VISIBILITY_CHANGED: - onSliderVisibilityChanged(msg.arg1, msg.arg2); - break; - - case MSG_DISPLAY_SAFE_VOLUME_WARNING: - onDisplaySafeVolumeWarning(msg.arg1); - break; - - case MSG_LAYOUT_DIRECTION: - setLayoutDirection(msg.arg1); - break; - - case MSG_ZEN_MODE_AVAILABLE_CHANGED: - mZenModeAvailable = msg.arg1 != 0; - updateZenPanelVisible(); - break; - - case MSG_USER_ACTIVITY: - if (mCallback != null) { - mCallback.onInteraction(); - } - break; - } - } - - private void resetTimeout() { - final boolean touchExploration = mAccessibilityManager.isTouchExplorationEnabled(); - if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis() - + " delay=" + mTimeoutDelay + " touchExploration=" + touchExploration); - if (sSafetyWarning == null || !touchExploration) { - removeMessages(MSG_TIMEOUT); - sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay); - removeMessages(MSG_USER_ACTIVITY); - sendEmptyMessage(MSG_USER_ACTIVITY); - } - } - - private void forceTimeout(long delay) { - if (LOGD) Log.d(mTag, "forceTimeout delay=" + delay + " callers=" + Debug.getCallers(3)); - removeMessages(MSG_TIMEOUT); - sendEmptyMessageDelayed(MSG_TIMEOUT, delay); - } - - public ZenModeController getZenController() { - return mZenController; - } - - @Override - public void dispatchDemoCommand(String command, Bundle args) { - if (!COMMAND_VOLUME.equals(command)) return; - String icon = args.getString("icon"); - final String iconMute = args.getString("iconmute"); - final boolean mute = iconMute != null; - icon = mute ? iconMute : icon; - icon = icon.endsWith("Stream") ? icon : (icon + "Stream"); - final StreamResources sr = StreamResources.valueOf(icon); - mDemoIcon = mute ? sr.iconMuteRes : sr.iconRes; - final int forcedStreamType = StreamResources.MediaStream.streamType; - mAudioManager.forceVolumeControlStream(forcedStreamType); - mAudioManager.adjustStreamVolume(forcedStreamType, AudioManager.ADJUST_SAME, - AudioManager.FLAG_SHOW_UI); - } - - private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - final Object tag = seekBar.getTag(); - if (fromUser && tag instanceof StreamControl) { - StreamControl sc = (StreamControl) tag; - setStreamVolume(sc, progress + sc.minVolume, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); - } - resetTimeout(); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }; - - private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { - @Override - public void onZenAvailableChanged(boolean available) { - obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget(); - } - - @Override - public void onEffectsSupressorChanged() { - mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor(); - sendEmptyMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED); - } - }; - - private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() { - public void onAudioInfoChanged(PlaybackInfo info) { - onRemoteVolumeUpdateIfShown(); - } - }; - - private final class SecondaryIconTransition extends AnimatorListenerAdapter - implements Runnable { - private static final int ANIMATION_TIME = 400; - private static final int WAIT_FOR_SWITCH_TIME = 1000; - - private final int mAnimationTime = (int)(ANIMATION_TIME * ValueAnimator.getDurationScale()); - private final int mFadeOutTime = mAnimationTime / 2; - private final int mDelayTime = mAnimationTime / 3; - - private final Interpolator mIconInterpolator = - AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); - - private StreamControl mTarget; - - public void start(StreamControl sc) { - if (sc == null) throw new IllegalArgumentException(); - if (LOGD) Log.d(mTag, "Secondary icon animation start"); - if (mTarget != null) { - cancel(); - } - mTarget = sc; - mTimeoutDelay = mAnimationTime + WAIT_FOR_SWITCH_TIME; - resetTimeout(); - mTarget.secondaryIcon.setClickable(false); - final int N = mTarget.group.getChildCount(); - for (int i = 0; i < N; i++) { - final View child = mTarget.group.getChildAt(i); - if (child != mTarget.secondaryIcon) { - child.animate().alpha(0).setDuration(mFadeOutTime).start(); - } - } - mTarget.secondaryIcon.animate() - .translationXBy(mTarget.icon.getX() - mTarget.secondaryIcon.getX()) - .setInterpolator(mIconInterpolator) - .setStartDelay(mDelayTime) - .setDuration(mAnimationTime - mDelayTime) - .setListener(this) - .start(); - } - - public boolean isRunning() { - return mTarget != null; - } - - public void cancel() { - if (mTarget == null) return; - mTarget.secondaryIcon.setClickable(true); - final int N = mTarget.group.getChildCount(); - for (int i = 0; i < N; i++) { - final View child = mTarget.group.getChildAt(i); - if (child != mTarget.secondaryIcon) { - child.animate().cancel(); - child.setAlpha(1); - } - } - mTarget.secondaryIcon.animate().cancel(); - mTarget.secondaryIcon.setTranslationX(0); - mTarget = null; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mTarget == null) return; - AsyncTask.execute(this); - } - - @Override - public void run() { - if (mTarget == null) return; - if (LOGD) Log.d(mTag, "Secondary icon animation complete, show notification slider"); - mAudioManager.forceVolumeControlStream(StreamResources.NotificationStream.streamType); - mAudioManager.adjustStreamVolume(StreamResources.NotificationStream.streamType, - AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI); - } - } - - public interface Callback { - void onZenSettings(); - void onZenPrioritySettings(); - void onInteraction(); - void onVisible(boolean visible); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java deleted file mode 100644 index b072cab34551..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelComponent.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.media.AudioManager; -import android.media.IRemoteVolumeController; -import android.media.IVolumeController; -import android.media.VolumePolicy; -import android.media.session.ISessionController; -import android.media.session.MediaController; -import android.media.session.MediaSessionManager; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; - -import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.qs.tiles.DndTile; -import com.android.systemui.statusbar.phone.PhoneStatusBar; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.io.FileDescriptor; -import java.io.PrintWriter; - -/** - * Implementation of VolumeComponent backed by the old volume panel. - */ -public class VolumePanelComponent implements VolumeComponent { - - private final SystemUI mSysui; - private final Context mContext; - private final Handler mHandler; - private final VolumeController mVolumeController; - private final RemoteVolumeController mRemoteVolumeController; - private final AudioManager mAudioManager; - private final MediaSessionManager mMediaSessionManager; - - private VolumePanel mPanel; - private int mDismissDelay; - - public VolumePanelComponent(SystemUI sysui, Context context, Handler handler, - ZenModeController controller) { - mSysui = sysui; - mContext = context; - mHandler = handler; - mAudioManager = context.getSystemService(AudioManager.class); - mMediaSessionManager = context.getSystemService(MediaSessionManager.class); - mVolumeController = new VolumeController(); - mRemoteVolumeController = new RemoteVolumeController(); - mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay); - mPanel = new VolumePanel(mContext, controller); - mPanel.setCallback(new VolumePanel.Callback() { - @Override - public void onZenSettings() { - mHandler.removeCallbacks(mStartZenSettings); - mHandler.post(mStartZenSettings); - } - - @Override - public void onZenPrioritySettings() { - mHandler.removeCallbacks(mStartZenPrioritySettings); - mHandler.post(mStartZenPrioritySettings); - } - - @Override - public void onInteraction() { - final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class); - if (kvm != null) { - kvm.userActivity(); - } - } - - @Override - public void onVisible(boolean visible) { - if (mAudioManager != null && mVolumeController != null) { - mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible); - } - } - }); - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mPanel != null) { - mPanel.dump(fd, pw, args); - } - } - - public void register() { - mAudioManager.setVolumeController(mVolumeController); - mAudioManager.setVolumePolicy(VolumePolicy.DEFAULT); - mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController); - DndTile.setVisible(mContext, false); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - if (mPanel != null) { - mPanel.onConfigurationChanged(newConfig); - } - } - - @Override - public ZenModeController getZenController() { - return mPanel.getZenController(); - } - - @Override - public void dispatchDemoCommand(String command, Bundle args) { - mPanel.dispatchDemoCommand(command, args); - } - - @Override - public void dismissNow() { - mPanel.postDismiss(0); - } - - private void startSettings(Intent intent) { - mSysui.getComponent(PhoneStatusBar.class).startActivityDismissingKeyguard(intent, - true /* onlyProvisioned */, true /* dismissShade */); - mPanel.postDismiss(mDismissDelay); - } - - private final Runnable mStartZenSettings = new Runnable() { - @Override - public void run() { - startSettings(ZenModePanel.ZEN_SETTINGS); - } - }; - - private final Runnable mStartZenPrioritySettings = new Runnable() { - @Override - public void run() { - startSettings(ZenModePanel.ZEN_PRIORITY_SETTINGS); - } - }; - - private final class RemoteVolumeController extends IRemoteVolumeController.Stub { - @Override - public void remoteVolumeChanged(ISessionController binder, int flags) - throws RemoteException { - MediaController controller = new MediaController(mContext, binder); - mPanel.postRemoteVolumeChanged(controller, flags); - } - - @Override - public void updateRemoteController(ISessionController session) throws RemoteException { - mPanel.postRemoteSliderVisibility(session != null); - // TODO stash default session in case the slider can be opened other - // than by remoteVolumeChanged. - } - } - - /** For now, simply host an unmodified base volume panel in this process. */ - private final class VolumeController extends IVolumeController.Stub { - - @Override - public void displaySafeVolumeWarning(int flags) throws RemoteException { - mPanel.postDisplaySafeVolumeWarning(flags); - } - - @Override - public void volumeChanged(int streamType, int flags) - throws RemoteException { - mPanel.postVolumeChanged(streamType, flags); - } - - @Override - public void masterMuteChanged(int flags) throws RemoteException { - // no-op - } - - @Override - public void setLayoutDirection(int layoutDirection) - throws RemoteException { - mPanel.postLayoutDirection(layoutDirection); - } - - @Override - public void dismiss() throws RemoteException { - dismissNow(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index e979786fc44f..5f04aaff90bb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -31,7 +31,6 @@ import android.content.res.Configuration; import android.media.AudioManager; import android.media.session.MediaSessionManager; import android.os.Handler; -import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -52,8 +51,6 @@ public class VolumeUI extends SystemUI { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean USE_OLD_VOLUME = SystemProperties.getBoolean("volume.old", false); - private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); private final RestorationNotification mRestorationNotification = new RestorationNotification(); @@ -64,8 +61,7 @@ public class VolumeUI extends SystemUI { private MediaSessionManager mMediaSessionManager; private ServiceMonitor mVolumeControllerService; - private VolumePanelComponent mOldVolume; - private VolumeDialogComponent mNewVolume; + private VolumeDialogComponent mVolumeComponent; @Override public void start() { @@ -77,8 +73,7 @@ public class VolumeUI extends SystemUI { mMediaSessionManager = (MediaSessionManager) mContext .getSystemService(Context.MEDIA_SESSION_SERVICE); final ZenModeController zenController = new ZenModeControllerImpl(mContext, mHandler); - mOldVolume = new VolumePanelComponent(this, mContext, mHandler, zenController); - mNewVolume = new VolumeDialogComponent(this, mContext, null, zenController); + mVolumeComponent = new VolumeDialogComponent(this, mContext, null, zenController); putComponent(VolumeComponent.class, getVolumeComponent()); mReceiver.start(); mVolumeControllerService = new ServiceMonitor(TAG, LOGD, @@ -88,7 +83,7 @@ public class VolumeUI extends SystemUI { } private VolumeComponent getVolumeComponent() { - return USE_OLD_VOLUME ? mOldVolume : mNewVolume; + return mVolumeComponent; } @Override @@ -235,7 +230,7 @@ public class VolumeUI extends SystemUI { .putExtra(Receiver.EXTRA_COMPONENT, component); mNotificationManager.notify(R.id.notification_volumeui, new Notification.Builder(mContext) - .setSmallIcon(R.drawable.ic_ringer_audible) + .setSmallIcon(R.drawable.ic_volume_media) .setWhen(0) .setShowWhen(false) .setOngoing(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java deleted file mode 100644 index e8a80d9f587d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java +++ /dev/null @@ -1,261 +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 com.android.systemui.statusbar.policy; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.os.*; -import android.service.notification.StatusBarNotification; -import com.android.systemui.SwipeHelper; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.ExpandableNotificationRow; -import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -/** - * Test the Heads Up Notification. - * - * Specifically the policy that a notificaiton must remain visibile for a minimum period of time. - */ -public class HeadsUpNotificationTest extends SysuiTestCase { - private static final String TAG = "HeadsUpNotificationTest"; - - private static int TOUCH_SENSITIVITY = 100; - private static int NOTIFICATION_DECAY = 10000; - private static int MINIMUM_DISPLAY_TIME = 3000; - private static int SNOOZE_TIME = 60000; - private static long TOO_SOON = 1000L; // less than MINIMUM_DISPLAY_TIME - private static long LATER = 5000L; // more than MINIMUM_DISPLAY_TIME - private static long REMAINING_VISIBILITY = MINIMUM_DISPLAY_TIME - TOO_SOON; - - protected HeadsUpNotificationView mHeadsUp; - - @Mock protected PhoneStatusBar mMockStatusBar; - @Mock private HeadsUpNotificationView.Clock mClock; - @Mock private SwipeHelper mMockSwipeHelper; - @Mock private HeadsUpNotificationView.EdgeSwipeHelper mMockEdgeSwipeHelper; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - MockitoAnnotations.initMocks(this); - - mHeadsUp = new HeadsUpNotificationView(mContext, - mClock, mMockSwipeHelper, mMockEdgeSwipeHelper, - NOTIFICATION_DECAY, MINIMUM_DISPLAY_TIME, TOUCH_SENSITIVITY, SNOOZE_TIME); - mHeadsUp.setBar(mMockStatusBar); - } - - private NotificationData.Entry makeNotification(String key) { - StatusBarNotification sbn = mock(StatusBarNotification.class); - when(sbn.getKey()).thenReturn(key); - return new NotificationData.Entry(sbn, null); - } - - public void testPostAndDecay() { - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpOpen(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture()); - // New notification gets a full decay time. - assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue()); - } - - public void testPostAndDeleteTooSoon() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - mHeadsUp.removeNotification(a.key); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture()); - // Leave the window up for the balance of the minumum time. - assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue()); - } - - public void testPostAndDeleteLater() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(LATER); - mHeadsUp.removeNotification(a.key); - // Delete closes immediately if the minimum time window is satisfied. - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt()); - } - - // This is a bad test. It should not care that there is a call to scheduleHeadsUpClose(), - // but it happens that there will be one, so it is important that it happen before the - // call to scheduleHeadsUpOpen(), so that the final state is open. - // Maybe mMockStatusBar should instead be a fake that tracks the open/closed state. - public void testPostAndReplaceTooSoon() { - InOrder callOrder = inOrder(mMockStatusBar); - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - NotificationData.Entry b = makeNotification("b"); - mHeadsUp.showNotification(b); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture()); - // New notification gets a full decay time. - assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue()); - - // Make sure close was called before open, so that the heads up stays open. - callOrder.verify(mMockStatusBar).scheduleHeadsUpClose(); - callOrder.verify(mMockStatusBar).scheduleHeadsUpOpen(); - } - - public void testPostAndUpdateAlertAgain() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - mHeadsUp.updateNotification(a, true); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture()); - // Alert again gets a full decay time. - assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue()); - } - - public void testPostAndUpdateAlertAgainFastFail() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - NotificationData.Entry a_prime = makeNotification("a"); - mHeadsUp.updateNotification(a_prime, true); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture()); - // Alert again gets a full decay time. - assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue()); - } - - public void testPostAndUpdateNoAlertAgain() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - mHeadsUp.updateNotification(a, false); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt()); - } - - public void testPostAndUpdateNoAlertAgainFastFail() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - NotificationData.Entry a_prime = makeNotification("a"); - mHeadsUp.updateNotification(a_prime, false); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt()); - } - - public void testPostAndUpdateLowPriorityTooSoon() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - mHeadsUp.release(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture()); - // Down grade on update leaves the window up for the balance of the minumum time. - assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue()); - } - - public void testPostAndUpdateLowPriorityTooSoonFastFail() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(TOO_SOON); - NotificationData.Entry a_prime = makeNotification("a"); - mHeadsUp.updateNotification(a_prime, false); - mHeadsUp.release(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose(); - ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class); - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture()); - // Down grade on update leaves the window up for the balance of the minumum time. - assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue()); - } - - public void testPostAndUpdateLowPriorityLater() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(LATER); - mHeadsUp.release(); - // Down grade on update closes immediately if the minimum time window is satisfied. - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt()); - } - - public void testPostAndUpdateLowPriorityLaterFastFail() { - when(mClock.currentTimeMillis()).thenReturn(0L); - NotificationData.Entry a = makeNotification("a"); - mHeadsUp.showNotification(a); - reset(mMockStatusBar); - - when(mClock.currentTimeMillis()).thenReturn(LATER); - NotificationData.Entry a_prime = makeNotification("a"); - mHeadsUp.updateNotification(a_prime, false); - mHeadsUp.release(); - // Down grade on update closes immediately if the minimum time window is satisfied. - Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose(); - Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 5d884076a1f3..5d40eed3b9ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -279,7 +279,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { // TODO: Verify all fields. Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setMobileDataIndicators( - visibleArg.capture(), iconArg.capture(), typeIconArg.capture(), + visibleArg.capture(), iconArg.capture(), iconArg.capture(), typeIconArg.capture(), ArgumentCaptor.forClass(String.class).capture(), ArgumentCaptor.forClass(String.class).capture(), ArgumentCaptor.forClass(Boolean.class).capture(), diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index 2203850bd03c..3b61f9dfe798 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -276,7 +276,7 @@ public class Allocation extends BaseObj { * Enable/Disable AutoPadding for Vec3 elements. * By default: Diabled. * - * @param useAutoPadding True: enable AutoPadding; flase: disable AutoPadding + * @param useAutoPadding True: enable AutoPadding; False: disable AutoPadding * */ public void setAutoPadding(boolean useAutoPadding) { diff --git a/rs/java/android/renderscript/AllocationAdapter.java b/rs/java/android/renderscript/AllocationAdapter.java index 35d59ddca736..9bfd6ec48d4e 100644 --- a/rs/java/android/renderscript/AllocationAdapter.java +++ b/rs/java/android/renderscript/AllocationAdapter.java @@ -208,7 +208,7 @@ public class AllocationAdapter extends Allocation { } /** - * + * @hide */ public void setArray(int arrayNum, int arrayVal) { if (mAdaptedAllocation.getType().getArray(arrayNum) == 0) { diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java index 7c927fd7a0cd..e7f210b5156d 100644 --- a/rs/java/android/renderscript/RenderScript.java +++ b/rs/java/android/renderscript/RenderScript.java @@ -951,6 +951,17 @@ public class RenderScript { rsnScriptIntrinsicBLAS_Z(mContext, id, func, TransA, TransB, Side, Uplo, Diag, M, N, K, alphaX, alphaY, A, B, betaX, betaY, C, incX, incY, KL, KU); } + native void rsnScriptIntrinsicBLAS_BNNM(long con, long id, int M, int N, int K, + long A, int a_offset, long B, int b_offset, long C, int c_offset, + int c_mult_int); + synchronized void nScriptIntrinsicBLAS_BNNM(long id, int M, int N, int K, + long A, int a_offset, long B, int b_offset, long C, int c_offset, + int c_mult_int) { + validate(); + rsnScriptIntrinsicBLAS_BNNM(mContext, id, M, N, K, A, a_offset, B, b_offset, C, c_offset, c_mult_int); + } + + long mDev; long mContext; diff --git a/rs/java/android/renderscript/ScriptIntrinsicBLAS.java b/rs/java/android/renderscript/ScriptIntrinsicBLAS.java index 90d2300e553f..6cfdfee5ecae 100644 --- a/rs/java/android/renderscript/ScriptIntrinsicBLAS.java +++ b/rs/java/android/renderscript/ScriptIntrinsicBLAS.java @@ -24,7 +24,6 @@ import java.lang.annotation.RetentionPolicy; * * BLAS * - * @hide **/ public final class ScriptIntrinsicBLAS extends ScriptIntrinsic { private Allocation mLUT; @@ -176,6 +175,9 @@ public final class ScriptIntrinsicBLAS extends ScriptIntrinsic { private static final int RsBlas_zherk = 141; private static final int RsBlas_zher2k = 142; + // BLAS extensions start here + private static final int RsBlas_bnnm = 1000; + /** */ public static ScriptIntrinsicBLAS create(RenderScript rs) { @@ -1485,5 +1487,22 @@ public final class ScriptIntrinsicBLAS extends ScriptIntrinsic { } + /** + * + * 8-bit GEMM-like operation for neural networks + * + **/ + public void BNNM(Allocation A, int a_offset, Allocation B, int b_offset, Allocation C, int c_offset, int c_mult) { + validateL3(Element.U8(mRS), NO_TRANSPOSE, TRANSPOSE, 0, A, B, C); + + int M = -1, N = -1, K = -1; + M = A.getType().getY(); + N = B.getType().getY(); + K = A.getType().getX(); + + + mRS.nScriptIntrinsicBLAS_BNNM(getID(mRS), M, N, K, A.getID(mRS), a_offset, B.getID(mRS), b_offset, C.getID(mRS), c_offset, c_mult); + + } } diff --git a/rs/java/android/renderscript/Type.java b/rs/java/android/renderscript/Type.java index cc9b58b3e310..dc2378596d00 100644 --- a/rs/java/android/renderscript/Type.java +++ b/rs/java/android/renderscript/Type.java @@ -150,6 +150,7 @@ public class Type extends BaseObj { } /** + * @hide * Return the dimension of the specified array. * * @param arrayNum The array dimension to query @@ -169,6 +170,7 @@ public class Type extends BaseObj { } /** + * @hide * Return the number of array dimensions. * * @return int @@ -382,6 +384,7 @@ public class Type extends BaseObj { } /** + * @hide * Adds an array dimension to the builder * * @param dim diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index 49afa6d5e458..ae48a5f76cec 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -579,6 +579,32 @@ nScriptIntrinsicBLAS_Z(JNIEnv *_env, jobject _this, jlong con, jlong id, jint fu static void +nScriptIntrinsicBLAS_BNNM(JNIEnv *_env, jobject _this, jlong con, jlong id, jint M, jint N, jint K, + jlong A, jint a_offset, jlong B, jint b_offset, jlong C, jint c_offset, + jint c_mult_int) { + RsBlasCall call; + memset(&call, 0, sizeof(call)); + call.func = RsBlas_bnnm; + call.M = M; + call.N = N; + call.K = K; + call.a_offset = a_offset; + call.b_offset = b_offset; + call.c_offset = c_offset; + call.c_mult_int = c_mult_int; + + RsAllocation in_allocs[3]; + in_allocs[0] = (RsAllocation)A; + in_allocs[1] = (RsAllocation)B; + in_allocs[2] = (RsAllocation)C; + + rsScriptForEachMulti((RsContext)con, (RsScript)id, 0, + in_allocs, sizeof(in_allocs), nullptr, + &call, sizeof(call), nullptr, 0); +} + + +static void nAssignName(JNIEnv *_env, jobject _this, jlong con, jlong obj, jbyteArray str) { if (kLogApi) { @@ -2417,6 +2443,8 @@ static JNINativeMethod methods[] = { {"rsnScriptIntrinsicBLAS_Complex", "(JJIIIIIIIIIFFJJFFJIIII)V", (void*)nScriptIntrinsicBLAS_Complex }, {"rsnScriptIntrinsicBLAS_Z", "(JJIIIIIIIIIDDJJDDJIIII)V", (void*)nScriptIntrinsicBLAS_Z }, +{"rsnScriptIntrinsicBLAS_BNNM", "(JJIIIJIJIJII)V", (void*)nScriptIntrinsicBLAS_BNNM }, + {"rsnProgramStoreCreate", "(JZZZZZZIII)J", (void*)nProgramStoreCreate }, {"rsnProgramBindConstants", "(JJIJ)V", (void*)nProgramBindConstants }, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index bb5ff1b09bd7..82a77d27e6f0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3154,6 +3154,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING: case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL: case WindowManager.LayoutParams.TYPE_BASE_APPLICATION: case WindowManager.LayoutParams.TYPE_PHONE: case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java index 9e28b64ff8f4..26f4232ff835 100644 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ b/services/core/java/com/android/server/AssetAtlasService.java @@ -199,8 +199,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub { private final ArrayList<Bitmap> mBitmaps; private final int mPixelCount; - private Bitmap mAtlasBitmap; - Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) { mBitmaps = bitmaps; mPixelCount = pixelCount; @@ -255,8 +253,9 @@ public class AssetAtlasService extends IAssetAtlas.Stub { // We always render the atlas into a bitmap. This bitmap is then // uploaded into the GraphicBuffer using OpenGL to swizzle the content - final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight()); - if (canvas == null) return false; + final Bitmap atlasBitmap = Bitmap.createBitmap( + buffer.getWidth(), buffer.getHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(atlasBitmap); final Atlas.Entry entry = new Atlas.Entry(); @@ -265,73 +264,58 @@ public class AssetAtlasService extends IAssetAtlas.Stub { int mapIndex = 0; boolean result = false; - try { - final long startRender = System.nanoTime(); - final int count = mBitmaps.size(); - - for (int i = 0; i < count; i++) { - final Bitmap bitmap = mBitmaps.get(i); - if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { - // We have more bitmaps to pack than the current configuration - // says, we were most likely not able to detect a change in the - // list of preloaded drawables, abort and delete the configuration - if (mapIndex >= mAtlasMap.length) { - deleteDataFile(); - break; - } + final long startRender = System.nanoTime(); + final int count = mBitmaps.size(); - canvas.save(); - canvas.translate(entry.x, entry.y); - if (entry.rotated) { - canvas.translate(bitmap.getHeight(), 0.0f); - canvas.rotate(90.0f); - } - canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); - canvas.restore(); - atlasMap[mapIndex++] = bitmap.getSkBitmap(); - atlasMap[mapIndex++] = entry.x; - atlasMap[mapIndex++] = entry.y; - atlasMap[mapIndex++] = entry.rotated ? 1 : 0; + for (int i = 0; i < count; i++) { + final Bitmap bitmap = mBitmaps.get(i); + if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { + // We have more bitmaps to pack than the current configuration + // says, we were most likely not able to detect a change in the + // list of preloaded drawables, abort and delete the configuration + if (mapIndex >= mAtlasMap.length) { + deleteDataFile(); + break; } - } - - final long endRender = System.nanoTime(); - result = nUploadAtlas(buffer, mAtlasBitmap); - final long endUpload = System.nanoTime(); - if (DEBUG_ATLAS) { - float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f; - float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f; - Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)", - renderDuration + uploadDuration, renderDuration, uploadDuration)); + canvas.save(); + canvas.translate(entry.x, entry.y); + if (entry.rotated) { + canvas.translate(bitmap.getHeight(), 0.0f); + canvas.rotate(90.0f); + } + canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); + canvas.restore(); + atlasMap[mapIndex++] = bitmap.refSkPixelRef(); + atlasMap[mapIndex++] = entry.x; + atlasMap[mapIndex++] = entry.y; + atlasMap[mapIndex++] = entry.rotated ? 1 : 0; } + } - } finally { - releaseCanvas(canvas); + final long endRender = System.nanoTime(); + releaseCanvas(canvas, atlasBitmap); + result = nUploadAtlas(buffer, atlasBitmap); + atlasBitmap.recycle(); + final long endUpload = System.nanoTime(); + + if (DEBUG_ATLAS) { + float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f; + float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f; + Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)", + renderDuration + uploadDuration, renderDuration, uploadDuration)); } return result; } /** - * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE} - * is turned on, the returned Canvas will render into a local bitmap that - * will then be saved out to disk for debugging purposes. - * @param width - * @param height - */ - private Canvas acquireCanvas(int width, int height) { - mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - return new Canvas(mAtlasBitmap); - } - - /** * Releases the canvas used to render into the buffer. Calling this method * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE} * is turend on, calling this method will write the content of the atlas * to disk in /data/system/atlas.png for debugging. */ - private void releaseCanvas(Canvas canvas) { + private void releaseCanvas(Canvas canvas, Bitmap atlasBitmap) { canvas.setBitmap(null); if (DEBUG_ATLAS_TEXTURE) { @@ -340,7 +324,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { try { FileOutputStream out = new FileOutputStream(dataFile); - mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + atlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.close(); } catch (FileNotFoundException e) { // Ignore @@ -348,8 +332,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub { // Ignore } } - mAtlasBitmap.recycle(); - mAtlasBitmap = null; } } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index ef51ad62c11e..3e5eee86a777 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -112,6 +112,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int SERVICE_IBLUETOOTHGATT = 2; private final Context mContext; + private static int mBleAppCount = 0; // Locks are not provided for mName and mAddress. // They are accessed in handler or broadcast receiver, same thread context. @@ -184,11 +185,40 @@ class BluetoothManagerService extends IBluetoothManager.Stub { persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); } } + + int st = BluetoothAdapter.STATE_OFF; + if (mBluetooth != null) { + try { + st = mBluetooth.getState(); + } catch (RemoteException e) { + Log.e(TAG,"Unable to call getState", e); + } + } + Log.d(TAG, "state" + st); + if (isAirplaneModeOn()) { - // disable without persisting the setting - sendDisableMsg(); + // Clear registered LE apps to force shut-off + synchronized (this) { + mBleAppCount = 0; + } + if (st == BluetoothAdapter.STATE_BLE_ON) { + //if state is BLE_ON make sure you trigger disableBLE part + try { + if (mBluetooth != null) { + mBluetooth.onBrEdrDown(); + mEnableExternal = false; + } + } catch(RemoteException e) { + Log.e(TAG,"Unable to call onBrEdrDown", e); + } + } else if (st == BluetoothAdapter.STATE_ON){ + // disable without persisting the setting + Log.d(TAG, "Calling disable"); + sendDisableMsg(); + } } else if (mEnableExternal) { // enable without persisting the setting + Log.d(TAG, "Calling enable"); sendEnableMsg(mQuietEnableExternal); } } @@ -203,12 +233,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { sendEnableMsg(mQuietEnableExternal); } } - - if (!isNameAndAddressSet()) { - //Sync the Bluetooth name and address from the Bluetooth Adapter - if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address..."); - getNameAndAddress(); - } } } }; @@ -218,6 +242,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mContext = context; mBluetooth = null; + mBluetoothGatt = null; mBinding = false; mUnbinding = false; mEnable = false; @@ -396,6 +421,133 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } + class ClientDeathRecipient implements IBinder.DeathRecipient { + public void binderDied() { + if (DBG) Log.d(TAG, "Binder is dead - unregister Ble App"); + if (mBleAppCount > 0) --mBleAppCount; + + if (mBleAppCount == 0) { + if (DBG) Log.d(TAG, "Disabling LE only mode after application crash"); + try { + if (mBluetooth != null) { + mBluetooth.onBrEdrDown(); + } + } catch(RemoteException e) { + Log.e(TAG,"Unable to call onBrEdrDown", e); + } + } + } + } + + /** Internal death rec list */ + Map<IBinder, ClientDeathRecipient> mBleApps = new HashMap<IBinder, ClientDeathRecipient>(); + + public int updateBleAppCount(IBinder token, boolean enable) { + if (enable) { + ClientDeathRecipient r = mBleApps.get(token); + if (r == null) { + ClientDeathRecipient deathRec = new ClientDeathRecipient(); + try { + token.linkToDeath(deathRec, 0); + } catch (RemoteException ex) { + throw new IllegalArgumentException("Wake lock is already dead."); + } + mBleApps.put(token, deathRec); + synchronized (this) { + ++mBleAppCount; + } + if (DBG) Log.d(TAG, "Registered for death Notification"); + } + + } else { + ClientDeathRecipient r = mBleApps.get(token); + if (r != null) { + try { + token.linkToDeath(r, 0); + } catch (RemoteException ex) { + throw new IllegalArgumentException("Wake lock is already dead."); + } + mBleApps.remove(token); + synchronized (this) { + if (mBleAppCount > 0) --mBleAppCount; + } + if (DBG) Log.d(TAG, "Unregistered for death Notification"); + } + } + if (DBG) Log.d(TAG, "Updated BleAppCount" + mBleAppCount); + if (mBleAppCount == 0 && mEnable) { + try { + if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON)) { + if (DBG) Log.d(TAG, "Reseting the mEnable flag for clean disable"); + mEnable = false; + } + } catch (RemoteException e) { + Log.e(TAG, "getState()", e); + } + } + return mBleAppCount; + } + + /** @hide*/ + public boolean isBleAppPresent() { + if (DBG) Log.d(TAG, "isBleAppPresent() count: " + mBleAppCount); + return (mBleAppCount > 0); + } + + /** + * Action taken when GattService is turned off + */ + private void onBluetoothGattServiceUp() { + if (DBG) Log.d(TAG,"BluetoothGatt Service is Up"); + try{ + if (isBleAppPresent() == false && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + mBluetooth.onLeServiceUp(); + + // waive WRITE_SECURE_SETTINGS permission check + long callingIdentity = Binder.clearCallingIdentity(); + persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); + Binder.restoreCallingIdentity(callingIdentity); + } + } catch(RemoteException e) { + Log.e(TAG,"Unable to call onServiceUp", e); + } + } + + /** + * Inform BluetoothAdapter instances that BREDR part is down + * and turn off all service and stack if no LE app needs it + */ + private void sendBrEdrDownCallback() { + if (DBG) Log.d(TAG,"Calling sendBrEdrDownCallback callbacks"); + int n = mCallbacks.beginBroadcast(); + + if (isBleAppPresent() == false) { + try { + mBluetooth.onBrEdrDown(); + } catch(RemoteException e) { + Log.e(TAG,"Unable to call onBrEdrDown", e); + } + } + else{//need to stay at BLE ON. disconnect all Gatt connections + try{ + mBluetoothGatt.unregAll();//disconnectAll(); + } catch(RemoteException e) { + Log.e(TAG,"Unable to disconn all", e); + } + } + + Log.d(TAG,"Broadcasting onBrEdrDown() to " + n + " receivers."); + for (int i=0; i <n; i++) { + try { + mCallbacks.getBroadcastItem(i).onBrEdrDown(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to call sendBrEdrDownCallback() on callback #" + i, e); + } + } + mCallbacks.finishBroadcast(); + } + + /** @hide*/ public void getNameAndAddress() { if (DBG) { Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth + @@ -445,11 +597,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mQuietEnableExternal = false; mEnableExternal = true; // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - Binder.restoreCallingIdentity(callingIdentity); sendEnableMsg(false); } + if (DBG) Log.d(TAG, "enable returning"); return true; } @@ -508,6 +658,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else { mUnbinding=false; } + mBluetoothGatt = null; } } @@ -1034,6 +1185,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mConnection) { if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service); + onBluetoothGattServiceUp(); break; } // else must be SERVICE_IBLUETOOTH @@ -1111,12 +1263,18 @@ class BluetoothManagerService extends IBluetoothManager.Stub { bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF // unbind and rebind bluetooth service and enable bluetooth - if ((prevState == BluetoothAdapter.STATE_TURNING_ON) && + if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) && (newState == BluetoothAdapter.STATE_OFF) && (mBluetooth != null) && mEnable) { recoverBluetoothServiceFromError(); } - if (newState == BluetoothAdapter.STATE_ON) { + if ((prevState == BluetoothAdapter.STATE_TURNING_ON) && + (newState == BluetoothAdapter.STATE_BLE_ON) && + (mBluetooth != null) && mEnable) { + recoverBluetoothServiceFromError(); + } + if (newState == BluetoothAdapter.STATE_ON || + newState == BluetoothAdapter.STATE_BLE_ON) { // bluetooth is working, reset the counter if (mErrorRecoveryRetryCounter != 0) { Log.w(TAG, "bluetooth is recovered from error"); @@ -1376,39 +1534,90 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return valid; } + private void sendBleStateChanged(int prevState, int newState) { + if (DBG) Log.d(TAG,"BLE State Change Intent: " + prevState + " -> " + newState); + // Send broadcast message to everyone else + Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); + } + private void bluetoothStateChangeHandler(int prevState, int newState) { + boolean isStandardBroadcast = true; if (prevState != newState) { //Notify all proxy objects first of adapter state change - if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) { - boolean isUp = (newState==BluetoothAdapter.STATE_ON); - sendBluetoothStateCallback(isUp); - - if (isUp) { - // connect to GattService - if (mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_BLUETOOTH_LE)) { - Intent i = new Intent(IBluetoothGatt.class.getName()); - doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT); - } - } else { - //If Bluetooth is off, send service down event to proxy objects, and unbind - if (!isUp && canUnbindBluetoothService()) { - unbindAllBluetoothProfileServices(); + if (newState == BluetoothAdapter.STATE_BLE_ON + || newState == BluetoothAdapter.STATE_OFF) { + boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF + && newState == BluetoothAdapter.STATE_BLE_ON); + + if (newState == BluetoothAdapter.STATE_OFF) { + // If Bluetooth is off, send service down event to proxy objects, and unbind + if (DBG) Log.d(TAG, "Bluetooth is complete turn off"); + if (canUnbindBluetoothService()) { + if (DBG) Log.d(TAG, "Good to unbind!"); sendBluetoothServiceDownCallback(); unbindAndFinish(); + sendBleStateChanged(prevState, newState); + // Don't broadcast as it has already been broadcast before + isStandardBroadcast = false; } + + } else if (!intermediate_off) { + // connect to GattService + if (DBG) Log.d(TAG, "Bluetooth is in LE only mode"); + if (mBluetoothGatt != null) { + if (DBG) Log.d(TAG, "Calling BluetoothGattServiceUp"); + onBluetoothGattServiceUp(); + } else { + if (DBG) Log.d(TAG, "Binding Bluetooth GATT service"); + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_BLUETOOTH_LE)) { + Intent i = new Intent(IBluetoothGatt.class.getName()); + doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT); + } + } + sendBleStateChanged(prevState, newState); + //Don't broadcase this as std intent + isStandardBroadcast = false; + + } else if (intermediate_off){ + if (DBG) Log.d(TAG, "Intermediate off, back to LE only mode"); + // For LE only mode, broadcast as is + sendBleStateChanged(prevState, newState); + sendBluetoothStateCallback(false); // BT is OFF for general users + // Broadcast as STATE_OFF + newState = BluetoothAdapter.STATE_OFF; + sendBrEdrDownCallback(); } + } else if (newState == BluetoothAdapter.STATE_ON) { + boolean isUp = (newState==BluetoothAdapter.STATE_ON); + sendBluetoothStateCallback(isUp); + sendBleStateChanged(prevState, newState); + + } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON + || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) { + sendBleStateChanged(prevState, newState); + isStandardBroadcast = false; + + } else if (newState == BluetoothAdapter.STATE_TURNING_ON + || newState == BluetoothAdapter.STATE_TURNING_OFF) { + sendBleStateChanged(prevState, newState); } - //Send broadcast message to everyone else - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, - BLUETOOTH_PERM); + if (isStandardBroadcast) { + if (prevState == BluetoothAdapter.STATE_BLE_ON) { + // Show prevState of BLE_ON as OFF to standard users + prevState = BluetoothAdapter.STATE_OFF; + } + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); + } } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b5796c9e3192..b785d3dc0ce4 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.getNetworkTypeName; @@ -89,6 +90,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.Xml; @@ -709,16 +711,15 @@ public class ConnectivityService extends IConnectivityManager.Stub return mNextNetworkRequestId++; } - private void assignNextNetId(NetworkAgentInfo nai) { + private int reserveNetId() { synchronized (mNetworkForNetId) { for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) { int netId = mNextNetId; if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID; // Make sure NetID unused. http://b/16815182 - if (mNetworkForNetId.get(netId) == null) { - nai.network = new Network(netId); - mNetworkForNetId.put(netId, nai); - return; + if (!mNetIdInUse.get(netId)) { + mNetIdInUse.put(netId, true); + return netId; } } } @@ -739,7 +740,9 @@ public class ConnectivityService extends IConnectivityManager.Stub info = new NetworkInfo(nai.networkInfo); lp = new LinkProperties(nai.linkProperties); nc = new NetworkCapabilities(nai.networkCapabilities); - network = new Network(nai.network); + // Network objects are outwardly immutable so there is no point to duplicating. + // Duplicating also precludes sharing socket factories and connection pools. + network = nai.network; subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null; } info.setType(networkType); @@ -807,7 +810,9 @@ public class ConnectivityService extends IConnectivityManager.Stub info = new NetworkInfo(nai.networkInfo); lp = new LinkProperties(nai.linkProperties); nc = new NetworkCapabilities(nai.networkCapabilities); - network = new Network(nai.network); + // Network objects are outwardly immutable so there is no point to duplicating. + // Duplicating also precludes sharing socket factories and connection pools. + network = nai.network; subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null; } } @@ -873,6 +878,28 @@ public class ConnectivityService extends IConnectivityManager.Stub return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid); } + @Override + public Network getActiveNetwork() { + enforceAccessPermission(); + final int uid = Binder.getCallingUid(); + final int user = UserHandle.getUserId(uid); + int vpnNetId = NETID_UNSET; + synchronized (mVpns) { + final Vpn vpn = mVpns.get(user); + if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId(); + } + NetworkAgentInfo nai; + if (vpnNetId != NETID_UNSET) { + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(vpnNetId); + } + if (nai != null) return nai.network; + } + nai = getDefaultNetwork(); + if (nai != null && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) nai = null; + return nai != null ? nai.network : null; + } + /** * Find the first Provisioning network. * @@ -985,13 +1012,13 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public Network[] getAllNetworks() { enforceAccessPermission(); - final ArrayList<Network> result = new ArrayList(); synchronized (mNetworkForNetId) { + final Network[] result = new Network[mNetworkForNetId.size()]; for (int i = 0; i < mNetworkForNetId.size(); i++) { - result.add(new Network(mNetworkForNetId.valueAt(i).network)); + result[i] = mNetworkForNetId.valueAt(i).network; } + return result; } - return result.toArray(new Network[result.size()]); } private NetworkCapabilities getNetworkCapabilitiesAndValidation(NetworkAgentInfo nai) { @@ -1960,6 +1987,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { synchronized (mNetworkForNetId) { mNetworkForNetId.remove(nai.network.netId); + mNetIdInUse.delete(nai.network.netId); } // Just in case. mLegacyTypeTracker.remove(nai); @@ -2003,6 +2031,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mLegacyTypeTracker.remove(nai); synchronized (mNetworkForNetId) { mNetworkForNetId.remove(nai.network.netId); + mNetIdInUse.delete(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. @@ -2549,25 +2578,27 @@ public class ConnectivityService extends IConnectivityManager.Stub public void reportInetCondition(int networkType, int percentage) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); if (nai == null) return; - boolean isGood = percentage > 50; - // Revalidate if the app report does not match our current validated state. - if (isGood != nai.lastValidated) { - // Make the message logged by reportBadNetwork below less confusing. - if (DBG && isGood) log("reportInetCondition: type=" + networkType + " ok, revalidate"); - reportBadNetwork(nai.network); - } + reportNetworkConnectivity(nai.network, percentage > 50); } - public void reportBadNetwork(Network network) { + public void reportNetworkConnectivity(Network network, boolean hasConnectivity) { enforceAccessPermission(); enforceInternetPermission(); - if (network == null) return; - - final int uid = Binder.getCallingUid(); - NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + NetworkAgentInfo nai; + if (network == null) { + nai = getDefaultNetwork(); + } else { + nai = getNetworkAgentInfoForNetwork(network); + } if (nai == null) return; - if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid); + // Revalidate if the app report does not match our current validated state. + if (hasConnectivity == nai.lastValidated) return; + final int uid = Binder.getCallingUid(); + if (DBG) { + log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity + + ") by " + uid); + } synchronized (nai) { // Validating an uncreated network could result in a call to rematchNetworkAndRequests() // which isn't meant to work on uncreated networks. @@ -3026,23 +3057,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - public int findConnectionTypeForIface(String iface) { - enforceConnectivityInternalPermission(); - - if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE; - - synchronized(mNetworkForNetId) { - for (int i = 0; i < mNetworkForNetId.size(); i++) { - NetworkAgentInfo nai = mNetworkForNetId.valueAt(i); - LinkProperties lp = nai.linkProperties; - if (lp != null && iface.equals(lp.getInterfaceName()) && nai.networkInfo != null) { - return nai.networkInfo.getType(); - } - } - } - return ConnectivityManager.TYPE_NONE; - } - @Override public int checkMobileProvisioning(int suggestedTimeOutMs) { // TODO: Remove? Any reason to trigger a provisioning check? @@ -3296,7 +3310,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); } } @@ -3441,6 +3455,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @Override + public boolean requestBwUpdate(Network network) { + enforceAccessPermission(); + NetworkAgentInfo nai = null; + if (network == null) { + return false; + } + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai != null) { + nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE); + return true; + } + return false; + } + + private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) { // if UID is restricted, don't allow them to bring up metered APNs if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) @@ -3548,14 +3580,23 @@ public class ConnectivityService extends IConnectivityManager.Stub * and the are the highest scored network available. * the are keyed off the Requests requestId. */ + // TODO: Yikes, this is accessed on multiple threads: add synchronization. private final SparseArray<NetworkAgentInfo> mNetworkForRequestId = new SparseArray<NetworkAgentInfo>(); + // NOTE: Accessed on multiple threads, must be synchronized on itself. + @GuardedBy("mNetworkForNetId") private final SparseArray<NetworkAgentInfo> mNetworkForNetId = new SparseArray<NetworkAgentInfo>(); + // NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId. + // An entry is first added to mNetIdInUse, prior to mNetworkForNetId, so + // there may not be a strict 1:1 correlation between the two. + @GuardedBy("mNetworkForNetId") + private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray(); // NetworkAgentInfo keyed off its connecting messenger // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays + // NOTE: Only should be accessed on ConnectivityServiceThread, except dump(). private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos = new HashMap<Messenger, NetworkAgentInfo>(); @@ -3570,7 +3611,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai == getDefaultNetwork(); } - public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, + public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int currentScore, NetworkMisc networkMisc) { enforceConnectivityInternalPermission(); @@ -3578,20 +3619,23 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network // satisfies mDefaultRequest. NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), - new NetworkInfo(networkInfo), new LinkProperties(linkProperties), - new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler, - new NetworkMisc(networkMisc), mDefaultRequest); + new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties( + linkProperties), new NetworkCapabilities(networkCapabilities), currentScore, + mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest); synchronized (this) { nai.networkMonitor.systemReady = mSystemReady; } if (DBG) log("registerNetworkAgent " + nai); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai)); + return nai.network.netId; } private void handleRegisterNetworkAgent(NetworkAgentInfo na) { if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(na.messenger, na); - assignNextNetId(na); + synchronized (mNetworkForNetId) { + mNetworkForNetId.put(na.network.netId, na); + } na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger); NetworkInfo networkInfo = na.networkInfo; na.networkInfo = null; @@ -4211,9 +4255,10 @@ public class ConnectivityService extends IConnectivityManager.Stub networkAgent.created = true; updateLinkProperties(networkAgent, null); notifyIfacesChanged(); - notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); + networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); scheduleUnvalidatedPrompt(networkAgent); + if (networkAgent.isVPN()) { // Temporarily disable the default proxy (not global). synchronized (mProxyLock) { @@ -4226,9 +4271,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } // TODO: support proxy per network. } + // Consider network even though it is not yet validated. rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED, ReapUnvalidatedNetworks.REAP); + + // This has to happen after matching the requests, because callbacks are just requests. + notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index ee73b1a6ea84..16689eed5f12 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -388,7 +388,9 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] currentHandle = getCurrentHandle(userId); if (pattern == null) { + getGateKeeperService().clearSecureUserId(userId); mStorage.writePatternHash(null, userId); + maybeUpdateKeystore(null, userId); return; } @@ -414,7 +416,9 @@ public class LockSettingsService extends ILockSettings.Stub { byte[] currentHandle = getCurrentHandle(userId); if (password == null) { + getGateKeeperService().clearSecureUserId(userId); mStorage.writePasswordHash(null, userId); + maybeUpdateKeystore(null, userId); return; } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index d1532331ef32..908ee22c75ec 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -181,6 +181,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private PreciseCallState mPreciseCallState = new PreciseCallState(); + private boolean mCarrierNetworkChangeState = false; + private PreciseDataConnectionState mPreciseDataConnectionState = new PreciseDataConnectionState(); @@ -607,6 +609,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) { + try { + r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -790,6 +799,31 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { broadcastSignalStrengthChanged(signalStrength, subId); } + @Override + public void notifyCarrierNetworkChange(boolean active) { + if (!checkNotifyPermissionOrCarrierPrivilege("notifyCarrierNetworkChange()")) { + return; + } + if (VDBG) { + log("notifyCarrierNetworkChange: active=" + active); + } + + synchronized (mRecords) { + mCarrierNetworkChangeState = active; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE)) { + try { + r.callback.onCarrierNetworkChange(active); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + public void notifyCellInfo(List<CellInfo> cellInfo) { notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo); } @@ -1422,9 +1456,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRECISE_PHONE_STATE); } + private boolean checkNotifyPermissionOrCarrierPrivilege(String method) { + if (checkNotifyPermission() || checkCarrierPrivilege()) { + return true; + } + + String msg = "Modify Phone State or Carrier Privilege Permission Denial: " + method + + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); + if (DBG) log(msg); + return false; + } + private boolean checkNotifyPermission(String method) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - == PackageManager.PERMISSION_GRANTED) { + if (checkNotifyPermission()) { return true; } String msg = "Modify Phone State Permission Denial: " + method + " from pid=" @@ -1433,6 +1477,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { return false; } + private boolean checkNotifyPermission() { + return mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean checkCarrierPrivilege() { + TelephonyManager tm = TelephonyManager.getDefault(); + String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid()); + for (String pkg : pkgs) { + if (tm.checkCarrierPrivilegesForPackage(pkg) == + TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + + return false; + } + private void checkListenerPermission(int events) { if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 69e61f67eb74..772a15c1bf64 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -190,6 +190,17 @@ public class Watchdog extends Thread { } } + /** Monitor for checking the availability of binder threads. The monitor will block until + * there is a binder thread available to process in coming IPCs to make sure other processes + * can still communicate with the service. + */ + private static final class BinderThreadMonitor implements Watchdog.Monitor { + @Override + public void monitor() { + Binder.blockUntilThreadAvailable(); + } + } + public interface Monitor { void monitor(); } @@ -227,6 +238,9 @@ public class Watchdog extends Thread { // And the display thread. mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(), "display thread", DEFAULT_TIMEOUT)); + + // Initialize monitor for Binder threads. + addMonitor(new BinderThreadMonitor()); } public void init(Context context, ActivityManagerService activity) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 37f0e351785a..b65893274c13 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -26,10 +26,14 @@ import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; -import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; +import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; import static com.android.server.am.ActivityManagerDebugConfig.*; +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; -import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -2179,15 +2183,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public static final class BinderThreadMonitor implements Watchdog.Monitor { - /** This method will block until there is a binder thread available to process - * in coming IPCs to make sure other processes can still communicate with the service. - */ - @Override - public void monitor() { - Binder.blockUntilThreadAvailable(); - } - } // Note: This method is invoked on the main thread but may need to attach various // handlers to other threads. So take care to be explicit about the looper. public ActivityManagerService(Context systemContext) { @@ -2282,7 +2277,6 @@ public final class ActivityManagerService extends ActivityManagerNative }; Watchdog.getInstance().addMonitor(this); - Watchdog.getInstance().addMonitor(new BinderThreadMonitor()); Watchdog.getInstance().addThread(mHandler); } @@ -3992,13 +3986,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (rootR == null) { Slog.w(TAG, "Finishing task with all activities already finished"); } - // Do not allow task to finish in Lock Task mode. - if (tr == mStackSupervisor.mLockTaskModeTask) { - if (rootR == r) { - Slog.i(TAG, "Not finishing task in lock task mode"); - mStackSupervisor.showLockTaskToast(); - return false; - } + // Do not allow task to finish if last task in lockTask mode. Launchable apps can + // finish themselves. + if (tr.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE && rootR == r && + mStackSupervisor.isLastLockedTask(tr)) { + Slog.i(TAG, "Not finishing task in lock task mode"); + mStackSupervisor.showLockTaskToast(); + return false; } if (mController != null) { // Find the first activity that is not finishing. @@ -4152,20 +4146,18 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { ActivityRecord r = ActivityRecord.isInStackLocked(token); - - ActivityRecord rootR = r.task.getRootActivity(); - // Do not allow task to finish in Lock Task mode. - if (r.task == mStackSupervisor.mLockTaskModeTask) { - if (rootR == r) { - mStackSupervisor.showLockTaskToast(); - return false; - } + if (r == null) { + return false; } - boolean res = false; - if (r != null) { - res = r.task.stack.finishActivityAffinityLocked(r); + + // Do not allow the last non-launchable task to finish in Lock Task mode. + final TaskRecord task = r.task; + if (task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE && + mStackSupervisor.isLastLockedTask(task) && task.getRootActivity() == r) { + mStackSupervisor.showLockTaskToast(); + return false; } - return res; + return task.stack.finishActivityAffinityLocked(r); } finally { Binder.restoreCallingIdentity(origId); } @@ -7859,6 +7851,27 @@ public final class ActivityManagerService extends ActivityManagerNative rti.lastActiveTime = tr.lastActiveTime; rti.affiliatedTaskId = tr.mAffiliatedTaskId; rti.affiliatedTaskColor = tr.mAffiliatedTaskColor; + rti.numActivities = 0; + + ActivityRecord base = null; + ActivityRecord top = null; + ActivityRecord tmp; + + for (int i = tr.mActivities.size() - 1; i >= 0; --i) { + tmp = tr.mActivities.get(i); + if (tmp.finishing) { + continue; + } + base = tmp; + if (top == null || (top.state == ActivityState.INITIALIZING)) { + top = base; + } + rti.numActivities++; + } + + rti.baseActivity = (base != null) ? base.intent.getComponent() : null; + rti.topActivity = (top != null) ? top.intent.getComponent() : null; + return rti; } @@ -8346,9 +8359,9 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot); - if (taskId >= 0) { - if ((mStackSupervisor.mLockTaskModeTask != null) - && (mStackSupervisor.mLockTaskModeTask.taskId == taskId)) { + final TaskRecord task = mRecentTasks.taskForIdLocked(taskId); + if (task != null) { + if (mStackSupervisor.isLockedTask(task)) { mStackSupervisor.showLockTaskToast(); return false; } @@ -8530,47 +8543,45 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void updateLockTaskPackages(int userId, String[] packages) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { throw new SecurityException("updateLockTaskPackage called from non-system process"); } synchronized (this) { mLockTaskPackages.put(userId, packages); + mStackSupervisor.onLockTaskPackagesUpdatedLocked(); } } - private boolean isLockTaskAuthorizedLocked(String pkg) { - String[] packages = mLockTaskPackages.get(mCurrentUserId); - if (packages == null) { - return false; - } - for (int i = packages.length - 1; i >= 0; --i) { - if (pkg.equals(packages[i])) { - return true; - } - } - return false; - } void startLockTaskModeLocked(TaskRecord task) { - final String pkg = task.intent.getComponent().getPackageName(); + if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { + return; + } + // isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode // is initiated by system after the pinning request was shown and locked mode is initiated // by an authorized app directly - boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID; + final int callingUid = Binder.getCallingUid(); + boolean isSystemInitiated = callingUid == Process.SYSTEM_UID; long ident = Binder.clearCallingIdentity(); try { - if (!isSystemInitiated && !isLockTaskAuthorizedLocked(pkg)) { - StatusBarManagerInternal statusBarManager = - LocalServices.getService(StatusBarManagerInternal.class); - if (statusBarManager != null) { - statusBarManager.showScreenPinningRequest(); + final ActivityStack stack = mStackSupervisor.getFocusedStack(); + if (!isSystemInitiated) { + task.mLockTaskUid = callingUid; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) { + // startLockTask() called by app and task mode is lockTaskModeDefault. + StatusBarManagerInternal statusBarManager = + LocalServices.getService(StatusBarManagerInternal.class); + if (statusBarManager != null) { + statusBarManager.showScreenPinningRequest(); + } + return; } - return; - } - final ActivityStack stack = mStackSupervisor.getFocusedStack(); - if (!isSystemInitiated && (stack == null || task != stack.topTask())) { - throw new IllegalArgumentException("Invalid task, not in foreground"); + if (stack == null || task != stack.topTask()) { + throw new IllegalArgumentException("Invalid task, not in foreground"); + } } mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ? ActivityManager.LOCK_TASK_MODE_PINNED : @@ -8624,23 +8635,15 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void stopLockTaskMode() { - // Verify that the user matches the package of the intent for the TaskRecord - // we are locked to or systtem. This will ensure the same caller for startLockTaskMode - // and stopLockTaskMode. - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID) { - try { - String pkg = - mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName(); - int uid = mContext.getPackageManager().getPackageUid(pkg, - Binder.getCallingUserHandle().getIdentifier()); - if (uid != callingUid) { - throw new SecurityException("Invalid uid, expected " + uid); - } - } catch (NameNotFoundException e) { - Log.d(TAG, "stopLockTaskMode " + e); - return; - } + final TaskRecord lockTask = mStackSupervisor.getLockedTaskLocked(); + if (lockTask == null) { + // Our work here is done. + return; + } + // Ensure the same caller for startLockTaskMode and stopLockTaskMode. + if (getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED && + Binder.getCallingUid() != lockTask.mLockTaskUid) { + throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid); } long ident = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 2362d287cf7c..33f915f24561 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -16,7 +16,7 @@ package com.android.server.am; -import static android.content.pm.ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN; +import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static com.android.server.am.ActivityManagerDebugConfig.*; @@ -363,19 +363,9 @@ final class ActivityStack { mOverrideConfig = Configuration.EMPTY; } - /** - * Checks whether the userid is a profile of the current user. - */ - private boolean isCurrentProfileLocked(int userId) { - if (userId == mCurrentUser) return true; - for (int i = 0; i < mService.mCurrentProfileIds.length; i++) { - if (mService.mCurrentProfileIds[i] == userId) return true; - } - return false; - } - boolean okToShowLocked(ActivityRecord r) { - return isCurrentProfileLocked(r.userId) || (r.info.flags & FLAG_SHOW_ON_LOCK_SCREEN) != 0; + return mStackSupervisor.isCurrentProfileLocked(r.userId) + || (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0; } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { @@ -619,12 +609,13 @@ final class ActivityStack { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); - final boolean notCurrentUserTask = !isCurrentProfileLocked(task.userId); + final boolean notCurrentUserTask = + !mStackSupervisor.isCurrentProfileLocked(task.userId); final ArrayList<ActivityRecord> activities = task.mActivities; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = activities.get(activityNdx); - if (notCurrentUserTask && (r.info.flags & FLAG_SHOW_ON_LOCK_SCREEN) == 0) { + if (notCurrentUserTask && (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) { return null; } if (!r.finishing && r.intent.getComponent().equals(cls) && r.userId == userId) { @@ -655,7 +646,7 @@ final class ActivityStack { // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is // okay to show the activity when locked. - if (isCurrentProfileLocked(task.userId) + if (mStackSupervisor.isCurrentProfileLocked(task.userId) || task.topRunningActivityLocked(null) != null) { if (DEBUG_TASKS) Slog.d(TAG_TASKS, "switchUserLocked: stack=" + getStackId() + " moving " + task + " to top"); @@ -817,8 +808,14 @@ final class ActivityStack { final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming, boolean dontWait) { if (mPausingActivity != null) { - Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity); - completePauseLocked(false); + Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity + + " state=" + mPausingActivity.state); + if (!mService.isSleeping()) { + // Avoid recursion among check for sleep and complete pause during sleeping. + // Because activity will be paused immediately after resume, just let pause + // be completed by the order of activity paused from clients. + completePauseLocked(false); + } } ActivityRecord prev = mResumedActivity; if (prev == null) { @@ -2024,13 +2021,13 @@ final class ActivityStack { // Now put task at top. int taskNdx = mTaskHistory.size(); final boolean notShownWhenLocked = - (newActivity != null && (newActivity.info.flags & FLAG_SHOW_ON_LOCK_SCREEN) == 0) + (newActivity != null && (newActivity.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) || (newActivity == null && task.topRunningActivityLocked(null) == null); - if (!isCurrentProfileLocked(task.userId) && notShownWhenLocked) { + if (!mStackSupervisor.isCurrentProfileLocked(task.userId) && notShownWhenLocked) { // Put non-current user tasks below current user tasks. while (--taskNdx >= 0) { final TaskRecord tmpTask = mTaskHistory.get(taskNdx); - if (!isCurrentProfileLocked(tmpTask.userId) + if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId) || tmpTask.topRunningActivityLocked(null) == null) { break; } @@ -2074,7 +2071,7 @@ final class ActivityStack { r.putInHistory(); mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); if (VALIDATE_TOKENS) { @@ -2138,7 +2135,7 @@ final class ActivityStack { } mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); boolean doShow = true; if (newTask) { @@ -2190,7 +2187,7 @@ final class ActivityStack { // because there is nothing for it to animate on top of. mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); ActivityOptions.abort(options); options = null; @@ -2875,7 +2872,7 @@ final class ActivityStack { } if (endTask) { - mStackSupervisor.endLockTaskModeIfTaskEnding(task); + mStackSupervisor.removeLockedTaskLocked(task); } } else if (r.state != ActivityState.PAUSING) { // If the activity is PAUSING, we will complete the finish once @@ -3674,8 +3671,7 @@ final class ActivityStack { } Slog.i(TAG, "moveTaskToBack: " + tr); - - mStackSupervisor.endLockTaskModeIfTaskEnding(tr); + mStackSupervisor.removeLockedTaskLocked(tr); // If we have a watcher, preflight the move before committing to it. First check // for *other* available tasks, but if none are available, then try again allowing the @@ -4062,6 +4058,7 @@ final class ActivityStack { } ActivityRecord r = null; ActivityRecord top = null; + ActivityRecord tmp; int numActivities = 0; int numRunning = 0; final ArrayList<ActivityRecord> activities = task.mActivities; @@ -4069,7 +4066,11 @@ final class ActivityStack { continue; } for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { - r = activities.get(activityNdx); + tmp = activities.get(activityNdx); + if (tmp.finishing) { + continue; + } + r = tmp; // Initialize state for next task if needed. if (top == null || (top.state == ActivityState.INITIALIZING)) { @@ -4240,7 +4241,7 @@ final class ActivityStack { */ void removeTask(TaskRecord task, String reason, boolean notMoving) { if (notMoving) { - mStackSupervisor.endLockTaskModeIfTaskEnding(task); + mStackSupervisor.removeLockedTaskLocked(task); mWindowManager.removeTask(task.taskId); } @@ -4345,4 +4346,10 @@ final class ActivityStack { mFullscreen = Configuration.EMPTY.equals(mOverrideConfig); return !mOverrideConfig.equals(oldConfig); } + + void onLockTaskPackagesUpdatedLocked() { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + mTaskHistory.get(taskNdx).setLockTaskAuth(); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index c2f6bfdcda35..1585f6114647 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -17,10 +17,15 @@ package com.android.server.am; import static android.Manifest.permission.START_ANY_ACTIVITY; +import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; @@ -28,6 +33,10 @@ import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityStack.ActivityState.*; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import android.app.Activity; import android.app.ActivityManager; @@ -261,17 +270,17 @@ public final class ActivityStackSupervisor implements DisplayListener { // TODO: Add listener for removal of references. /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */ - private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>(); + private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>(); /** Mapping from displayId to display current state */ - private final SparseArray<ActivityDisplay> mActivityDisplays = - new SparseArray<ActivityDisplay>(); + private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>(); InputManagerInternal mInputManagerInternal; - /** If non-null then the task specified remains in front and no other tasks may be started - * until the task exits or #stopLockTaskMode() is called. */ - TaskRecord mLockTaskModeTask; + /** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks + * may be finished until there is only one entry left. If this is empty the system is not + * in lockTask mode. */ + ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>(); /** Store the current lock task mode. Possible values: * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, * {@link ActivityManager#LOCK_TASK_MODE_PINNED} @@ -282,8 +291,7 @@ public final class ActivityStackSupervisor implements DisplayListener { */ private LockTaskNotify mLockTaskNotify; - final ArrayList<PendingActivityLaunch> mPendingActivityLaunches - = new ArrayList<PendingActivityLaunch>(); + final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); /** Used to keep resumeTopActivityLocked() from being entered recursively */ boolean inResumeTopActivity; @@ -796,7 +804,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>(); + ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>(); runningTaskLists.add(stackTaskList); stack.getTasksLocked(stackTaskList, callingUid, allowed); } @@ -894,8 +902,8 @@ public final class ActivityStackSupervisor implements DisplayListener { intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, - profilerInfo, userId); + ActivityInfo aInfo = + resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId); ActivityContainer container = (ActivityContainer)iContainer; synchronized (mService) { @@ -1170,7 +1178,12 @@ public final class ActivityStackSupervisor implements DisplayListener { mService.updateLruProcessLocked(app, true, null); mService.updateOomAdjLocked(); - final ActivityStack stack = r.task.stack; + final TaskRecord task = r.task; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) { + setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "lockTaskLaunchMode attribute"); + } + + final ActivityStack stack = task.stack; try { if (app.thread == null) { throw new RemoteException(); @@ -1187,11 +1200,11 @@ public final class ActivityStackSupervisor implements DisplayListener { if (andResume) { EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, r.userId, System.identityHashCode(r), - r.task.taskId, r.shortComponentName); + task.taskId, r.shortComponentName); } if (r.isHomeActivity() && r.isNotResolverActivity()) { // Home process is the root process of the task. - mService.mHomeProcess = r.task.mActivities.get(0).app; + mService.mHomeProcess = task.mActivities.get(0).app; } mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); r.sleeping = false; @@ -1233,7 +1246,7 @@ public final class ActivityStackSupervisor implements DisplayListener { app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration), new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage, - r.task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, + task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo); if ((app.info.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { @@ -1369,8 +1382,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + if (err == ActivityManager.START_SUCCESS) { - final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + "} from uid " + callingUid + " on display " + (container == null ? (mFocusedStack == null ? @@ -1394,7 +1408,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final int launchFlags = intent.getFlags(); - if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { + if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { @@ -1438,6 +1452,13 @@ public final class ActivityStackSupervisor implements DisplayListener { err = ActivityManager.START_CLASS_NOT_FOUND; } + if (err == ActivityManager.START_SUCCESS + && !isCurrentProfileLocked(userId) + && (aInfo.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) { + // Trying to launch a background activity that doesn't show for all users. + err = ActivityManager.START_NOT_CURRENT_USER_ACTIVITY; + } + if (err == ActivityManager.START_SUCCESS && sourceRecord != null && sourceRecord.task.voiceSession != null) { // If this activity is being launched as part of a voice session, we need @@ -1946,7 +1967,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); } - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and // the client said not to do anything if that // is the case, so this is it! And for paranoia, make @@ -1964,8 +1985,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } return ActivityManager.START_RETURN_INTENT_TO_CALLER; } - if ((launchFlags & - (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { // The caller has requested to completely replace any // existing task with its new activity. Well that should @@ -2128,10 +2148,6 @@ public final class ActivityStackSupervisor implements DisplayListener { // Should this be considered a new task? if (r.resultTo == null && inTask == null && !addingToTask && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - if (isLockTaskModeViolation(reuseTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } newTask = true; targetStack = computeStackFocus(r, newTask); targetStack.moveToFront("startingNewTask"); @@ -2147,6 +2163,10 @@ public final class ActivityStackSupervisor implements DisplayListener { } else { r.setTask(reuseTask, taskToAffiliate); } + if (isLockTaskModeViolation(r.task)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } if (!movedHome) { if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) @@ -2815,7 +2835,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityRecord r = activities.get(activityNdx); mWindowManager.addAppToken(0, r.appToken, task.taskId, stack.mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); } @@ -3220,6 +3240,15 @@ public final class ActivityStackSupervisor implements DisplayListener { mStartingBackgroundUsers.add(uss); } + /** Checks whether the userid is a profile of the current user. */ + boolean isCurrentProfileLocked(int userId) { + if (userId == mCurrentUser) return true; + for (int i = 0; i < mService.mCurrentProfileIds.length; i++) { + if (mService.mCurrentProfileIds[i] == userId) return true; + } + return false; + } + final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { ArrayList<ActivityRecord> stops = null; @@ -3292,6 +3321,7 @@ public final class ActivityStackSupervisor implements DisplayListener { pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId); pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); pw.print(prefix); pw.println("mActivityContainers=" + mActivityContainers); + pw.print(prefix); pw.println("mLockTaskModeTasks" + mLockTaskModeTasks); } ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { @@ -3592,6 +3622,32 @@ public final class ActivityStackSupervisor implements DisplayListener { return list; } + TaskRecord getLockedTaskLocked() { + final int top = mLockTaskModeTasks.size() - 1; + if (top >= 0) { + return mLockTaskModeTasks.get(top); + } + return null; + } + + boolean isLockedTask(TaskRecord task) { + return mLockTaskModeTasks.contains(task); + } + + boolean isLastLockedTask(TaskRecord task) { + return mLockTaskModeTasks.size() == 1 && mLockTaskModeTasks.contains(task); + } + + void removeLockedTaskLocked(final TaskRecord task) { + if (mLockTaskModeTasks.remove(task) && mLockTaskModeTasks.isEmpty()) { + // Last one. + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.arg1 = task.userId; + lockTaskMsg.what = LOCK_TASK_END_MSG; + mHandler.sendMessage(lockTaskMsg); + } + } + void showLockTaskToast() { mLockTaskNotify.showToast(mLockTaskModeState); } @@ -3599,42 +3655,93 @@ public final class ActivityStackSupervisor implements DisplayListener { void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason) { if (task == null) { // Take out of lock task mode if necessary - if (mLockTaskModeTask != null) { - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mLockTaskModeTask = null; - mHandler.sendMessage(lockTaskMsg); + final TaskRecord lockedTask = getLockedTaskLocked(); + if (lockedTask != null) { + removeLockedTaskLocked(lockedTask); + if (!mLockTaskModeTasks.isEmpty()) { + // There are locked tasks remaining, can only finish this task, not unlock it. + lockedTask.performClearTaskLocked(); + resumeTopActivitiesLocked(); + return; + } } return; } + + // Should have already been checked, but do it again. + if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { + return; + } if (isLockTaskModeViolation(task)) { - Slog.e(TAG, "setLockTaskMode: Attempt to start a second Lock Task Mode task."); + Slog.e(TAG, "setLockTaskMode: Attempt to start an unauthorized lock task."); return; } - mLockTaskModeTask = task; + + if (mLockTaskModeTasks.isEmpty()) { + // First locktask. + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.obj = task.intent.getComponent().getPackageName(); + lockTaskMsg.arg1 = task.userId; + lockTaskMsg.what = LOCK_TASK_START_MSG; + lockTaskMsg.arg2 = lockTaskModeState; + mHandler.sendMessage(lockTaskMsg); + } + // Add it or move it to the top. + mLockTaskModeTasks.remove(task); + mLockTaskModeTasks.add(task); + + if (task.mLockTaskUid == -1) { + task.mLockTaskUid = task.mCallingUid; + } findTaskToMoveToFrontLocked(task, 0, null, reason); resumeTopActivitiesLocked(); - - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_START_MSG; - lockTaskMsg.arg2 = lockTaskModeState; - mHandler.sendMessage(lockTaskMsg); } boolean isLockTaskModeViolation(TaskRecord task) { - return mLockTaskModeTask != null && mLockTaskModeTask != task; + if (getLockedTaskLocked() == task) { + return false; + } + final int lockTaskAuth = task.mLockTaskAuth; + switch (lockTaskAuth) { + case LOCK_TASK_AUTH_DONT_LOCK: + return !mLockTaskModeTasks.isEmpty(); + case LOCK_TASK_AUTH_LAUNCHABLE: + case LOCK_TASK_AUTH_WHITELISTED: + return false; + case LOCK_TASK_AUTH_PINNABLE: + // Pinnable tasks can't be launched on top of locktask tasks. + return !mLockTaskModeTasks.isEmpty(); + default: + Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth); + return true; + } } - void endLockTaskModeIfTaskEnding(TaskRecord task) { - if (mLockTaskModeTask != null && mLockTaskModeTask == task) { - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mLockTaskModeTask = null; - mHandler.sendMessage(lockTaskMsg); + void onLockTaskPackagesUpdatedLocked() { + boolean didSomething = false; + for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx); + if (lockedTask.mLockTaskMode != LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED) { + continue; + } + final boolean wasLaunchable = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE; + lockedTask.setLockTaskAuth(); + if (wasLaunchable && lockedTask.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE) { + // Lost whitelisting authorization. End it now. + removeLockedTaskLocked(lockedTask); + lockedTask.performClearTaskLocked(); + didSomething = true; + } + } + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.onLockTaskPackagesUpdatedLocked(); + } + } + if (didSomething) { + resumeTopActivitiesLocked(); } } @@ -3734,11 +3841,10 @@ public final class ActivityStackSupervisor implements DisplayListener { mLockTaskModeState = msg.arg2; if (getStatusBarService() != null) { int flags = 0; - if (mLockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) { + if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { flags = StatusBarManager.DISABLE_MASK & (~StatusBarManager.DISABLE_BACK); - } else if (mLockTaskModeState == - ActivityManager.LOCK_TASK_MODE_PINNED) { + } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { flags = StatusBarManager.DISABLE_MASK & (~StatusBarManager.DISABLE_BACK) & (~StatusBarManager.DISABLE_HOME) @@ -3776,8 +3882,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean shouldLockKeyguard = Settings.Secure.getInt( mService.mContext.getContentResolver(), Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0; - if (mLockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED && - shouldLockKeyguard) { + if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(); new LockPatternUtils(mService.mContext) @@ -3789,7 +3894,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } catch (RemoteException ex) { throw new RuntimeException(ex); } finally { - mLockTaskModeState = ActivityManager.LOCK_TASK_MODE_NONE; + mLockTaskModeState = LOCK_TASK_MODE_NONE; } } break; case CONTAINER_CALLBACK_TASK_LIST_EMPTY: { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index bfc4fc7c89e0..905adc0b02d9 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -18,8 +18,6 @@ package com.android.server.am; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -40,7 +38,6 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.WorkSource; -import android.telephony.DataConnectionRealTimeInfo; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Slog; @@ -71,8 +68,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub final BatteryStatsImpl mStats; final BatteryStatsHandler mHandler; Context mContext; - private boolean mBluetoothPendingStats; - private BluetoothHeadset mBluetoothHeadset; PowerManagerInternal mPowerManagerInternal; class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync { @@ -87,11 +82,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void handleMessage(Message msg) { switch (msg.what) { case MSG_SYNC_EXTERNAL_STATS: - updateExternalStats(); + updateExternalStats((String)msg.obj); break; case MSG_WRITE_TO_DISK: - updateExternalStats(); + updateExternalStats("write"); synchronized (mStats) { mStats.writeAsyncLocked(); } @@ -100,9 +95,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub } @Override - public void scheduleSync() { + public void scheduleSync(String reason) { if (!hasMessages(MSG_SYNC_EXTERNAL_STATS)) { - sendEmptyMessage(MSG_SYNC_EXTERNAL_STATS); + Message msg = Message.obtain(this, MSG_SYNC_EXTERNAL_STATS, reason); + sendMessage(msg); } } } @@ -140,7 +136,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); - updateExternalStats(); + updateExternalStats("shutdown"); synchronized (mStats) { mStats.shutdownLocked(); } @@ -231,7 +227,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - updateExternalStats(); + updateExternalStats("get-stats"); synchronized (mStats) { mStats.writeToParcel(out, 0); } @@ -246,7 +242,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - updateExternalStats(); + updateExternalStats("get-stats"); synchronized (mStats) { mStats.writeToParcel(out, 0); } @@ -569,7 +565,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub // There was a change in WiFi power state. // Collect data now for the past activity. - mHandler.scheduleSync(); + mHandler.scheduleSync("wifi-data"); + synchronized (mStats) { + mStats.noteWifiRadioPowerState(powerState, tsNanos); + } } public void noteWifiRunning(WorkSource ws) { @@ -614,56 +613,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - public void noteBluetoothOn() { - enforceCallingPermission(); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - } - synchronized (mStats) { - if (mBluetoothHeadset != null) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(mBluetoothHeadset); - } else { - mBluetoothPendingStats = true; - } - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mBluetoothHeadset = (BluetoothHeadset) proxy; - synchronized (mStats) { - if (mBluetoothPendingStats) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(mBluetoothHeadset); - mBluetoothPendingStats = false; - } - } - } - - public void onServiceDisconnected(int profile) { - mBluetoothHeadset = null; - } - }; - - public void noteBluetoothOff() { - enforceCallingPermission(); - synchronized (mStats) { - mBluetoothPendingStats = false; - mStats.noteBluetoothOffLocked(); - } - } - - public void noteBluetoothState(int bluetoothState) { - enforceCallingPermission(); - synchronized (mStats) { - mStats.noteBluetoothStateLocked(bluetoothState); - } - } - public void noteFullWifiLockAcquired(int uid) { enforceCallingPermission(); synchronized (mStats) { @@ -820,7 +769,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub // Sync external stats first as the battery has changed states. If we don't sync // immediately here, we may not collect the relevant data later. - updateExternalStats(); + updateExternalStats("battery-state"); synchronized (mStats) { mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt); } @@ -974,9 +923,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println("Battery stats reset."); noOutput = true; } - updateExternalStats(); + updateExternalStats("dump"); } else if ("--write".equals(arg)) { - updateExternalStats(); + updateExternalStats("dump"); synchronized (mStats) { mStats.writeSyncLocked(); pw.println("Battery stats written."); @@ -1047,7 +996,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } // Fetch data from external sources and update the BatteryStatsImpl object with them. - updateExternalStats(); + updateExternalStats("dump"); if (useCheckinFormat) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); @@ -1178,7 +1127,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub * We first grab a lock specific to this method, then once all the data has been collected, * we grab the mStats lock and update the data. */ - void updateExternalStats() { + void updateExternalStats(String reason) { synchronized (mExternalStatsLock) { if (mContext == null) { // We haven't started yet (which means the BatteryStatsImpl object has @@ -1189,6 +1138,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked(); final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked(); synchronized (mStats) { + if (mStats.mRecordAllHistory) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + mStats.addHistoryEventLocked(elapsedRealtime, uptime, + BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0); + } mStats.updateKernelWakelocksLocked(); mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime()); mStats.updateWifiStateLocked(wifiEnergyInfo); diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 82e6d4710c0b..790a78dcb17b 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -18,6 +18,10 @@ package com.android.server.am; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; @@ -121,6 +125,20 @@ final class TaskRecord { boolean mResizeable; // Activities in the task resizeable. Based on the resizable setting of // the root activity. + int mLockTaskMode; // Which tasklock mode to launch this task in. One of + // ActivityManager.LOCK_TASK_LAUNCH_MODE_* + /** Can't be put in lockTask mode. */ + final static int LOCK_TASK_AUTH_DONT_LOCK = 0; + /** Can enter lockTask with user approval if not already in lockTask. */ + final static int LOCK_TASK_AUTH_PINNABLE = 1; + /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */ + final static int LOCK_TASK_AUTH_LAUNCHABLE = 2; + /** Enters LOCK_TASK_MODE_LOCKED via startLockTask(), enters LOCK_TASK_MODE_PINNED from + * Overview. Can start over existing lockTask task. */ + final static int LOCK_TASK_AUTH_WHITELISTED = 3; + int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE; + + int mLockTaskUid = -1; // The uid of the application that called startLockTask(). // This represents the last resolved activity values for this task // NOTE: This value needs to be persisted with each task @@ -186,6 +204,8 @@ final class TaskRecord { voiceInteractor = _voiceInteractor; isAvailable = true; mActivities = new ArrayList<>(); + mCallingUid = info.applicationInfo.uid; + mCallingPackage = info.packageName; setIntent(_intent, info); } @@ -201,12 +221,12 @@ final class TaskRecord { voiceInteractor = null; isAvailable = true; mActivities = new ArrayList<>(); + mCallingUid = info.applicationInfo.uid; + mCallingPackage = info.packageName; setIntent(_intent, info); taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; isPersistable = true; - mCallingUid = info.applicationInfo.uid; - mCallingPackage = info.packageName; // Clamp to [1, max]. maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); @@ -215,8 +235,6 @@ final class TaskRecord { mTaskToReturnTo = HOME_ACTIVITY_TYPE; userId = UserHandle.getUserId(info.applicationInfo.uid); lastTaskDescription = _taskDescription; - mCallingUid = info.applicationInfo.uid; - mCallingPackage = info.packageName; } private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, @@ -278,9 +296,9 @@ final class TaskRecord { /** Sets the original intent, and the calling uid and package. */ void setIntent(ActivityRecord r) { - setIntent(r.intent, r.info); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; + setIntent(r.intent, r.info); } /** Sets the original intent, _without_ updating the calling uid or package. */ @@ -362,6 +380,8 @@ final class TaskRecord { autoRemoveRecents = false; } mResizeable = info.resizeable; + mLockTaskMode = info.lockTaskLaunchMode; + setLockTaskAuth(); } void setTaskToReturnTo(int taskToReturnTo) { @@ -716,6 +736,53 @@ final class TaskRecord { performClearTaskAtIndexLocked(0); } + private boolean isPrivileged() { + final ProcessRecord proc = mService.mProcessNames.get(mCallingPackage, mCallingUid); + if (proc != null) { + return (proc.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + } + return false; + } + + void setLockTaskAuth() { + switch (mLockTaskMode) { + case LOCK_TASK_LAUNCH_MODE_DEFAULT: + mLockTaskAuth = isLockTaskWhitelistedLocked() ? + LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_NEVER: + mLockTaskAuth = isPrivileged() ? + LOCK_TASK_AUTH_DONT_LOCK : LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_ALWAYS: + mLockTaskAuth = isPrivileged() ? + LOCK_TASK_AUTH_LAUNCHABLE: LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED: + mLockTaskAuth = isLockTaskWhitelistedLocked() ? + LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE; + break; + } + } + + boolean isLockTaskWhitelistedLocked() { + if (mCallingPackage == null) { + return false; + } + String[] packages = mService.mLockTaskPackages.get(userId); + if (packages == null) { + return false; + } + for (int i = packages.length - 1; i >= 0; --i) { + if (mCallingPackage.equals(packages[i])) { + return true; + } + } + return false; + } boolean isHomeTask() { return taskType == HOME_ACTIVITY_TYPE; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index dac0580af479..8a7c9020d952 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -40,7 +40,10 @@ import java.util.ArrayList; */ public class NetworkAgentInfo { public NetworkInfo networkInfo; - public Network network; + // This Network object should always be used if possible, so as to encourage reuse of the + // enclosed socket factory and connection pool. Avoid creating other Network objects. + // This Network object is always valid. + public final Network network; public LinkProperties linkProperties; public NetworkCapabilities networkCapabilities; public final NetworkMonitor networkMonitor; @@ -86,12 +89,12 @@ public class NetworkAgentInfo { // Used by ConnectivityService to keep track of 464xlat. public Nat464Xlat clatd; - public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info, + public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc, NetworkRequest defaultRequest) { this.messenger = messenger; asyncChannel = ac; - network = null; + network = net; networkInfo = info; linkProperties = lp; networkCapabilities = nc; diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index d0e1665e19ae..7e202768cffc 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -216,7 +216,7 @@ public class NetworkMonitor extends StateMachine { // If a network is not validated, make one attempt every 10 mins to see if it starts working. private static final int REEVALUATE_PAUSE_MS = 10*60*1000; private static final int PERIODIC_ATTEMPTS = 1; - // When an application calls reportBadNetwork, only make one attempt. + // When an application calls reportNetworkConnectivity, only make one attempt. private static final int REEVALUATE_ATTEMPTS = 1; private final int mReevaluateDelayMs; private int mReevaluateToken = 0; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 0b430ea45624..3d478f9a2eec 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -17,8 +17,13 @@ package com.android.server.connectivity; import static android.Manifest.permission.BIND_VPN_SERVICE; +import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.os.UserHandle.PER_USER_RANGE; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + import android.Manifest; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -29,6 +34,7 @@ import android.content.Context; import android.content.Intent; 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; @@ -64,6 +70,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.security.Credentials; import android.security.KeyStore; +import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -110,7 +117,6 @@ public class Vpn { private LegacyVpnRunner mLegacyVpnRunner; private PendingIntent mStatusIntent; private volatile boolean mEnableTeardown = true; - private final IConnectivityManager mConnService; private final INetworkManagementService mNetd; private VpnConfig mConfig; private NetworkAgent mNetworkAgent; @@ -126,10 +132,9 @@ public class Vpn { private final int mUserHandle; public Vpn(Looper looper, Context context, INetworkManagementService netService, - IConnectivityManager connService, int userHandle) { + int userHandle) { mContext = context; mNetd = netService; - mConnService = connService; mUserHandle = userHandle; mLooper = looper; @@ -336,6 +341,10 @@ public class Vpn { return mNetworkInfo; } + public int getNetId() { + return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET; + } + private LinkProperties makeLinkProperties() { boolean allowIPv4 = mConfig.allowIPv4; boolean allowIPv6 = mConfig.allowIPv6; @@ -1106,11 +1115,15 @@ public class Vpn { // registering mOuterInterface = mConfig.interfaze; - try { - mOuterConnection.set( - mConnService.findConnectionTypeForIface(mOuterInterface)); - } catch (Exception e) { - mOuterConnection.set(ConnectivityManager.TYPE_NONE); + if (!TextUtils.isEmpty(mOuterInterface)) { + final ConnectivityManager cm = ConnectivityManager.from(mContext); + for (Network network : cm.getAllNetworks()) { + final LinkProperties lp = cm.getLinkProperties(network); + if (lp != null && mOuterInterface.equals(lp.getInterfaceName())) { + final NetworkInfo networkInfo = cm.getNetworkInfo(network); + if (networkInfo != null) mOuterConnection.set(networkInfo.getType()); + } + } } IntentFilter filter = new IntentFilter(); diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index e16be71d1e46..ee36972f7c82 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -47,6 +47,10 @@ abstract class DisplayDevice { // within a transaction from performTraversalInTransactionLocked. private Surface mCurrentSurface; + // DEBUG STATE: Last device info which was written to the log, or null if none. + // Do not use for any other purpose. + DisplayDeviceInfo mDebugLastLoggedDeviceInfo; + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) { mDisplayAdapter = displayAdapter; mDisplayToken = displayToken; diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index d1e73f075e80..ebf6e4e348ce 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -104,6 +104,16 @@ final class DisplayDeviceInfo { public static final int TOUCH_EXTERNAL = 2; /** + * Diff result: The {@link #state} fields differ. + */ + public static final int DIFF_STATE = 1 << 0; + + /** + * Diff result: Other fields differ. + */ + public static final int DIFF_OTHER = 1 << 1; + + /** * Gets the name of the display device, which may be derived from EDID or * other sources. The name may be localized and displayed to the user. */ @@ -238,26 +248,39 @@ final class DisplayDeviceInfo { } public boolean equals(DisplayDeviceInfo other) { - return other != null - && Objects.equal(name, other.name) - && Objects.equal(uniqueId, other.uniqueId) - && width == other.width - && height == other.height - && refreshRate == other.refreshRate - && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) - && densityDpi == other.densityDpi - && xDpi == other.xDpi - && yDpi == other.yDpi - && appVsyncOffsetNanos == other.appVsyncOffsetNanos - && presentationDeadlineNanos == other.presentationDeadlineNanos - && flags == other.flags - && touch == other.touch - && rotation == other.rotation - && type == other.type - && Objects.equal(address, other.address) - && state == other.state - && ownerUid == other.ownerUid - && Objects.equal(ownerPackageName, other.ownerPackageName); + return other != null && diff(other) == 0; + } + + /** + * Computes the difference between display device infos. + * Assumes other is not null. + */ + public int diff(DisplayDeviceInfo other) { + int diff = 0; + if (state != other.state) { + diff |= DIFF_STATE; + } + if (!Objects.equal(name, other.name) + || !Objects.equal(uniqueId, other.uniqueId) + || width != other.width + || height != other.height + || refreshRate != other.refreshRate + || !Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) + || densityDpi != other.densityDpi + || xDpi != other.xDpi + || yDpi != other.yDpi + || appVsyncOffsetNanos != other.appVsyncOffsetNanos + || presentationDeadlineNanos != other.presentationDeadlineNanos + || flags != other.flags + || touch != other.touch + || rotation != other.rotation + || type != other.type + || !Objects.equal(address, other.address) + || ownerUid != other.ownerUid + || !Objects.equal(ownerPackageName, other.ownerPackageName)) { + diff |= DIFF_OTHER; + } + return diff; } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 9ea0444818aa..1e87433360af 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -668,13 +668,14 @@ public final class DisplayManagerService extends SystemService { } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to add already added display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to add already added display device: " + info); return; } - Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); + Slog.i(TAG, "Display device added: " + info); + device.mDebugLastLoggedDeviceInfo = info; mDisplayDevices.add(device); addLogicalDisplayLocked(device); @@ -687,13 +688,20 @@ public final class DisplayManagerService extends SystemService { private void handleDisplayDeviceChanged(DisplayDevice device) { synchronized (mSyncRoot) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (!mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to change non-existent display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to change non-existent display device: " + info); return; } - Slog.i(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); + int diff = device.mDebugLastLoggedDeviceInfo.diff(info); + if (diff == DisplayDeviceInfo.DIFF_STATE) { + Slog.i(TAG, "Display device changed state: \"" + info.name + + "\", " + Display.stateToString(info.state)); + } else if (diff != 0) { + Slog.i(TAG, "Display device changed: " + info); + } + device.mDebugLastLoggedDeviceInfo = info; device.applyPendingDisplayDeviceInfoChangesLocked(); if (updateLogicalDisplaysLocked()) { @@ -708,13 +716,14 @@ public final class DisplayManagerService extends SystemService { } } private void handleDisplayDeviceRemovedLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (!mDisplayDevices.remove(device)) { - Slog.w(TAG, "Attempted to remove non-existent display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to remove non-existent display device: " + info); return; } - Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); + Slog.i(TAG, "Display device removed: " + info); + device.mDebugLastLoggedDeviceInfo = info; updateLogicalDisplaysLocked(); scheduleTraversalLocked(false); diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 3bb7818c339a..65dc72f4280a 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -302,7 +302,10 @@ final class LogicalDisplay { // multiplying the fractions by the product of their denominators before // comparing them. int displayRectWidth, displayRectHeight; - if (physWidth * displayInfo.logicalHeight + if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0) { + displayRectWidth = displayInfo.logicalWidth; + displayRectHeight = displayInfo.logicalHeight; + } else if (physWidth * displayInfo.logicalHeight < physHeight * displayInfo.logicalWidth) { // Letter box. displayRectWidth = physWidth; diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 90e69d7c9812..0ca0c0e5ad27 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -358,6 +358,11 @@ public class FingerprintService extends SystemService { return result; } + public boolean hasEnrolledFingerprints(int groupId) { + ContentResolver resolver = mContext.getContentResolver(); + return FingerprintUtils.getFingerprintIdsForUser(resolver, groupId).length > 0; + } + void checkPermission(String permission) { getContext().enforceCallingOrSelfPermission(permission, "Must have " + permission + " permission."); @@ -572,6 +577,13 @@ public class FingerprintService extends SystemService { checkPermission(USE_FINGERPRINT); return FingerprintService.this.getEnrolledFingerprints(groupId); } + + @Override + // Binder call + public boolean hasEnrolledFingerprints(int groupId) { + checkPermission(USE_FINGERPRINT); + return FingerprintService.this.hasEnrolledFingerprints(groupId); + } } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 4ac2b48e377a..94f8dee08a5c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -913,6 +913,12 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } + @ServiceThreadOnly + boolean isConnected(int portId) { + assertRunOnServiceThread(); + return mService.isConnected(portId); + } + private void notifyArcStatusToAudioService(boolean enabled) { // Note that we don't set any name to ARC. mService.getAudioManager().setWiredDeviceConnectionState( diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 49a96d81a6cc..2cbc1b9cdd77 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -780,6 +780,12 @@ public final class HdmiControlService extends SystemService { return false; } + @ServiceThreadOnly + boolean isConnected(int portId) { + assertRunOnServiceThread(); + return mCecController.isConnected(portId); + } + void runOnServiceThread(Runnable runnable) { mHandler.post(runnable); } diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index a944a277696b..5f2d65172180 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -156,10 +156,13 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { int index = -1; while ((index = removed.nextSetBit(index + 1)) != -1) { if (index == Constants.ADDR_AUDIO_SYSTEM) { - ++mAvrStatusCount; - Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); - if (mAvrStatusCount < AVR_COUNT_MAX) { - continue; + HdmiDeviceInfo avr = tv().getAvrDeviceInfo(); + if (avr != null && tv().isConnected(avr.getPortId())) { + ++mAvrStatusCount; + Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); + if (mAvrStatusCount < AVR_COUNT_MAX) { + continue; + } } } Slog.v(TAG, "Remove device by hot-plug detection:" + index); diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index d200d359bffb..9b4950b5b1ba 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -54,7 +54,7 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { boolean start() { // Seq #37. if (mEnabled) { - // Enable ARC status immediately after sending <Report Arc Initiated>. + // Enable ARC status immediately before sending <Report Arc Initiated>. // If AVR responds with <Feature Abort>, disable ARC status again. // This is different from spec that says that turns ARC status to // "Enabled" if <Report ARC Initiated> is acknowledged and no @@ -80,12 +80,21 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error != Constants.SEND_RESULT_SUCCESS) { - // If fails to send <Report ARC Initiated>, disable ARC and - // send <Report ARC Terminated> directly. - setArcStatus(false); - HdmiLogger.debug("Failed to send <Report Arc Initiated>."); - finish(); + switch (error) { + case Constants.SEND_RESULT_SUCCESS: + case Constants.SEND_RESULT_BUSY: + case Constants.SEND_RESULT_FAILURE: + // The result of the command transmission, unless it is an obvious + // failure indicated by the target device (or lack thereof), should + // not affect the ARC status. Ignores it silently. + break; + case Constants.SEND_RESULT_NAK: + // If <Report ARC Initiated> is negatively ack'ed, disable ARC and + // send <Report ARC Terminated> directly. + setArcStatus(false); + HdmiLogger.debug("Failed to send <Report Arc Initiated>."); + finish(); + break; } } }); diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index 23d5c05e2ef0..98fb11b6aef2 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -107,7 +107,16 @@ public class AppIdleController extends StateController @Override public void dumpControllerState(PrintWriter pw) { - // TODO: + pw.println("AppIdle"); + pw.println("Plugged In: " + mPluggedIn); + synchronized (mTrackedTasks) { + for (JobStatus task : mTrackedTasks) { + pw.print(task.job.getService().getPackageName()); + pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get()); + pw.print(", "); + } + pw.println(); + } } @Override diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 7c41abbc3340..a279e0f4282f 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -197,6 +197,8 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final int REMOVE_LISTENER = 9; private static final int INJECT_NTP_TIME_FINISHED = 10; private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11; + private static final int SUBSCRIPTION_OR_SIM_CHANGED = 12; + private static final int INITIALIZE_HANDLER = 13; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -338,8 +340,12 @@ public class GpsLocationProvider implements LocationProviderInterface { // True if gps should be disabled (used to support battery saver mode in settings). private boolean mDisableGps = false; - // properties loaded from PROPERTIES_FILE + /** + * Properties loaded from PROPERTIES_FILE. + * It must be accessed only inside {@link #mHandler}. + */ private Properties mProperties; + private String mSuplServerHost; private int mSuplServerPort = TCP_MIN_PORT; private String mC2KServerHost; @@ -462,7 +468,7 @@ public class GpsLocationProvider implements LocationProviderInterface { new OnSubscriptionsChangedListener() { @Override public void onSubscriptionsChanged() { - subscriptionOrSimChanged(mContext); + sendMessage(SUBSCRIPTION_OR_SIM_CHANGED, 0, null); } }; @@ -627,53 +633,22 @@ public class GpsLocationProvider implements LocationProviderInterface { mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); - // Load GPS configuration. + // Construct internal handler + mHandler = new ProviderHandler(looper); + + // Load GPS configuration and register listeners in the background: + // some operations, such as opening files and registering broadcast receivers, can take a + // relative long time, so the ctor() is kept to create objects needed by this instance, + // while IO initialization and registration is delegated to our internal handler + // this approach is just fine because events are posted to our handler anyway mProperties = new Properties(); - reloadGpsProperties(mContext, mProperties); + sendMessage(INITIALIZE_HANDLER, 0, null); // Create a GPS net-initiated handler. mNIHandler = new GpsNetInitiatedHandler(context, mNetInitiatedListener, mSuplEsEnabled); - // TODO: When this object "finishes" we should unregister by invoking - // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener); - // This is not strictly necessary because it will be unregistered if the - // notification fails but it is good form. - - // Register for SubscriptionInfo list changes which is guaranteed - // to invoke onSubscriptionsChanged the first time. - SubscriptionManager.from(mContext) - .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); - - // construct handler, listen for events - mHandler = new ProviderHandler(looper); - listenForBroadcasts(); - - // also listen for PASSIVE_PROVIDER updates - mHandler.post(new Runnable() { - @Override - public void run() { - LocationManager locManager = - (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - final long minTime = 0; - final float minDistance = 0; - final boolean oneShot = false; - LocationRequest request = LocationRequest.createFromDeprecatedProvider( - LocationManager.PASSIVE_PROVIDER, - minTime, - minDistance, - oneShot); - // Don't keep track of this request since it's done on behalf of other clients - // (which are kept track of separately). - request.setHideFromAppOps(true); - locManager.requestLocationUpdates( - request, - new NetworkLocationListener(), - mHandler.getLooper()); - } - }); - mListenerHelper = new GpsStatusListenerHelper(mHandler) { @Override protected boolean isAvailableInPlatform() { @@ -731,33 +706,6 @@ public class GpsLocationProvider implements LocationProviderInterface { }; } - private void listenForBroadcasts() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); - intentFilter.addDataScheme("sms"); - intentFilter.addDataAuthority("localhost","7275"); - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - - intentFilter = new IntentFilter(); - intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); - try { - intentFilter.addDataType("application/vnd.omaloc-supl-init"); - } catch (IntentFilter.MalformedMimeTypeException e) { - Log.w(TAG, "Malformed SUPL init mime type"); - } - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - - intentFilter = new IntentFilter(); - intentFilter.addAction(ALARM_WAKEUP); - intentFilter.addAction(ALARM_TIMEOUT); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); - intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(SIM_STATE_CHANGED); - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - } - /** * Returns the name of this provider. */ @@ -2012,13 +1960,85 @@ public class GpsLocationProvider implements LocationProviderInterface { case UPDATE_LOCATION: handleUpdateLocation((Location)msg.obj); break; + case SUBSCRIPTION_OR_SIM_CHANGED: + subscriptionOrSimChanged(mContext); + break; + case INITIALIZE_HANDLER: + initialize(); + break; } if (msg.arg2 == 1) { // wakelock was taken for this message, release it mWakeLock.release(); } } - }; + + /** + * This method is bound to {@link #GpsLocationProvider(Context, ILocationManager, Looper)}. + * It is in charge of loading properties and registering for events that will be posted to + * this handler. + */ + private void initialize() { + // load default GPS configuration + // (this configuration might change in the future based on SIM changes) + reloadGpsProperties(mContext, mProperties); + + // TODO: When this object "finishes" we should unregister by invoking + // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener); + // This is not strictly necessary because it will be unregistered if the + // notification fails but it is good form. + + // Register for SubscriptionInfo list changes which is guaranteed + // to invoke onSubscriptionsChanged the first time. + SubscriptionManager.from(mContext) + .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); + + // listen for events + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); + intentFilter.addDataScheme("sms"); + intentFilter.addDataAuthority("localhost","7275"); + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + + intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); + try { + intentFilter.addDataType("application/vnd.omaloc-supl-init"); + } catch (IntentFilter.MalformedMimeTypeException e) { + Log.w(TAG, "Malformed SUPL init mime type"); + } + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + + intentFilter = new IntentFilter(); + intentFilter.addAction(ALARM_WAKEUP); + intentFilter.addAction(ALARM_TIMEOUT); + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); + intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(SIM_STATE_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + + // listen for PASSIVE_PROVIDER updates + LocationManager locManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + long minTime = 0; + float minDistance = 0; + boolean oneShot = false; + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + LocationManager.PASSIVE_PROVIDER, + minTime, + minDistance, + oneShot); + // Don't keep track of this request since it's done on behalf of other clients + // (which are kept track of separately). + request.setHideFromAppOps(true); + locManager.requestLocationUpdates( + request, + new NetworkLocationListener(), + getLooper()); + } + } private final class NetworkLocationListener implements LocationListener { @Override diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4cf2909269b6..997d5468da96 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -29,9 +29,11 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.INotificationManager; +import android.app.INotificationManagerCallback; import android.app.ITransientNotification; import android.app.Notification; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; @@ -227,6 +229,8 @@ public class NotificationManagerService extends SystemService { new ArrayMap<String, NotificationRecord>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); + private final ArrayMap<String, Policy.Token> mPolicyTokens = new ArrayMap<>(); + // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); @@ -893,6 +897,13 @@ public class NotificationManagerService extends SystemService { updateInterruptionFilterLocked(); } } + + @Override + void onPolicyChanged() { + getContext().sendBroadcast( + new Intent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); + } }); final File systemDir = new File(Environment.getDataDirectory(), "system"); mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml")); @@ -1551,6 +1562,18 @@ public class NotificationManagerService extends SystemService { message); } + private void enforcePolicyToken(Policy.Token token, String method) { + if (!checkPolicyToken(token)) { + Slog.w(TAG, "Invalid notification policy token calling " + method); + throw new SecurityException("Invalid notification policy token"); + } + } + + private boolean checkPolicyToken(Policy.Token token) { + return mPolicyTokens.containsValue(token) + || mListeners.mPolicyTokens.containsValue(token); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -1586,24 +1609,73 @@ public class NotificationManagerService extends SystemService { enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled"); return mConditionProviders.isSystemProviderEnabled(path); } - }; - private String[] getActiveNotificationKeys(INotificationListener token) { - final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); - final ArrayList<String> keys = new ArrayList<String>(); - if (info.isEnabledForCurrentProfiles()) { - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i = 0; i < N; i++) { - final StatusBarNotification sbn = mNotificationList.get(i).sbn; - if (info.enabledAndUserMatches(sbn.getUserId())) { - keys.add(sbn.getKey()); + @Override + public Policy.Token getPolicyTokenFromListener(INotificationListener listener) { + final long identity = Binder.clearCallingIdentity(); + try { + return mListeners.getPolicyToken(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void requestNotificationPolicyToken(String pkg, + INotificationManagerCallback callback) throws RemoteException { + if (callback == null) { + Slog.w(TAG, "requestNotificationPolicyToken: no callback specified"); + return; + } + if (pkg == null) { + Slog.w(TAG, "requestNotificationPolicyToken denied: no package specified"); + callback.onPolicyToken(null); + return; + } + Policy.Token token = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationList) { + token = mPolicyTokens.get(pkg); + if (token == null) { + token = new Policy.Token(new Binder()); + mPolicyTokens.put(pkg, token); } + if (DBG) Slog.w(TAG, "requestNotificationPolicyToken granted for " + pkg); } + } finally { + Binder.restoreCallingIdentity(identity); } + callback.onPolicyToken(token); + } + + @Override + public boolean isNotificationPolicyTokenValid(String pkg, Policy.Token token) { + return checkPolicyToken(token); } - return keys.toArray(new String[keys.size()]); - } + + @Override + public Policy getNotificationPolicy(Policy.Token token) { + enforcePolicyToken(token, "getNotificationPolicy"); + final long identity = Binder.clearCallingIdentity(); + try { + return mZenModeHelper.getNotificationPolicy(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void setNotificationPolicy(Policy.Token token, Policy policy) { + enforcePolicyToken(token, "setNotificationPolicy"); + final long identity = Binder.clearCallingIdentity(); + try { + mZenModeHelper.setNotificationPolicy(policy); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }; private String disableNotificationEffects(NotificationRecord record) { if (mDisableNotificationEffects) { @@ -1718,6 +1790,10 @@ public class NotificationManagerService extends SystemService { pw.print(listener.component); } pw.println(')'); + pw.print(" mPolicyTokens.keys: "); + pw.println(TextUtils.join(",", mPolicyTokens.keySet())); + pw.print(" mListeners.mPolicyTokens.keys: "); + pw.println(TextUtils.join(",", mListeners.mPolicyTokens.keySet())); } pw.println("\n Condition providers:"); @@ -2970,12 +3046,18 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>(); + private final ArrayMap<ComponentName, Policy.Token> mPolicyTokens = new ArrayMap<>(); private boolean mNotificationGroupsDesired; public NotificationListeners() { super(getContext(), mHandler, mNotificationList, mUserProfiles); } + public Policy.Token getPolicyToken(INotificationListener listener) { + final ManagedServiceInfo info = checkServiceTokenLocked(listener); + return info == null ? null : mPolicyTokens.get(info.component); + } + @Override protected Config getConfig() { Config c = new Config(); @@ -3000,6 +3082,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationList) { updateNotificationGroupsDesiredLocked(); update = makeRankingUpdateLocked(info); + mPolicyTokens.put(info.component, new Policy.Token(new Binder())); } try { listener.onListenerConnected(update); @@ -3016,6 +3099,7 @@ public class NotificationManagerService extends SystemService { } mLightTrimListeners.remove(removed); updateNotificationGroupsDesiredLocked(); + mPolicyTokens.remove(removed.component); } public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 40218bb43e77..9cb8af508744 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,6 +21,7 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import android.app.AppOpsManager; +import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -57,6 +58,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Objects; /** * NotificationManagerService helper for functionality related to zen mode. @@ -230,6 +232,21 @@ public class ZenModeHelper { mConfig.writeXml(out); } + public Policy getNotificationPolicy() { + return getNotificationPolicy(mConfig); + } + + private static Policy getNotificationPolicy(ZenModeConfig config) { + return config == null ? null : config.toNotificationPolicy(); + } + + public void setNotificationPolicy(Policy policy) { + if (policy == null || mConfig == null) return; + final ZenModeConfig newConfig = mConfig.copy(); + newConfig.applyNotificationPolicy(policy); + setConfig(newConfig, "setNotificationPolicy"); + } + public ZenModeConfig getConfig() { return mConfig; } @@ -247,8 +264,13 @@ public class ZenModeHelper { if (config.equals(mConfig)) return true; if (DEBUG) Log.d(TAG, "setConfig reason=" + reason); ZenLog.traceConfig(mConfig, config); + final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig), + getNotificationPolicy(config)); mConfig = config; dispatchOnConfigChanged(); + if (policyChanged){ + dispatchOnPolicyChanged(); + } final String val = Integer.toString(mConfig.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); if (!evaluateZenMode(reason, setRingerMode)) { @@ -355,6 +377,12 @@ public class ZenModeHelper { } } + private void dispatchOnPolicyChanged() { + for (Callback callback : mCallbacks) { + callback.onPolicyChanged(); + } + } + private void dispatchOnZenModeChanged() { for (Callback callback : mCallbacks) { callback.onZenModeChanged(); @@ -617,6 +645,7 @@ public class ZenModeHelper { public static class Callback { void onConfigChanged() {} void onZenModeChanged() {} + void onPolicyChanged() {} } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a260b0ed3667..c12545b36d82 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6280,7 +6280,8 @@ public class PackageManagerService extends IPackageManager.Stub { !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; for (int userId : userIds) { - if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { + if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName, + nativeLibPath, userId) < 0) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Failed linking native library dir (user=" + userId + ")"); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 936840ae5a1b..fce01e526389 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -183,6 +183,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; static final int APPLICATION_PANEL_SUBLAYER = 1; static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; + static final int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3; static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; @@ -2015,6 +2016,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return APPLICATION_MEDIA_OVERLAY_SUBLAYER; case TYPE_APPLICATION_SUB_PANEL: return APPLICATION_SUB_PANEL_SUBLAYER; + case TYPE_APPLICATION_ABOVE_SUB_PANEL: + return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER; } Log.e(TAG, "Unknown sub-window type: " + type); return 0; diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 75c33af2a1e9..1a52933056fa 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -145,6 +145,9 @@ public class KeyguardServiceDelegate { if (mKeyguardState.bootCompleted) { mKeyguardService.onBootCompleted(); } + if (mKeyguardState.occluded) { + mKeyguardService.setOccluded(mKeyguardState.occluded); + } } @Override diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index dec195d8172c..fb7d1864381b 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -116,7 +116,7 @@ public class TrustAgentWrapper { } mTrusted = true; mMessage = (CharSequence) msg.obj; - boolean initiatedByUser = msg.arg1 != 0; + int flags = msg.arg1; long durationMs = msg.getData().getLong(DATA_DURATION); if (durationMs > 0) { final long duration; @@ -141,8 +141,8 @@ public class TrustAgentWrapper { } mTrustManagerService.mArchive.logGrantTrust(mUserId, mName, (mMessage != null ? mMessage.toString() : null), - durationMs, initiatedByUser); - mTrustManagerService.updateTrust(mUserId, initiatedByUser); + durationMs, flags); + mTrustManagerService.updateTrust(mUserId, flags); break; case MSG_TRUST_TIMEOUT: if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString()); @@ -156,7 +156,7 @@ public class TrustAgentWrapper { if (msg.what == MSG_REVOKE_TRUST) { mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName); } - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); break; case MSG_RESTART_TIMEOUT: destroy(); @@ -171,7 +171,7 @@ public class TrustAgentWrapper { if (DEBUG) Log.v(TAG, "Re-enabling agent because it acknowledged " + "enabled features: " + mName); mTrustDisabledByDpm = false; - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); } } else { if (DEBUG) Log.w(TAG, "Ignoring MSG_SET_TRUST_AGENT_FEATURES_COMPLETED " @@ -185,7 +185,7 @@ public class TrustAgentWrapper { mMessage = null; } mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust); - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); break; } } @@ -194,12 +194,12 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence userMessage, long durationMs, boolean initiatedByUser) { + public void grantTrust(CharSequence userMessage, long durationMs, int flags) { if (DEBUG) Slog.v(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs - + ", initiatedByUser = " + initiatedByUser + ")"); + + ", flags = " + flags + ")"); Message msg = mHandler.obtainMessage( - MSG_GRANT_TRUST, initiatedByUser ? 1 : 0, 0, userMessage); + MSG_GRANT_TRUST, flags, 0, userMessage); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -381,7 +381,7 @@ public class TrustAgentWrapper { } if (mTrustDisabledByDpm != trustDisabled) { mTrustDisabledByDpm = trustDisabled; - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); } return trustDisabled; } diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java index 725371666acf..fd63d486d049 100644 --- a/services/core/java/com/android/server/trust/TrustArchive.java +++ b/services/core/java/com/android/server/trust/TrustArchive.java @@ -19,6 +19,7 @@ package com.android.server.trust; import android.content.ComponentName; import android.os.SystemClock; import android.os.UserHandle; +import android.service.trust.TrustAgentService; import android.util.TimeUtils; import java.io.PrintWriter; @@ -48,20 +49,20 @@ public class TrustArchive { // grantTrust final String message; final long duration; - final boolean userInitiated; + final int flags; // managingTrust final boolean managingTrust; private Event(int type, int userId, ComponentName agent, String message, - long duration, boolean userInitiated, boolean managingTrust) { + long duration, int flags, boolean managingTrust) { this.type = type; this.userId = userId; this.agent = agent; this.elapsedTimestamp = SystemClock.elapsedRealtime(); this.message = message; this.duration = duration; - this.userInitiated = userInitiated; + this.flags = flags; this.managingTrust = managingTrust; } } @@ -69,33 +70,33 @@ public class TrustArchive { ArrayDeque<Event> mEvents = new ArrayDeque<Event>(); public void logGrantTrust(int userId, ComponentName agent, String message, - long duration, boolean userInitiated) { + long duration, int flags) { addEvent(new Event(TYPE_GRANT_TRUST, userId, agent, message, duration, - userInitiated, false)); + flags, false)); } public void logRevokeTrust(int userId, ComponentName agent) { - addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, 0, false)); } public void logTrustTimeout(int userId, ComponentName agent) { - addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, 0, false)); } public void logAgentDied(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, 0, false)); } public void logAgentConnected(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, 0, false)); } public void logAgentStopped(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, 0, false)); } public void logManagingTrust(int userId, ComponentName agent, boolean managing) { - addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, false, managing)); + addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, 0, managing)); } private void addEvent(Event e) { @@ -129,8 +130,8 @@ public class TrustArchive { } switch (ev.type) { case TYPE_GRANT_TRUST: - writer.printf(", message=\"%s\", duration=%s, initiatedByUser=%d", - ev.message, formatDuration(ev.duration), ev.userInitiated ? 1 : 0); + writer.printf(", message=\"%s\", duration=%s, flags=%s", + ev.message, formatDuration(ev.duration), dumpGrantFlags(ev.flags)); break; case TYPE_MANAGING_TRUST: writer.printf(", managingTrust=" + ev.managingTrust); @@ -184,4 +185,20 @@ public class TrustArchive { return "Unknown(" + type + ")"; } } + + private String dumpGrantFlags(int flags) { + StringBuilder sb = new StringBuilder(); + if ((flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0) { + if (sb.length() != 0) sb.append('|'); + sb.append("INITIATED_BY_USER"); + } + if ((flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0) { + if (sb.length() != 0) sb.append('|'); + sb.append("DISMISS_KEYGUARD"); + } + if (sb.length() == 0) { + sb.append('0'); + } + return sb.toString(); + } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index b38d33d14b8b..7d2fb433a0da 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -179,11 +179,11 @@ public class TrustManagerService extends SystemService { private void updateTrustAll() { List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */); for (UserInfo userInfo : userInfos) { - updateTrust(userInfo.id, false); + updateTrust(userInfo.id, 0); } } - public void updateTrust(int userId, boolean initiatedByUser) { + public void updateTrust(int userId, int flags) { dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId); boolean trusted = aggregateIsTrusted(userId); boolean changed; @@ -191,7 +191,7 @@ public class TrustManagerService extends SystemService { changed = mUserIsTrusted.get(userId) != trusted; mUserIsTrusted.put(userId, trusted); } - dispatchOnTrustChanged(trusted, userId, initiatedByUser); + dispatchOnTrustChanged(trusted, userId, flags); if (changed) { refreshDeviceLockedForUser(userId); } @@ -281,7 +281,7 @@ public class TrustManagerService extends SystemService { if (userId == UserHandle.USER_ALL) { updateTrustAll(); } else { - updateTrust(userId, false /* initiatedByUser */); + updateTrust(userId, 0); } } } @@ -394,7 +394,7 @@ public class TrustManagerService extends SystemService { } } if (trustMayHaveChanged) { - updateTrust(userId, false); + updateTrust(userId, 0); } refreshAgentList(userId); } @@ -587,11 +587,11 @@ public class TrustManagerService extends SystemService { } } - private void dispatchOnTrustChanged(boolean enabled, int userId, boolean initiatedByUser) { - if (!enabled) initiatedByUser = false; + private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) { + if (!enabled) flags = 0; for (int i = 0; i < mTrustListeners.size(); i++) { try { - mTrustListeners.get(i).onTrustChanged(enabled, userId, initiatedByUser); + mTrustListeners.get(i).onTrustChanged(enabled, userId, flags); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index d4c5f8716c0e..ac79b36f7005 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -40,6 +40,8 @@ public class WebViewUpdateService extends SystemService { private boolean mRelroReady32Bit = false; private boolean mRelroReady64Bit = false; + private String oldWebViewPackageName = null; + private BroadcastReceiver mWebViewUpdatedReceiver; public WebViewUpdateService(Context context) { @@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService { mWebViewUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName(); - if (webviewPackage.equals(intent.getDataString())) { - onWebViewUpdateInstalled(); + + for (String packageName : WebViewFactory.getWebViewPackageNames()) { + String webviewPackage = "package:" + packageName; + + if (webviewPackage.equals(intent.getDataString())) { + String usedPackageName = + WebViewFactory.findPreferredWebViewPackage().packageName; + // Only trigger update actions if the updated package is the one that + // will be used, or the one that was in use before the update. + if (packageName.equals(usedPackageName) || + packageName.equals(oldWebViewPackageName)) { + onWebViewUpdateInstalled(); + oldWebViewPackageName = usedPackageName; + } + return; + } } } }; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index ae8832ab78ea..91ce7398e2d7 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -339,6 +339,7 @@ final class AccessibilityController { case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: case WindowManager.LayoutParams.TYPE_SEARCH_BAR: case WindowManager.LayoutParams.TYPE_PHONE: diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index a04f6cbf7fac..e914cd4e7e36 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -56,7 +56,7 @@ class AppWindowToken extends WindowToken { boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean layoutConfigChanges; - boolean showWhenLocked; + boolean showForAllUsers; // The input dispatching timeout for this application token in nanoseconds. long inputDispatchingTimeoutNanos; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f073c23878a6..f914369df6ef 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -66,6 +66,7 @@ class DisplayContent { int mBaseDisplayWidth = 0; int mBaseDisplayHeight = 0; int mBaseDisplayDensity = 0; + boolean mDisplayScalingDisabled; private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; @@ -360,6 +361,9 @@ class DisplayContent { pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi"); } + if (mDisplayScalingDisabled) { + pw.println(" noscale"); + } pw.print(" cur="); pw.print(mDisplayInfo.logicalWidth); pw.print("x"); pw.print(mDisplayInfo.logicalHeight); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 34120a054d9b..0c3cf65705f2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -107,9 +107,9 @@ class Task { } } - boolean showWhenLocked() { + boolean showForAllUsers() { final int tokensCount = mAppTokens.size(); - return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showWhenLocked; + return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; } @Override diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index e845f833eab2..7cdf8b21c955 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -276,27 +276,26 @@ public class TaskStack { } void addTask(Task task, boolean toTop) { - addTask(task, toTop, task.showWhenLocked()); + addTask(task, toTop, task.showForAllUsers()); } /** * Put a Task in this stack. Used for adding and moving. * @param task The task to add. * @param toTop Whether to add it to the top or bottom. - * @param showWhenLocked Whether to show the task when the device is locked - * regardless of the current user. + * @param showForAllUsers Whether to show the task regardless of the current user. */ - void addTask(Task task, boolean toTop, boolean showWhenLocked) { + void addTask(Task task, boolean toTop, boolean showForAllUsers) { int stackNdx; if (!toTop) { stackNdx = 0; } else { stackNdx = mTasks.size(); - if (!showWhenLocked && !mService.isCurrentProfileLocked(task.mUserId)) { + if (!showForAllUsers && !mService.isCurrentProfileLocked(task.mUserId)) { // Place the task below all current user tasks. while (--stackNdx >= 0) { final Task tmpTask = mTasks.get(stackNdx); - if (!tmpTask.showWhenLocked() + if (!tmpTask.showForAllUsers() || !mService.isCurrentProfileLocked(tmpTask.mUserId)) { break; } @@ -498,7 +497,7 @@ public class TaskStack { int top = mTasks.size(); for (int taskNdx = 0; taskNdx < top; ++taskNdx) { Task task = mTasks.get(taskNdx); - if (mService.isCurrentProfileLocked(task.mUserId) || task.showWhenLocked()) { + if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) { mTasks.remove(taskNdx); mTasks.add(task); --top; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e790fb02502e..964dcc2155e5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3620,13 +3620,13 @@ public class WindowManagerService extends IWindowManager.Stub EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId); Task task = new Task(taskId, stack, userId, this); mTaskIdToTask.put(taskId, task); - stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showWhenLocked); + stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers); return task; } @Override public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId, int configChanges, boolean voiceInteraction, boolean launchTaskBehind) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { @@ -3656,7 +3656,7 @@ public class WindowManagerService extends IWindowManager.Stub atoken = new AppWindowToken(this, token, voiceInteraction); atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; atoken.appFullscreen = fullscreen; - atoken.showWhenLocked = showWhenLocked; + atoken.showForAllUsers = showForAllUsers; atoken.requestedOrientation = requestedOrientation; atoken.layoutConfigChanges = (configChanges & (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0; @@ -7249,8 +7249,15 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); displayInfo.getAppMetrics(mDisplayMetrics); + if (displayContent.mDisplayScalingDisabled) { + displayInfo.flags |= Display.FLAG_SCALING_DISABLED; + } else { + displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; + } + mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( displayContent.getDisplayId(), displayInfo); + displayContent.mBaseDisplayRect.set(0, 0, dw, dh); } if (false) { @@ -7547,7 +7554,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = getDefaultDisplayContentLocked(); - readForcedDisplaySizeAndDensityLocked(displayContent); + readForcedDisplayPropertiesLocked(displayContent); mDisplayReady = true; } @@ -8320,7 +8327,47 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) { + @Override + public void setForcedDisplayScalingMode(int displayId, int mode) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) != + PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold permission " + + android.Manifest.permission.WRITE_SECURE_SETTINGS); + } + if (displayId != Display.DEFAULT_DISPLAY) { + throw new IllegalArgumentException("Can only set the default display"); + } + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + if (mode < 0 || mode > 1) { + mode = 0; + } + setForcedDisplayScalingModeLocked(displayContent, mode); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DISPLAY_SCALING_FORCE, mode); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, + int mode) { + Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off")); + + synchronized(displayContent.mDisplaySizeLock) { + displayContent.mDisplayScalingDisabled = (mode != 0); + } + reconfigureDisplayLocked(displayContent); + } + + private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) { + // Display size. String sizeStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_SIZE_FORCED); if (sizeStr == null || sizeStr.length() == 0) { @@ -8345,6 +8392,8 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + // Display density. String densityStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_DENSITY_FORCED); if (densityStr == null || densityStr.length() == 0) { @@ -8363,6 +8412,16 @@ public class WindowManagerService extends IWindowManager.Stub } catch (NumberFormatException ex) { } } + + // Display scaling mode. + int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DISPLAY_SCALING_FORCE, 0); + if (mode != 0) { + synchronized(displayContent.mDisplaySizeLock) { + Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED"); + displayContent.mDisplayScalingDisabled = true; + } + } } // displayContent must not be null diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index db3268d14fe0..4f795a65a9db 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; @@ -478,6 +479,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (mAppToken != null) { final DisplayContent appDisplay = getDisplayContent(); mNotOnAppsDisplay = displayContent != appDisplay; + + if (mAppToken.showForAllUsers) { + // Windows for apps that can show for all users should also show when the + // device is locked. + mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED; + } } mWinAnimator = new WindowStateAnimator(this); @@ -1353,7 +1360,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { win = win.mAttachedWindow; } if (win.mAttrs.type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW - && win.mAppToken != null && win.mAppToken.showWhenLocked) { + && win.mAppToken != null && win.mAppToken.showForAllUsers) { // Save some cycles by not calling getDisplayInfo unless it is an application // window intended for all users. final DisplayContent displayContent = win.getDisplayContent(); diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp index 17f86ca9271f..5a869238263f 100644 --- a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp +++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp @@ -26,6 +26,7 @@ #include <utils/String16.h> #include <utils/Looper.h> #include <keystore/IKeystoreService.h> +#include <keystore/keystore.h> // for error code #include <hardware/hardware.h> #include <hardware/fingerprint.h> @@ -74,8 +75,9 @@ static void notifyKeystore(uint8_t *auth_token, size_t auth_token_length) { sp<IBinder> binder = sm->getService(String16("android.security.keystore")); sp<IKeystoreService> service = interface_cast<IKeystoreService>(binder); if (service != NULL) { - if (service->addAuthToken(auth_token, auth_token_length) != NO_ERROR) { - ALOGE("Falure sending auth token to KeyStore"); + status_t ret = service->addAuthToken(auth_token, auth_token_length); + if (ret != ResponseCode::NO_ERROR) { + ALOGE("Falure sending auth token to KeyStore: %d", ret); } } else { ALOGE("Unable to communicate with KeyStore"); @@ -136,14 +138,15 @@ static jint nativeEnroll(JNIEnv* env, jobject clazz, jbyteArray token, jint grou ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll() : invalid token size %d\n", tokenSize); return -1; } - int ret = gContext.device->enroll(gContext.device, (hw_auth_token_t*) tokenData, groupId, timeout); + int ret = gContext.device->enroll(gContext.device, + reinterpret_cast<const hw_auth_token_t*>(tokenData), groupId, timeout); env->ReleaseByteArrayElements(token, tokenData, 0); return reinterpret_cast<jint>(ret); } -static jint nativePreEnroll(JNIEnv* env, jobject clazz) { +static jlong nativePreEnroll(JNIEnv* env, jobject clazz) { uint64_t ret = gContext.device->pre_enroll(gContext.device); - ALOG(LOG_VERBOSE, LOG_TAG, "nativePreEnroll(), result = %" PRId64 "\n", ret); + // ALOG(LOG_VERBOSE, LOG_TAG, "nativePreEnroll(), result = %llx", ret); return reinterpret_cast<jlong>((int64_t)ret); } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 7c5980a74a8a..f3edbd1db281 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -752,7 +752,7 @@ void NativeInputManager::setInteractive(bool interactive) { void NativeInputManager::reloadCalibration() { mInputManager->getReader()->requestRefreshConfiguration( - InputReaderConfiguration::TOUCH_AFFINE_TRANSFORMATION); + InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION); } TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 452b3eb94c95..e22a2cc317c2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1638,10 +1638,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void updateLockTaskPackagesLocked(DevicePolicyData policy, int userId) { IActivityManager am = ActivityManagerNative.getDefault(); + long ident = Binder.clearCallingIdentity(); try { am.updateLockTaskPackages(userId, policy.mLockTaskPackages.toArray(new String[0])); } catch (RemoteException e) { // Not gonna happen. + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8a28d5154b35..7dce83e08b60 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -393,12 +393,9 @@ public class VoiceInteractionManagerService extends SystemService { throw new SecurityException( "Caller is not the current voice interaction service"); } - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.showSessionLocked(callingPid, callingUid, args, flags, - null /* showCallback */); + mImpl.showSessionLocked(args, flags, null /* showCallback */); } finally { Binder.restoreCallingIdentity(caller); } @@ -432,12 +429,9 @@ public class VoiceInteractionManagerService extends SystemService { Slog.w(TAG, "showSessionFromSession without running voice interaction service"); return false; } - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - return mImpl.showSessionLocked(callingPid, callingUid, sessionArgs, flags, - null /* showCallback */); + return mImpl.showSessionLocked(sessionArgs, flags, null /* showCallback */); } finally { Binder.restoreCallingIdentity(caller); } @@ -506,11 +500,9 @@ public class VoiceInteractionManagerService extends SystemService { Slog.w(TAG, "finish without running voice interaction service"); return; } - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.finishLocked(callingPid, callingUid, token); + mImpl.finishLocked(token); } finally { Binder.restoreCallingIdentity(caller); } @@ -708,11 +700,9 @@ public class VoiceInteractionManagerService extends SystemService { + "service"); return; } - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.showSessionLocked(callingPid, callingUid, new Bundle() /* sessionArgs */, + mImpl.showSessionLocked(new Bundle() /* sessionArgs */, VoiceInteractionService.START_SOURCE_ASSIST_GESTURE | VoiceInteractionService.START_WITH_ASSIST | VoiceInteractionService.START_WITH_SCREENSHOT, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index bca757bc6945..61ec162a375f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -135,11 +135,11 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); } - public boolean showSessionLocked(int callingPid, int callingUid, Bundle args, int flags, + public boolean showSessionLocked(Bundle args, int flags, IVoiceInteractionSessionShowCallback showCallback) { if (mActiveSession == null) { mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName, - mUser, mContext, this, callingPid, callingUid); + mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid); } return mActiveSession.showLocked(args, flags, showCallback); } @@ -196,7 +196,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - public void finishLocked(int callingPid, int callingUid, IBinder token) { + public void finishLocked(IBinder token) { if (mActiveSession == null || token != mActiveSession.mToken) { Slog.w(TAG, "finish does not match active session"); return; @@ -267,7 +267,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne @Override public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { synchronized (mLock) { - finishLocked(connection.mCallingPid, connection.mCallingUid, connection.mToken); + finishLocked(connection.mToken); } } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index fb8395673fd3..9634ab886e66 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -59,7 +59,6 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { final int mUser; final Context mContext; final Callback mCallback; - final int mCallingPid; final int mCallingUid; final IActivityManager mAm; final IWindowManager mIWindowManager; @@ -139,13 +138,12 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { }; public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user, - Context context, Callback callback, int callingPid, int callingUid) { + Context context, Callback callback, int callingUid) { mLock = lock; mSessionComponentName = component; mUser = user; mContext = context; mCallback = callback; - mCallingPid = callingPid; mCallingUid = callingUid; mAm = ActivityManagerNative.getDefault(); mIWindowManager = IWindowManager.Stub.asInterface( diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java index 0720df37f60f..465c5f41a297 100644 --- a/telecomm/java/android/telecom/AudioState.java +++ b/telecomm/java/android/telecom/AudioState.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,9 +24,7 @@ import java.util.Locale; /** * Encapsulates the telecom audio state, including the current audio routing, supported audio * routing and mute. - * @hide */ -@SystemApi public final class AudioState implements Parcelable { /** Direct the audio stream through the device's earpiece. */ public static final int ROUTE_EARPIECE = 0x00000001; @@ -47,11 +44,8 @@ public final class AudioState implements Parcelable { */ public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET; - /** Bit mask of all possible audio routes. - * - * @hide - */ - public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | + /** Bit mask of all possible audio routes. */ + private static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET | ROUTE_SPEAKER; private final boolean isMuted; @@ -92,7 +86,6 @@ public final class AudioState implements Parcelable { audioRouteToString(supportedRouteMask)); } - /** @hide */ public static String audioRouteToString(int route) { if (route == 0 || (route & ~ROUTE_ALL) != 0x0) { return "UNKNOWN"; diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 6a828ec0473c..a46585a47349 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -30,10 +30,7 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * Represents an ongoing phone call that the in-call app should present to the user. - * - * {@hide} */ -@SystemApi public final class Call { /** * The state of a {@code Call} when newly created. @@ -91,8 +88,6 @@ public final class Call { * The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call * extras. Used to pass the phone accounts to display on the front end to the user in order to * select phone accounts to (for example) place a call. - * - * @hide */ public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts"; @@ -142,38 +137,32 @@ public final class Call { /** * Local device supports receiving video. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_LOCAL_RX = 0x00000100; /** * Local device supports transmitting video. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_LOCAL_TX = 0x00000200; /** * Local device supports bidirectional video calling. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = CAPABILITY_SUPPORTS_VT_LOCAL_RX | CAPABILITY_SUPPORTS_VT_LOCAL_TX; /** * Remote device supports receiving video. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_REMOTE_RX = 0x00000400; /** * Remote device supports transmitting video. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 0x00000800; /** * Remote device supports bidirectional video calling. - * @hide */ public static final int CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = CAPABILITY_SUPPORTS_VT_REMOTE_RX | CAPABILITY_SUPPORTS_VT_REMOTE_TX; @@ -191,27 +180,21 @@ public final class Call { /** * Whether the call is a generic conference, where we do not know the precise state of * participants in the conference (eg. on CDMA). - * - * @hide */ public static final int CAPABILITY_GENERIC_CONFERENCE = 0x00004000; /** * Call is using high definition audio. - * @hide */ public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00008000; /** * Call is using WIFI. - * @hide */ public static final int CAPABILITY_WIFI = 0x00010000; /** * Indicates that the current device callback number should be shown. - * - * @hide */ public static final int CAPABILITY_SHOW_CALLBACK_NUMBER = 0x00020000; @@ -221,14 +204,21 @@ public final class Call { */ public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000; - /** - * Call type can be modified for IMS call + /** + * Call can be upgraded to a video call. * @hide */ public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000; + /** + * For video calls, indicates whether the outgoing video for the call can be paused using + * the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState. + * @hide + */ + public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000; + //****************************************************************************************** - // Next CAPABILITY value: 0x00100000 + // Next CAPABILITY value: 0x00200000 //****************************************************************************************** private final Uri mHandle; @@ -244,7 +234,6 @@ public final class Call { private final int mVideoState; private final StatusHints mStatusHints; private final Bundle mExtras; - private final int mCallSubstate; /** * Whether the supplied capabilities supports the specified capability. @@ -252,7 +241,6 @@ public final class Call { * @param capabilities A bit field of capabilities. * @param capability The capability to check capabilities for. * @return Whether the specified capability is supported. - * @hide */ public static boolean can(int capabilities, int capability) { return (capabilities & capability) != 0; @@ -263,7 +251,6 @@ public final class Call { * * @param capability The capability to check capabilities for. * @return Whether the specified capability is supported. - * @hide */ public boolean can(int capability) { return can(mCallCapabilities, capability); @@ -335,6 +322,9 @@ public final class Call { if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) { builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO"); } + if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) { + builder.append(" CAPABILITY_CAN_PAUSE_VIDEO"); + } builder.append("]"); return builder.toString(); } @@ -440,14 +430,6 @@ public final class Call { return mExtras; } - /** - * @return The substate of the {@code Call}. - * @hide - */ - public int getCallSubstate() { - return mCallSubstate; - } - @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -466,8 +448,7 @@ public final class Call { Objects.equals(mGatewayInfo, d.mGatewayInfo) && Objects.equals(mVideoState, d.mVideoState) && Objects.equals(mStatusHints, d.mStatusHints) && - Objects.equals(mExtras, d.mExtras) && - Objects.equals(mCallSubstate, d.mCallSubstate); + Objects.equals(mExtras, d.mExtras); } return false; } @@ -487,8 +468,7 @@ public final class Call { Objects.hashCode(mGatewayInfo) + Objects.hashCode(mVideoState) + Objects.hashCode(mStatusHints) + - Objects.hashCode(mExtras) + - Objects.hashCode(mCallSubstate); + Objects.hashCode(mExtras); } /** {@hide} */ @@ -505,8 +485,7 @@ public final class Call { GatewayInfo gatewayInfo, int videoState, StatusHints statusHints, - Bundle extras, - int callSubstate) { + Bundle extras) { mHandle = handle; mHandlePresentation = handlePresentation; mCallerDisplayName = callerDisplayName; @@ -520,11 +499,10 @@ public final class Call { mVideoState = videoState; mStatusHints = statusHints; mExtras = extras; - mCallSubstate = callSubstate; } } - public static abstract class Listener { + public static abstract class Callback { /** * Invoked when the state of this {@code Call} has changed. See {@link #getState()}. * @@ -583,7 +561,6 @@ public final class Call { * * @param call The {@code Call} invoking this method. * @param videoCall The {@code Call.VideoCall} associated with the {@code Call}. - * @hide */ public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {} @@ -609,13 +586,21 @@ public final class Call { public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {} } + /** + * @deprecated Use {@code Call.Callback} instead. + * @hide + */ + @Deprecated + @SystemApi + public static abstract class Listener extends Callback { } + private final Phone mPhone; private final String mTelecomCallId; private final InCallAdapter mInCallAdapter; private final List<String> mChildrenIds = new ArrayList<>(); private final List<Call> mChildren = new ArrayList<>(); private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren); - private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); + private final List<Callback> mCallbacks = new CopyOnWriteArrayList<>(); private final List<Call> mConferenceableCalls = new ArrayList<>(); private final List<Call> mUnmodifiableConferenceableCalls = Collections.unmodifiableList(mConferenceableCalls); @@ -710,8 +695,8 @@ public final class Call { * {@code Call} will temporarily pause playing the tones for a pre-defined period of time. * * If the DTMF string contains a {@link TelecomManager#DTMF_CHARACTER_WAIT} symbol, this - * {@code Call} will pause playing the tones and notify listeners via - * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app + * {@code Call} will pause playing the tones and notify callbacks via + * {@link Callback#onPostDialWait(Call, String)}. At this point, the in-call app * should display to the user an indication of this state and an affordance to continue * the postdial sequence. When the user decides to continue the postdial sequence, the in-call * app should invoke the {@link #postDialContinue(boolean)} method. @@ -836,7 +821,6 @@ public final class Call { * Obtains an object that can be used to display video from this {@code Call}. * * @return An {@code Call.VideoCall}. - * @hide */ public InCallService.VideoCall getVideoCall() { return mVideoCall; @@ -853,25 +837,52 @@ public final class Call { } /** + * Registers a callback to this {@code Call}. + * + * @param callback A {@code Callback}. + */ + public void registerCallback(Callback callback) { + mCallbacks.add(callback); + } + + /** + * Unregisters a callback from this {@code Call}. + * + * @param callback A {@code Callback}. + */ + public void unregisterCallback(Callback callback) { + if (callback != null) { + mCallbacks.remove(callback); + } + } + + /** * Adds a listener to this {@code Call}. * * @param listener A {@code Listener}. + * @deprecated Use {@link #registerCallback} instead. + * @hide */ + @Deprecated + @SystemApi public void addListener(Listener listener) { - mListeners.add(listener); + registerCallback(listener); } /** * Removes a listener from this {@code Call}. * * @param listener A {@code Listener}. + * @deprecated Use {@link #unregisterCallback} instead. + * @hide */ + @Deprecated + @SystemApi public void removeListener(Listener listener) { - if (listener != null) { - mListeners.remove(listener); - } + unregisterCallback(listener); } + /** {@hide} */ Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) { mPhone = phone; @@ -901,8 +912,7 @@ public final class Call { parcelableCall.getGatewayInfo(), parcelableCall.getVideoState(), parcelableCall.getStatusHints(), - parcelableCall.getExtras(), - parcelableCall.getCallSubstate()); + parcelableCall.getExtras()); boolean detailsChanged = !Objects.equals(mDetails, details); if (detailsChanged) { mDetails = details; @@ -1003,56 +1013,56 @@ public final class Call { } private void fireStateChanged(int newState) { - for (Listener listener : mListeners) { - listener.onStateChanged(this, newState); + for (Callback callback : mCallbacks) { + callback.onStateChanged(this, newState); } } private void fireParentChanged(Call newParent) { - for (Listener listener : mListeners) { - listener.onParentChanged(this, newParent); + for (Callback callback : mCallbacks) { + callback.onParentChanged(this, newParent); } } private void fireChildrenChanged(List<Call> children) { - for (Listener listener : mListeners) { - listener.onChildrenChanged(this, children); + for (Callback callback : mCallbacks) { + callback.onChildrenChanged(this, children); } } private void fireDetailsChanged(Details details) { - for (Listener listener : mListeners) { - listener.onDetailsChanged(this, details); + for (Callback callback : mCallbacks) { + callback.onDetailsChanged(this, details); } } private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) { - for (Listener listener : mListeners) { - listener.onCannedTextResponsesLoaded(this, cannedTextResponses); + for (Callback callback : mCallbacks) { + callback.onCannedTextResponsesLoaded(this, cannedTextResponses); } } private void fireVideoCallChanged(InCallService.VideoCall videoCall) { - for (Listener listener : mListeners) { - listener.onVideoCallChanged(this, videoCall); + for (Callback callback : mCallbacks) { + callback.onVideoCallChanged(this, videoCall); } } private void firePostDialWait(String remainingPostDialSequence) { - for (Listener listener : mListeners) { - listener.onPostDialWait(this, remainingPostDialSequence); + for (Callback callback : mCallbacks) { + callback.onPostDialWait(this, remainingPostDialSequence); } } private void fireCallDestroyed() { - for (Listener listener : mListeners) { - listener.onCallDestroyed(this); + for (Callback callback : mCallbacks) { + callback.onCallDestroyed(this); } } private void fireConferenceableCallsChanged() { - for (Listener listener : mListeners) { - listener.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls); + for (Callback callback : mCallbacks) { + callback.onConferenceableCallsChanged(this, mUnmodifiableConferenceableCalls); } } diff --git a/telecomm/java/android/telecom/CallProperties.java b/telecomm/java/android/telecom/CallProperties.java index b1b82e233882..1721a392eff8 100644 --- a/telecomm/java/android/telecom/CallProperties.java +++ b/telecomm/java/android/telecom/CallProperties.java @@ -18,7 +18,6 @@ package android.telecom; /** * Defines properties of a phone call which may be affected by changes to the call. - * @hide */ public class CallProperties { /** Call is currently in a conference call. */ diff --git a/telecomm/java/android/telecom/CallState.java b/telecomm/java/android/telecom/CallState.java index bd9223affbff..558422622a06 100644 --- a/telecomm/java/android/telecom/CallState.java +++ b/telecomm/java/android/telecom/CallState.java @@ -16,17 +16,12 @@ package android.telecom; -import android.annotation.SystemApi; - /** * Defines call-state constants of the different states in which a call can exist. Although states * have the notion of normal transitions, due to the volatile nature of telephony systems, code * that uses these states should be resilient to unexpected state changes outside of what is * considered traditional. - * - * {@hide} */ -@SystemApi public final class CallState { private CallState() {} diff --git a/telecomm/java/android/telecom/CameraCapabilities.java b/telecomm/java/android/telecom/CameraCapabilities.java index 6eaf6a2cb543..62429569c71e 100644 --- a/telecomm/java/android/telecom/CameraCapabilities.java +++ b/telecomm/java/android/telecom/CameraCapabilities.java @@ -21,7 +21,6 @@ import android.os.Parcelable; /** * Represents the camera capabilities important to a Video Telephony provider. - * @hide */ public final class CameraCapabilities implements Parcelable { @@ -46,7 +45,7 @@ public final class CameraCapabilities implements Parcelable { private final float mMaxZoom; /** - * Create a call camera capabilities instance that doesn't support zoom. + * Create a call camera capabilities instance. * * @param width The width of the camera video (in pixels). * @param height The height of the camera video (in pixels). @@ -56,7 +55,8 @@ public final class CameraCapabilities implements Parcelable { } /** - * Create a call camera capabilities instance. + * Create a call camera capabilities instance that optionally + * supports zoom. * * @param width The width of the camera video (in pixels). * @param height The height of the camera video (in pixels). diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index c37460be0ab2..15a1da114cdf 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.telecom.Connection.VideoProvider; import java.util.ArrayList; @@ -29,9 +28,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * Represents a conference call which can contain any number of {@link Connection} objects. - * @hide */ -@SystemApi public abstract class Conference implements IConferenceable { /** @@ -487,7 +484,7 @@ public abstract class Conference implements IConferenceable { * * @return The time the {@code Conference} has been connected. */ - public long getConnectTimeMillis() { + public final long getConnectTimeMillis() { return mConnectTimeMillis; } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 741ba1798616..11632dcab9cc 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -19,7 +19,6 @@ package android.telecom; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; -import android.annotation.SystemApi; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -44,9 +43,7 @@ import java.util.concurrent.ConcurrentHashMap; * Implementations are then responsible for updating the state of the {@code Connection}, and * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no * longer used and associated resources may be recovered. - * @hide */ -@SystemApi public abstract class Connection implements IConferenceable { public static final int STATE_INITIALIZING = 0; @@ -187,56 +184,21 @@ public abstract class Connection implements IConferenceable { public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000; /** - * Call type can be modified for IMS call + * Call can be upgraded to a video call. * @hide */ public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000; - //********************************************************************************************** - // Next CAPABILITY value: 0x00100000 - //********************************************************************************************** - - /** - * Call substate bitmask values - */ - - /* Default case */ /** + * For video calls, indicates whether the outgoing video for the call can be paused using + * the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState. * @hide */ - public static final int SUBSTATE_NONE = 0; + public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000; - /* Indicates that the call is connected but audio attribute is suspended */ - /** - * @hide - */ - public static final int SUBSTATE_AUDIO_CONNECTED_SUSPENDED = 0x1; - - /* Indicates that the call is connected but video attribute is suspended */ - /** - * @hide - */ - public static final int SUBSTATE_VIDEO_CONNECTED_SUSPENDED = 0x2; - - /* Indicates that the call is established but media retry is needed */ - /** - * @hide - */ - public static final int SUBSTATE_AVP_RETRY = 0x4; - - /* Indicates that the call is multitasking */ - /** - * @hide - */ - public static final int SUBSTATE_MEDIA_PAUSED = 0x8; - - /* Mask containing all the call substate bits set */ - /** - * @hide - */ - public static final int SUBSTATE_ALL = SUBSTATE_AUDIO_CONNECTED_SUSPENDED | - SUBSTATE_VIDEO_CONNECTED_SUSPENDED | SUBSTATE_AVP_RETRY | - SUBSTATE_MEDIA_PAUSED; + //********************************************************************************************** + // Next CAPABILITY value: 0x00200000 + //********************************************************************************************** // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); @@ -345,6 +307,9 @@ public abstract class Connection implements IConferenceable { if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) { builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO"); } + if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) { + builder.append(" CAPABILITY_CAN_PAUSE_VIDEO"); + } builder.append("]"); return builder.toString(); } @@ -373,10 +338,8 @@ public abstract class Connection implements IConferenceable { public void onConferenceParticipantsChanged(Connection c, List<ConferenceParticipant> participants) {} public void onConferenceStarted() {} - public void onCallSubstateChanged(Connection c, int substate) {} } - /** @hide */ public static abstract class VideoProvider { /** @@ -801,7 +764,6 @@ public abstract class Connection implements IConferenceable { private DisconnectCause mDisconnectCause; private Conference mConference; private ConnectionService mConnectionService; - private int mCallSubstate; /** * Create a new Connection. @@ -860,21 +822,6 @@ public abstract class Connection implements IConferenceable { } /** - * Returns the call substate of the call. - * Valid values: {@link Connection#SUBSTATE_NONE}, - * {@link Connection#SUBSTATE_AUDIO_CONNECTED_SUSPENDED}, - * {@link Connection#SUBSTATE_VIDEO_CONNECTED_SUSPENDED}, - * {@link Connection#SUBSTATE_AVP_RETRY}, - * {@link Connection#SUBSTATE_MEDIA_PAUSED}. - * - * @param callSubstate The new call substate. - * @hide - */ - public final int getCallSubstate() { - return mCallSubstate; - } - - /** * @return The audio state of the connection, describing how its audio is currently * being routed by the system. This is {@code null} if this Connection * does not directly know about its audio state. @@ -1048,25 +995,6 @@ public abstract class Connection implements IConferenceable { } /** - * Set the call substate for the connection. - * Valid values: {@link Connection#SUBSTATE_NONE}, - * {@link Connection#SUBSTATE_AUDIO_CONNECTED_SUSPENDED}, - * {@link Connection#SUBSTATE_VIDEO_CONNECTED_SUSPENDED}, - * {@link Connection#SUBSTATE_AVP_RETRY}, - * {@link Connection#SUBSTATE_MEDIA_PAUSED}. - * - * @param callSubstate The new call substate. - * @hide - */ - public final void setCallSubstate(int callSubstate) { - Log.d(this, "setCallSubstate %d", callSubstate); - mCallSubstate = callSubstate; - for (Listener l : mListeners) { - l.onCallSubstateChanged(this, mCallSubstate); - } - } - - /** * Sets state to active (e.g., an ongoing connection where two or more parties can actively * communicate). */ @@ -1129,7 +1057,6 @@ public abstract class Connection implements IConferenceable { } } - /** @hide */ public final VideoProvider getVideoProvider() { return mVideoProvider; } @@ -1586,6 +1513,7 @@ public abstract class Connection implements IConferenceable { /** * Notifies listeners that a conference call has been started. + * @hide */ protected void notifyConferenceStarted() { for (Listener l : mListeners) { diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index f691c179af53..71b481b89c77 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; @@ -25,9 +24,7 @@ import android.os.Parcelable; /** * Simple data container encapsulating a request to some entity to * create a new {@link Connection}. - * @hide */ -@SystemApi public final class ConnectionRequest implements Parcelable { // TODO: Token to limit recursive invocations diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 0c719cd08383..73d1644e9463 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -17,7 +17,6 @@ package android.telecom; import android.annotation.SdkConstant; -import android.annotation.SystemApi; import android.app.Service; import android.content.ComponentName; import android.content.Intent; @@ -72,9 +71,7 @@ import java.util.concurrent.ConcurrentHashMap; * receives call-commands such as answer, reject, hold and disconnect. * <p> * When there are no more live calls, telecom will unbind from the {@code ConnectionService}. - * @hide */ -@SystemApi public abstract class ConnectionService extends Service { /** * The {@link Intent} that must be declared as handled by the service. @@ -559,13 +556,6 @@ public abstract class ConnectionService extends Service { mAdapter.setIsConferenced(id, conferenceId); } } - - @Override - public void onCallSubstateChanged(Connection c, int callSubstate) { - String id = mIdByConnection.get(c); - Log.d(this, "Adapter set call substate %d", callSubstate); - mAdapter.setCallSubstate(id, callSubstate); - } }; /** {@inheritDoc} */ @@ -635,8 +625,7 @@ public abstract class ConnectionService extends Service { connection.getAudioModeIsVoip(), connection.getStatusHints(), connection.getDisconnectCause(), - createIdList(connection.getConferenceables()), - connection.getCallSubstate())); + createIdList(connection.getConferenceables()))); } private void abort(String callId) { @@ -959,7 +948,7 @@ public abstract class ConnectionService extends Service { connection.getAudioModeIsVoip(), connection.getStatusHints(), connection.getDisconnectCause(), - emptyList, connection.getCallSubstate()); + emptyList); mAdapter.addExistingConnection(id, parcelableConnection); } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index a410976f4ec2..d026a28f1742 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -369,26 +369,4 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } } - - /** - * Set the call substate for the connection. - * Valid values: {@link Connection#CALL_SUBSTATE_NONE}, - * {@link Connection#CALL_SUBSTATE_AUDIO_CONNECTED_SUSPENDED}, - * {@link Connection#CALL_SUBSTATE_VIDEO_CONNECTED_SUSPENDED}, - * {@link Connection#CALL_SUBSTATE_AVP_RETRY}, - * {@link Connection#CALL_SUBSTATE_MEDIA_PAUSED}. - * - * @param callId The unique ID of the call to set the substate for. - * @param callSubstate The new call substate. - * @hide - */ - public final void setCallSubstate(String callId, int callSubstate) { - Log.v(this, "setCallSubstate: %d", callSubstate); - for (IConnectionServiceAdapter adapter : mAdapters) { - try { - adapter.setCallSubstate(callId, callSubstate); - } catch (RemoteException ignored) { - } - } - } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 5f937893cfe9..429f296e34d7 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -59,7 +59,6 @@ final class ConnectionServiceAdapterServant { private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20; private static final int MSG_ADD_EXISTING_CONNECTION = 21; private static final int MSG_ON_POST_DIAL_CHAR = 22; - private static final int MSG_SET_CALL_SUBSTATE = 23; private final IConnectionServiceAdapter mDelegate; @@ -221,10 +220,6 @@ final class ConnectionServiceAdapterServant { } break; } - case MSG_SET_CALL_SUBSTATE: { - mDelegate.setCallSubstate((String) msg.obj, msg.arg1); - break; - } } } }; @@ -389,12 +384,6 @@ final class ConnectionServiceAdapterServant { args.arg2 = connection; mHandler.obtainMessage(MSG_ADD_EXISTING_CONNECTION, args).sendToTarget(); } - - @Override - public void setCallSubstate(String connectionId, int callSubstate) { - mHandler.obtainMessage(MSG_SET_CALL_SUBSTATE, callSubstate, 0, - connectionId).sendToTarget(); - } }; public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 130d6762a213..73bcd0c422cb 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.media.ToneGenerator; @@ -30,9 +29,7 @@ import java.util.Objects; * user. It is the responsibility of the {@link ConnectionService} to provide localized versions of * the label and description. It also may contain a reason for the disconnect, which is intended for * logging and not for display to the user. - * @hide */ -@SystemApi public final class DisconnectCause implements Parcelable { /** Disconnected because of an unknown or unspecified reason. */ diff --git a/telecomm/java/android/telecom/GatewayInfo.java b/telecomm/java/android/telecom/GatewayInfo.java index 5b8e4ab3d533..928570e2ec69 100644 --- a/telecomm/java/android/telecom/GatewayInfo.java +++ b/telecomm/java/android/telecom/GatewayInfo.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -34,17 +33,13 @@ import android.text.TextUtils; * <li> Call the appropriate gateway address. * <li> Display information about how the call is being routed to the user. * </ol> - * @hide */ -@SystemApi public class GatewayInfo implements Parcelable { private final String mGatewayProviderPackageName; private final Uri mGatewayAddress; private final Uri mOriginalAddress; - /** @hide */ - @SystemApi public GatewayInfo(String packageName, Uri gatewayUri, Uri originalAddress) { mGatewayProviderPackageName = packageName; mGatewayAddress = gatewayUri; diff --git a/telecomm/java/android/telecom/IConferenceable.java b/telecomm/java/android/telecom/IConferenceable.java index 095d7cbac545..a9be20b6ea8f 100644 --- a/telecomm/java/android/telecom/IConferenceable.java +++ b/telecomm/java/android/telecom/IConferenceable.java @@ -16,16 +16,11 @@ package android.telecom; -import android.annotation.SystemApi; - /** * Interface used to identify entities with which another entity can participate in a conference * call with. The {@link ConnectionService} implementation will only recognize * {@link IConferenceable}s which are {@link Connection}s or {@link Conference}s. - * - * @hide */ -@SystemApi public interface IConferenceable { } diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 1f7547df88ff..7cbc0fc4beb1 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -16,8 +16,8 @@ package android.telecom; -import android.annotation.SystemApi; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.Handler; @@ -31,15 +31,14 @@ import com.android.internal.telecom.IInCallAdapter; import com.android.internal.telecom.IInCallService; import java.lang.String; +import java.util.Collections; +import java.util.List; /** * This service is implemented by any app that wishes to provide the user-interface for managing * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, * and uses it to notify the in-call app of any live and and recently disconnected calls. - * - * {@hide} */ -@SystemApi public abstract class InCallService extends Service { /** @@ -67,6 +66,7 @@ public abstract class InCallService extends Service { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj)); + mPhone.addListener(mPhoneListener); onPhoneCreated(mPhone); break; case MSG_ADD_CALL: @@ -148,6 +148,39 @@ public abstract class InCallService extends Service { } } + private Phone.Listener mPhoneListener = new Phone.Listener() { + /** ${inheritDoc} */ + @Override + public void onAudioStateChanged(Phone phone, AudioState audioState) { + InCallService.this.onAudioStateChanged(audioState); + } + + /** ${inheritDoc} */ + @Override + public void onBringToForeground(Phone phone, boolean showDialpad) { + InCallService.this.onBringToForeground(showDialpad); + } + + /** ${inheritDoc} */ + @Override + public void onCallAdded(Phone phone, Call call) { + InCallService.this.onCallAdded(call); + } + + /** ${inheritDoc} */ + @Override + public void onCallRemoved(Phone phone, Call call) { + InCallService.this.onCallRemoved(call); + } + + /** ${inheritDoc} */ + @Override + public void onCanAddCallChanged(Phone phone, boolean canAddCall) { + InCallService.this.onCanAddCallChanged(canAddCall); + } + + }; + private Phone mPhone; public InCallService() { @@ -165,8 +198,14 @@ public abstract class InCallService extends Service { mPhone = null; oldPhone.destroy(); + // destroy sets all the calls to disconnected if any live ones still exist. Therefore, + // it is important to remove the Listener *after* the call to destroy so that + // InCallService.on* callbacks are appropriately called. + oldPhone.removeListener(mPhoneListener); + onPhoneDestroyed(oldPhone); } + return false; } @@ -176,19 +215,79 @@ public abstract class InCallService extends Service { * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null} * if the {@code InCallService} is not in a state where it has an associated * {@code Phone}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ - public final Phone getPhone() { + @SystemApi + @Deprecated + public Phone getPhone() { return mPhone; } /** + * Obtains the current list of {@code Call}s to be displayed by this in-call experience. + * + * @return A list of the relevant {@code Call}s. + */ + public final List<Call> getCalls() { + return mPhone == null ? Collections.<Call>emptyList() : mPhone.getCalls(); + } + + /** + * Returns if the device can support additional calls. + * + * @return Whether the phone supports adding more calls. + */ + public final boolean canAddCall() { + return mPhone == null ? false : mPhone.canAddCall(); + } + + /** + * Obtains the current phone call audio state. + * + * @return An object encapsulating the audio state. Returns null if the service is not + * fully initialized. + */ + public final AudioState getAudioState() { + return mPhone == null ? null : mPhone.getAudioState(); + } + + /** + * Sets the microphone mute state. When this request is honored, there will be change to + * the {@link #getAudioState()}. + * + * @param state {@code true} if the microphone should be muted; {@code false} otherwise. + */ + public final void setMuted(boolean state) { + if (mPhone != null) { + mPhone.setMuted(state); + } + } + + /** + * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will + * be change to the {@link #getAudioState()}. + * + * @param route The audio route to use. + */ + public final void setAudioRoute(int route) { + if (mPhone != null) { + mPhone.setAudioRoute(route); + } + } + + /** * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience * to start displaying in-call information to the user. Each instance of {@code InCallService} * will have only one {@code Phone}, and this method will be called exactly once in the lifetime * of the {@code InCallService}. * * @param phone The {@code Phone} object associated with this {@code InCallService}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ + @SystemApi + @Deprecated public void onPhoneCreated(Phone phone) { } @@ -199,23 +298,76 @@ public abstract class InCallService extends Service { * call to {@link #onPhoneCreated(Phone)}. * * @param phone The {@code Phone} object associated with this {@code InCallService}. + * @hide + * @deprecated Use direct methods on InCallService instead of {@link Phone}. */ + @SystemApi + @Deprecated public void onPhoneDestroyed(Phone phone) { } /** + * Called when the audio state changes. + * + * @param audioState The new {@link AudioState}. + */ + public void onAudioStateChanged(AudioState audioState) { + } + + /** + * Called to bring the in-call screen to the foreground. The in-call experience should + * respond immediately by coming to the foreground to inform the user of the state of + * ongoing {@code Call}s. + * + * @param showDialpad If true, put up the dialpad when the screen is shown. + */ + public void onBringToForeground(boolean showDialpad) { + } + + /** + * Called when a {@code Call} has been added to this in-call session. The in-call user + * experience should add necessary state listeners to the specified {@code Call} and + * immediately start to show the user information about the existence + * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will + * include this {@code Call}. + * + * @param call A newly added {@code Call}. + */ + public void onCallAdded(Call call) { + } + + /** + * Called when a {@code Call} has been removed from this in-call session. The in-call user + * experience should remove any state listeners from the specified {@code Call} and + * immediately stop displaying any information about this {@code Call}. + * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}. + * + * @param call A newly removed {@code Call}. + */ + public void onCallRemoved(Call call) { + } + + /** + * Called when the ability to add more calls changes. If the phone cannot + * support more calls then {@code canAddCall} is set to {@code false}. If it can, then it + * is set to {@code true}. This can be used to control the visibility of UI to add more calls. + * + * @param canAddCall Indicates whether an additional call can be added. + */ + public void onCanAddCallChanged(boolean canAddCall) { + } + + /** * Class to invoke functionality related to video calls. - * @hide */ public static abstract class VideoCall { /** - * Sets a listener to invoke callback methods in the InCallUI after performing video - * telephony actions. + * Registers a callback to receive commands and state changes for video calls. * - * @param videoCallListener The call video client. + * @param callback The video call callback. */ - public abstract void setVideoCallListener(VideoCall.Listener videoCallListener); + public abstract void registerCallback(VideoCall.Callback callback); /** * Sets the camera to be used for video recording in a video call. @@ -258,7 +410,7 @@ public abstract class InCallService extends Service { /** * Issues a request to modify the properties of the current session. The request is sent to * the remote device where it it handled by - * {@link VideoCall.Listener#onSessionModifyRequestReceived}. + * {@link VideoCall.Callback#onSessionModifyRequestReceived}. * Some examples of session modification requests: upgrade call from audio to video, * downgrade call from video to audio, pause video. * @@ -270,9 +422,9 @@ public abstract class InCallService extends Service { * Provides a response to a request to change the current call session video * properties. * This is in response to a request the InCall UI has received via - * {@link VideoCall.Listener#onSessionModifyRequestReceived}. + * {@link VideoCall.Callback#onSessionModifyRequestReceived}. * The response is handled on the remove device by - * {@link VideoCall.Listener#onSessionModifyResponseReceived}. + * {@link VideoCall.Callback#onSessionModifyResponseReceived}. * * @param responseProfile The response call video properties. */ @@ -281,14 +433,14 @@ public abstract class InCallService extends Service { /** * Issues a request to the video provider to retrieve the camera capabilities. * Camera capabilities are reported back to the caller via - * {@link VideoCall.Listener#onCameraCapabilitiesChanged(CameraCapabilities)}. + * {@link VideoCall.Callback#onCameraCapabilitiesChanged(CameraCapabilities)}. */ public abstract void requestCameraCapabilities(); /** * Issues a request to the video telephony framework to retrieve the cumulative data usage for * the current call. Data usage is reported back to the caller via - * {@link VideoCall.Listener#onCallDataUsageChanged}. + * {@link VideoCall.Callback#onCallDataUsageChanged}. */ public abstract void requestCallDataUsage(); @@ -301,10 +453,9 @@ public abstract class InCallService extends Service { public abstract void setPauseImage(String uri); /** - * Listener class which invokes callbacks after video call actions occur. - * @hide + * Callback class which invokes callbacks after video call actions occur. */ - public static abstract class Listener { + public static abstract class Callback { /** * Called when a session modification request is received from the remote device. * The remote request is sent via @@ -379,8 +530,7 @@ public abstract class InCallService extends Service { * * @param cameraCapabilities The changed camera capabilities. */ - public abstract void onCameraCapabilitiesChanged( - CameraCapabilities cameraCapabilities); + public abstract void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities); } } } diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index adc648f2aa6c..c5c3d11a1690 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -54,7 +54,6 @@ public final class ParcelableCall implements Parcelable { private final int mVideoState; private final List<String> mConferenceableCallIds; private final Bundle mExtras; - private int mCallSubstate; public ParcelableCall( String id, @@ -76,8 +75,7 @@ public final class ParcelableCall implements Parcelable { StatusHints statusHints, int videoState, List<String> conferenceableCallIds, - Bundle extras, - int callSubstate) { + Bundle extras) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -98,7 +96,6 @@ public final class ParcelableCall implements Parcelable { mVideoState = videoState; mConferenceableCallIds = Collections.unmodifiableList(conferenceableCallIds); mExtras = extras; - mCallSubstate = callSubstate; } /** The unique ID of the call. */ @@ -235,14 +232,6 @@ public final class ParcelableCall implements Parcelable { return mExtras; } - /** - * The call substate. - * @return The substate of the call. - */ - public int getCallSubstate() { - return mCallSubstate; - } - /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ public static final Parcelable.Creator<ParcelableCall> CREATOR = new Parcelable.Creator<ParcelableCall> () { @@ -273,7 +262,6 @@ public final class ParcelableCall implements Parcelable { List<String> conferenceableCallIds = new ArrayList<>(); source.readList(conferenceableCallIds, classLoader); Bundle extras = source.readParcelable(classLoader); - int callSubstate = source.readInt(); return new ParcelableCall( id, state, @@ -294,8 +282,7 @@ public final class ParcelableCall implements Parcelable { statusHints, videoState, conferenceableCallIds, - extras, - callSubstate); + extras); } @Override @@ -334,7 +321,6 @@ public final class ParcelableCall implements Parcelable { destination.writeInt(mVideoState); destination.writeList(mConferenceableCallIds); destination.writeParcelable(mExtras, 0); - destination.writeInt(mCallSubstate); } @Override diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java index b60b99da3bcd..552e2503166d 100644 --- a/telecomm/java/android/telecom/ParcelableConnection.java +++ b/telecomm/java/android/telecom/ParcelableConnection.java @@ -46,7 +46,6 @@ public final class ParcelableConnection implements Parcelable { private final StatusHints mStatusHints; private final DisconnectCause mDisconnectCause; private final List<String> mConferenceableConnectionIds; - private final int mCallSubstate; /** @hide */ public ParcelableConnection( @@ -63,8 +62,7 @@ public final class ParcelableConnection implements Parcelable { boolean isVoipAudioMode, StatusHints statusHints, DisconnectCause disconnectCause, - List<String> conferenceableConnectionIds, - int callSubstate) { + List<String> conferenceableConnectionIds) { mPhoneAccount = phoneAccount; mState = state; mConnectionCapabilities = capabilities; @@ -79,7 +77,6 @@ public final class ParcelableConnection implements Parcelable { mStatusHints = statusHints; mDisconnectCause = disconnectCause; this.mConferenceableConnectionIds = conferenceableConnectionIds; - mCallSubstate = callSubstate; } public PhoneAccountHandle getPhoneAccount() { @@ -139,10 +136,6 @@ public final class ParcelableConnection implements Parcelable { return mConferenceableConnectionIds; } - public int getCallSubstate() { - return mCallSubstate; - } - @Override public String toString() { return new StringBuilder() @@ -177,7 +170,6 @@ public final class ParcelableConnection implements Parcelable { DisconnectCause disconnectCause = source.readParcelable(classLoader); List<String> conferenceableConnectionIds = new ArrayList<>(); source.readStringList(conferenceableConnectionIds); - int callSubstate = source.readInt(); return new ParcelableConnection( phoneAccount, @@ -193,8 +185,7 @@ public final class ParcelableConnection implements Parcelable { audioModeIsVoip, statusHints, disconnectCause, - conferenceableConnectionIds, - callSubstate); + conferenceableConnectionIds); } @Override @@ -227,6 +218,5 @@ public final class ParcelableConnection implements Parcelable { destination.writeParcelable(mStatusHints, 0); destination.writeParcelable(mDisconnectCause, 0); destination.writeStringList(mConferenceableConnectionIds); - destination.writeInt(mCallSubstate); } } diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index cc731095f9c0..c1c1129a2aab 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -28,9 +28,11 @@ import java.util.concurrent.CopyOnWriteArrayList; /** * A unified virtual device providing a means of voice (and other) communication on a device. * - * {@hide} + * @hide + * @deprecated Use {@link InCallService} directly instead of using this class. */ @SystemApi +@Deprecated public final class Phone { public abstract static class Listener { @@ -104,12 +106,10 @@ public final class Phone { private boolean mCanAddCall = true; - /** {@hide} */ Phone(InCallAdapter adapter) { mInCallAdapter = adapter; } - /** {@hide} */ final void internalAddCall(ParcelableCall parcelableCall) { Call call = new Call(this, parcelableCall.getId(), mInCallAdapter); mCallByTelecomCallId.put(parcelableCall.getId(), call); @@ -119,14 +119,12 @@ public final class Phone { fireCallAdded(call); } - /** {@hide} */ final void internalRemoveCall(Call call) { mCallByTelecomCallId.remove(call.internalGetCallId()); mCalls.remove(call); fireCallRemoved(call); } - /** {@hide} */ final void internalUpdateCall(ParcelableCall parcelableCall) { Call call = mCallByTelecomCallId.get(parcelableCall.getId()); if (call != null) { @@ -135,7 +133,6 @@ public final class Phone { } } - /** {@hide} */ final void internalSetPostDialWait(String telecomId, String remaining) { Call call = mCallByTelecomCallId.get(telecomId); if (call != null) { @@ -143,7 +140,6 @@ public final class Phone { } } - /** {@hide} */ final void internalAudioStateChanged(AudioState audioState) { if (!Objects.equals(mAudioState, audioState)) { mAudioState = audioState; @@ -151,17 +147,14 @@ public final class Phone { } } - /** {@hide} */ final Call internalGetCallByTelecomId(String telecomId) { return mCallByTelecomCallId.get(telecomId); } - /** {@hide} */ final void internalBringToForeground(boolean showDialpad) { fireBringToForeground(showDialpad); } - /** {@hide} */ final void internalSetCanAddCall(boolean canAddCall) { if (mCanAddCall != canAddCall) { mCanAddCall = canAddCall; @@ -171,7 +164,6 @@ public final class Phone { /** * Called to destroy the phone and cleanup any lingering calls. - * @hide */ final void destroy() { for (Call call : mCalls) { diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 07f905314d0b..bab460dfb71e 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -60,9 +60,7 @@ public class PhoneAccount implements Parcelable { * if the user has explicitly selected it to be used as the default connection manager. * <p> * See {@link #getCapabilities} - * @hide */ - @SystemApi public static final int CAPABILITY_CONNECTION_MANAGER = 0x1; /** @@ -74,9 +72,7 @@ public class PhoneAccount implements Parcelable { * <p> * See {@link #getCapabilities} * <p> - * {@hide} */ - @SystemApi public static final int CAPABILITY_CALL_PROVIDER = 0x2; /** @@ -203,13 +199,6 @@ public class PhoneAccount implements Parcelable { mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes()); } - /** @hide */ - @SystemApi - public Builder setAccountHandle(PhoneAccountHandle accountHandle) { - mAccountHandle = accountHandle; - return this; - } - /** * Sets the address. See {@link PhoneAccount#getAddress}. * @@ -333,9 +322,7 @@ public class PhoneAccount implements Parcelable { * * @param uriScheme The URI scheme. * @return The builder. - * @hide */ - @SystemApi public Builder addSupportedUriScheme(String uriScheme) { if (!TextUtils.isEmpty(uriScheme) && !mSupportedUriSchemes.contains(uriScheme)) { this.mSupportedUriSchemes.add(uriScheme); @@ -424,9 +411,7 @@ public class PhoneAccount implements Parcelable { * Returns a builder initialized with the current {@link PhoneAccount} instance. * * @return The builder. - * @hide */ - @SystemApi public Builder toBuilder() { return new Builder(this); } /** diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java index 4600b724e3ef..60917b2dc568 100644 --- a/telecomm/java/android/telecom/PhoneAccountHandle.java +++ b/telecomm/java/android/telecom/PhoneAccountHandle.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -47,8 +46,6 @@ public class PhoneAccountHandle implements Parcelable { this(componentName, id, Process.myUserHandle()); } - /** @hide */ - @SystemApi public PhoneAccountHandle( ComponentName componentName, String id, @@ -91,9 +88,7 @@ public class PhoneAccountHandle implements Parcelable { /** * @return the {@link UserHandle} to use when connecting to this PhoneAccount. - * @hide */ - @SystemApi public UserHandle getUserHandle() { return mUserHandle; } diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java index 3c6416b93961..fba3ee3265f9 100644 --- a/telecomm/java/android/telecom/RemoteConference.java +++ b/telecomm/java/android/telecom/RemoteConference.java @@ -18,7 +18,6 @@ package android.telecom; import com.android.internal.telecom.IConnectionService; -import android.annotation.SystemApi; import android.os.RemoteException; import java.util.ArrayList; @@ -30,9 +29,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * Represents a conference call which can contain any number of {@link Connection} objects. - * @hide */ -@SystemApi public final class RemoteConference { public abstract static class Callback { diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index 917fa77de66d..4c423f208f12 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -20,7 +20,6 @@ import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; -import android.annotation.SystemApi; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; @@ -38,9 +37,7 @@ import java.util.concurrent.ConcurrentHashMap; * * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest) * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest) - * @hide */ -@SystemApi public final class RemoteConnection { public static abstract class Callback { @@ -156,16 +153,6 @@ public final class RemoteConnection { public void onVideoStateChanged(RemoteConnection connection, int videoState) {} /** - * Indicates that the call substate of this {@code RemoteConnection} has changed. - * See {@link #getCallSubstate()}. - * - * @param connection The {@code RemoteConnection} invoking this method. - * @param callSubstate The new call substate of the {@code RemoteConnection}. - * @hide - */ - public void onCallSubstateChanged(RemoteConnection connection, int callSubstate) {} - - /** * Indicates that this {@code RemoteConnection} has been destroyed. No further requests * should be made to the {@code RemoteConnection}, and references to it should be cleared. * @@ -417,7 +404,6 @@ public final class RemoteConnection { private boolean mConnected; private int mConnectionCapabilities; private int mVideoState; - private int mCallSubstate; private VideoProvider mVideoProvider; private boolean mIsVoipAudioMode; private StatusHints mStatusHints; @@ -598,15 +584,6 @@ public final class RemoteConnection { } /** - * - * @return The call substate of the {@code RemoteConnection}. See - * @hide - */ - public int getCallSubstate() { - return mCallSubstate; - } - - /** * Obtains the video provider of this {@code RemoteConnection}. * @return The video provider associated with this {@code RemoteConnection}. * @hide @@ -919,16 +896,6 @@ public final class RemoteConnection { /** * @hide */ - void setCallSubstate(int callSubstate) { - mCallSubstate = callSubstate; - for (Callback c : mCallbacks) { - c.onCallSubstateChanged(this, callSubstate); - } - } - - /** - * @hide - */ void setVideoProvider(VideoProvider videoProvider) { mVideoProvider = videoProvider; for (Callback c : mCallbacks) { diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 7374a3b022ae..a9b725be133f 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -84,7 +84,6 @@ final class RemoteConnectionService { } connection.setConferenceableConnections(conferenceable); connection.setVideoState(parcel.getVideoState()); - connection.setCallSubstate(parcel.getCallSubstate()); if (connection.getState() == Connection.STATE_DISCONNECTED) { // ... then, if it was created in a disconnected state, that indicates // failure on the providing end, so immediately mark it destroyed @@ -312,12 +311,6 @@ final class RemoteConnectionService { mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction); } - - @Override - public void setCallSubstate(String callId, int callSubstate) { - findConnectionForAction(callId, "callSubstate") - .setCallSubstate(callSubstate); - } }; private final ConnectionServiceAdapterServant mServant = diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index dd3a639e6bf3..a32eae76dcea 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -16,7 +16,6 @@ package android.telecom; -import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -30,9 +29,7 @@ import java.util.Objects; /** * Contains status label and icon displayed in the in-call UI. - * @hide */ -@SystemApi public final class StatusHints implements Parcelable { private final ComponentName mPackageName; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 642a386c43ca..5abbb504aabc 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -55,8 +55,6 @@ public class TelecomManager { * Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the * {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then * ask the connection service for more information about the call prior to showing any UI. - * - * @hide */ public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL"; @@ -97,9 +95,7 @@ public class TelecomManager { /** * The {@link android.content.Intent} action used to show the settings page used to configure * {@link PhoneAccount} preferences. - * @hide */ - @SystemApi public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -145,10 +141,7 @@ public class TelecomManager { * Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains * metadata about the call. This {@link Bundle} will be returned to the * {@link ConnectionService}. - * - * @hide */ - @SystemApi public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS"; @@ -221,9 +214,7 @@ public class TelecomManager { * {@link ConnectionService}s which interact with {@link RemoteConnection}s should only populate * this if the {@link android.telephony.TelephonyManager#getLine1Number()} value, as that is the * user's expected caller ID. - * @hide */ - @SystemApi public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; /** @@ -364,9 +355,7 @@ public class TelecomManager { * @param uriScheme The URI scheme. * @return The {@link PhoneAccountHandle} corresponding to the user-chosen default for outgoing * phone calls for a specified URI scheme. - * @hide */ - @SystemApi public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { try { if (isServiceConnected()) { @@ -419,7 +408,6 @@ public class TelecomManager { * {@code null}, indicating that there currently exists no user-chosen default * {@code PhoneAccount}. * @return The phone account handle of the current sim call manager. - * @hide */ public PhoneAccountHandle getSimCallManager() { try { @@ -634,10 +622,7 @@ public class TelecomManager { * {@link PhoneAccountHandle#getComponentName()} does not match the package name of the app. * * @param account The complete {@link PhoneAccount}. - * - * @hide */ - @SystemApi public void registerPhoneAccount(PhoneAccount account) { try { if (isServiceConnected()) { @@ -652,9 +637,7 @@ public class TelecomManager { * Remove a {@link PhoneAccount} registration from the system. * * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister. - * @hide */ - @SystemApi public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { @@ -724,10 +707,7 @@ public class TelecomManager { * * @param accountHandle The handle for the account to check the voicemail number against * @param number The number to look up. - * - * @hide */ - @SystemApi public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { try { if (isServiceConnected()) { @@ -740,23 +720,21 @@ public class TelecomManager { } /** - * Return whether a given phone account has a voicemail number configured. - * - * @param accountHandle The handle for the account to check for a voicemail number. - * @return {@code true} If the given phone account has a voicemail number. + * Return the voicemail number for a given phone account. * - * @hide + * @param accountHandle The handle for the phone account. + * @return The voicemail number for the phone account, and {@code null} if one has not been + * configured. */ - @SystemApi - public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) { + public String getVoiceMailNumber(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { - return getTelecomService().hasVoiceMailNumber(accountHandle); + return getTelecomService().getVoiceMailNumber(accountHandle); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e); } - return false; + return null; } /** @@ -764,10 +742,7 @@ public class TelecomManager { * * @param accountHandle The handle for the account retrieve a number for. * @return A string representation of the line 1 phone number. - * - * @hide */ - @SystemApi public String getLine1Number(PhoneAccountHandle accountHandle) { try { if (isServiceConnected()) { @@ -877,10 +852,7 @@ public class TelecomManager { /** * Silences the ringer if a ringing call exists. - * - * @hide */ - @SystemApi public void silenceRinger() { try { if (isServiceConnected()) { @@ -941,9 +913,7 @@ public class TelecomManager { * {@link #registerPhoneAccount}. * @param extras A bundle that will be passed through to * {@link ConnectionService#onCreateIncomingConnection}. - * @hide */ - @SystemApi public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) { try { if (isServiceConnected()) { @@ -1031,9 +1001,7 @@ public class TelecomManager { * {@code null} to return a URI which will use the default account. * @return The URI (with the content:// scheme) specific to the specified {@link PhoneAccount} * for the the content retrieve. - * @hide */ - @SystemApi public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) { ITelecomService service = getTelecomService(); if (service != null && accountHandle != null) { diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java index 04454489a0be..7bef6881f571 100644 --- a/telecomm/java/android/telecom/VideoCallImpl.java +++ b/telecomm/java/android/telecom/VideoCallImpl.java @@ -46,7 +46,7 @@ public class VideoCallImpl extends VideoCall { private final IVideoProvider mVideoProvider; private final VideoCallListenerBinder mBinder; - private VideoCall.Listener mVideoCallListener; + private VideoCall.Callback mCallback; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -109,14 +109,14 @@ public class VideoCallImpl extends VideoCall { private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { - if (mVideoCallListener == null) { + if (mCallback == null) { return; } SomeArgs args; switch (msg.what) { case MSG_RECEIVE_SESSION_MODIFY_REQUEST: - mVideoCallListener.onSessionModifyRequestReceived((VideoProfile) msg.obj); + mCallback.onSessionModifyRequestReceived((VideoProfile) msg.obj); break; case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: args = (SomeArgs) msg.obj; @@ -125,34 +125,34 @@ public class VideoCallImpl extends VideoCall { VideoProfile requestProfile = (VideoProfile) args.arg2; VideoProfile responseProfile = (VideoProfile) args.arg3; - mVideoCallListener.onSessionModifyResponseReceived( + mCallback.onSessionModifyResponseReceived( status, requestProfile, responseProfile); } finally { args.recycle(); } break; case MSG_HANDLE_CALL_SESSION_EVENT: - mVideoCallListener.onCallSessionEvent((int) msg.obj); + mCallback.onCallSessionEvent((int) msg.obj); break; case MSG_CHANGE_PEER_DIMENSIONS: args = (SomeArgs) msg.obj; try { int width = (int) args.arg1; int height = (int) args.arg2; - mVideoCallListener.onPeerDimensionsChanged(width, height); + mCallback.onPeerDimensionsChanged(width, height); } finally { args.recycle(); } break; case MSG_CHANGE_CALL_DATA_USAGE: - mVideoCallListener.onCallDataUsageChanged((long) msg.obj); + mCallback.onCallDataUsageChanged((long) msg.obj); break; case MSG_CHANGE_CAMERA_CAPABILITIES: - mVideoCallListener.onCameraCapabilitiesChanged( + mCallback.onCameraCapabilitiesChanged( (CameraCapabilities) msg.obj); break; case MSG_CHANGE_VIDEO_QUALITY: - mVideoCallListener.onVideoQualityChanged(msg.arg1); + mCallback.onVideoQualityChanged(msg.arg1); break; default: break; @@ -170,8 +170,8 @@ public class VideoCallImpl extends VideoCall { } /** {@inheritDoc} */ - public void setVideoCallListener(VideoCall.Listener videoCallListener) { - mVideoCallListener = videoCallListener; + public void registerCallback(VideoCall.Callback callback) { + mCallback = callback; } /** {@inheritDoc} */ diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index e6c28f3e913c..7e7e9cc4a904 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -81,6 +81,4 @@ oneway interface IConnectionServiceAdapter { void setConferenceableConnections(String callId, in List<String> conferenceableCallIds); void addExistingConnection(String callId, in ParcelableConnection connection); - - void setCallSubstate(String callId, int callSubstate); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index d2030f24e6e2..35db97fb50ee 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -121,9 +121,9 @@ interface ITelecomService { boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number); /** - * @see TelecomServiceImpl#hasVoiceMailNumber + * @see TelecomServiceImpl#getVoiceMailNumber */ - boolean hasVoiceMailNumber(in PhoneAccountHandle accountHandle); + String getVoiceMailNumber(in PhoneAccountHandle accountHandle); /** * @see TelecomServiceImpl#getLine1Number diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java new file mode 100644 index 000000000000..b1fb3a612d52 --- /dev/null +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2015 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.telephony; + +import com.android.internal.telephony.ICarrierConfigLoader; + +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Provides access to telephony configuration values that are carrier-specific. + * <p> + * Users should obtain an instance of this class by calling + * {@code mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);} + * </p> + * + * @see Context#getSystemService + * @see Context#CARRIER_CONFIG_SERVICE + */ +public class CarrierConfigManager { + /** + * @hide + */ + public CarrierConfigManager() { + } + + /** + * This intent is broadcast by the system when carrier config changes. + */ + public static final String + ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + + /** + * Flag specifying whether VoLTE should be available for carrier, independent of carrier + * provisioning. If false: hard disabled. If true: then depends on carrier provisioning, + * availability, etc. + */ + public static final String BOOL_CARRIER_VOLTE_AVAILABLE = "bool_carrier_volte_available"; + + /** + * Flag specifying whether VoLTE availability is based on provisioning. + */ + public static final String BOOL_CARRIER_VOLTE_PROVISIONED = "bool_carrier_volte_provisioned"; + + /** + * Flag specifying whether VoLTE TTY is supported. + */ + public static final String BOOL_CARRIER_VOLTE_TTY_SUPPORTED + = "bool_carrier_volte_tty_supported"; + + /** + * Show APN Settings for some CDMA carriers. + */ + public static final String BOOL_SHOW_APN_SETTING_CDMA = "bool_show_apn_setting_cdma"; + + /** + * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0 + * this is the value that should be used instead. A configuration value of + * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default + * assumption for phone type (GSM) should be used. + */ + public static final String INT_VOLTE_REPLACEMENT_RAT = "int_volte_replacement_rat"; + + /* The following 3 fields are related to carrier visual voicemail. */ + + /** + * The carrier number MO sms messages are sent to. + * + * @hide + */ + @SystemApi + public static final String STRING_VVM_DESTINATION_NUMBER = "string_vvm_destination_number"; + + /** + * The port through which the MO sms messages are sent through. + * + * @hide + */ + @SystemApi + public static final String SHORT_VVM_PORT_NUMBER = "string_vvm_port_number"; + + /** + * The type of visual voicemail protocol the carrier adheres to (see below). + * + * @hide + */ + @SystemApi + public static final String STRING_VVM_TYPE = "string_vvm_type"; + + /* Visual voicemail protocols */ + + /** + * The OMTP protocol. + * + * @hide + */ + @SystemApi + public static final String VVM_TYPE_OMTP = "vvm_type_omtp"; + + private final static String TAG = "CarrierConfigManager"; + + /** The default value for every variable. */ + private final static Bundle sDefaults; + + static { + sDefaults = new Bundle(); + sDefaults.putBoolean(BOOL_CARRIER_VOLTE_AVAILABLE, false); + sDefaults.putBoolean(BOOL_CARRIER_VOLTE_PROVISIONED, false); + sDefaults.putBoolean(BOOL_CARRIER_VOLTE_TTY_SUPPORTED, true); + sDefaults.putBoolean(BOOL_SHOW_APN_SETTING_CDMA, false); + + sDefaults.putInt(INT_VOLTE_REPLACEMENT_RAT, 0); + } + + /** + * Gets the configuration values for a particular subscription, which is associated with a + * specific SIM card. If an invalid subId is used, the returned config will contain default + * values. + * + * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}. + * @return A {@link Bundle} containing the config for the given subId, or default values for an + * invalid subId. + */ + public Bundle getConfigForSubId(int subId) { + try { + return getICarrierConfigLoader().getConfigForSubId(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error getting config for subId " + Integer.toString(subId) + ": " + + ex.toString()); + } catch (NullPointerException ex) { + Rlog.e(TAG, "Error getting config for subId " + Integer.toString(subId) + ": " + + ex.toString()); + } + return null; + } + + /** + * Gets the configuration values for the default subscription. + * + * @see #getConfigForSubId + */ + public Bundle getConfig() { + return getConfigForSubId(SubscriptionManager.getDefaultSubId()); + } + + /** + * Calling this method triggers telephony services to fetch the current carrier configuration. + * <p> + * Normally this does not need to be called because the platform reloads config on its own. Call + * this method if your app wants to update config at an arbitrary moment. + * </p> + * <p> + * This method returns before the reload has completed, and + * {@link android.service.carrier.CarrierConfigService#onLoadConfig} will be called from an + * arbitrary thread. + * </p> + */ + public void reloadCarrierConfigForSubId(int subId) { + try { + getICarrierConfigLoader().reloadCarrierConfigForSubId(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error reloading config for subId=" + subId + ": " + ex.toString()); + } catch (NullPointerException ex) { + Rlog.e(TAG, "Error reloading config for subId=" + subId + ": " + ex.toString()); + } + } + + /** + * Request the carrier config loader to update the cofig for phoneId. + * + * Depending on simState, the config may be cleared or loaded from config app. + * This is only used by SubscriptionInfoUpdater. + * + * @hide + */ + @SystemApi + public void updateConfigForPhoneId(int phoneId, String simState) { + try { + getICarrierConfigLoader().updateConfigForPhoneId(phoneId, simState); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error updating config for phoneId=" + phoneId + ": " + ex.toString()); + } catch (NullPointerException ex) { + Rlog.e(TAG, "Error updating config for phoneId=" + phoneId + ": " + ex.toString()); + } + } + + /** + * Returns a bundle with the default value for every supported configuration variable. + * + * @hide + */ + @SystemApi + public static Bundle getDefaultConfig() { + return sDefaults; + } + + /** @hide */ + private ICarrierConfigLoader getICarrierConfigLoader() { + return ICarrierConfigLoader.Stub + .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE)); + } +} diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index 611dd7bd512b..d19228896c5c 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -219,6 +219,15 @@ public class PhoneStateListener { */ public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000; + /** + * Listen for carrier network changes indicated by a carrier app. + * + * @see #onCarrierNetworkRequest + * @see TelephonyManager#notifyCarrierNetworkChange(boolean) + * @hide + */ + public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000; + /* * Subscription used to listen to the phone state changes * @hide @@ -321,6 +330,9 @@ public class PhoneStateListener { case LISTEN_OEM_HOOK_RAW_EVENT: PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj); break; + case LISTEN_CARRIER_NETWORK_CHANGE: + PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj); + break; } } @@ -500,6 +512,22 @@ public class PhoneStateListener { } /** + * Callback invoked when telephony has received notice from a carrier + * app that a network action that could result in connectivity loss + * has been requested by an app using + * {@link android.telephony.TelephonyManager#notifyCarrierNetworkChange(boolean)} + * + * @param active Whether the carrier network change is or shortly + * will be active. This value is true to indicate + * showing alternative UI and false to stop. + * + * @hide + */ + public void onCarrierNetworkChange(boolean active) { + // default implementation empty + } + + /** * The callback methods need to be called on the handler thread where * this object was created. If the binder did that for us it'd be nice. */ @@ -575,6 +603,10 @@ public class PhoneStateListener { public void onOemHookRawEvent(byte[] rawData) { Message.obtain(mHandler, LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData).sendToTarget(); } + + public void onCarrierNetworkChange(boolean active) { + Message.obtain(mHandler, LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active).sendToTarget(); + } }; private void log(String s) { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c5573bac884f..128f6e3c1eea 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -160,7 +160,6 @@ public class TelephonyManager { * Returns 1 for Single standby mode (Single SIM functionality) * Returns 2 for Dual standby mode.(Dual SIM functionality) */ - /** {@hide} */ public int getPhoneCount() { int phoneCount = 1; switch (getMultiSimConfiguration()) { @@ -682,7 +681,6 @@ public class TelephonyManager { * * @param slotId of which deviceID is returned */ - /** {@hide} */ public String getDeviceId(int slotId) { // FIXME this assumes phoneId == slotId try { @@ -2041,6 +2039,35 @@ public class TelephonyManager { } /** + * Informs the system of an intentional upcoming carrier network change by + * a carrier app. This call is optional and is only used to allow the + * system to provide alternative UI while telephony is performing an action + * that may result in intentional, temporary network lack of connectivity. + * <p> + * Based on the active parameter passed in, this method will either show or + * hide the alternative UI. There is no timeout associated with showing + * this UX, so a carrier app must be sure to call with active set to false + * sometime after calling with it set to true. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param active Whether the carrier network change is or shortly will be + * active. Set this value to true to begin showing + * alternative UI and false to stop. + */ + public void notifyCarrierNetworkChange(boolean active) { + try { + if (sRegistry != null) + sRegistry.notifyCarrierNetworkChange(active); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** * Returns the alphabetic identifier associated with the line 1 number. * Return null if it is unavailable. * <p> diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl index 9b435dcdf924..b1f2d3287dbe 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -255,11 +255,4 @@ interface IImsCallSession { * @return {@code True} if the session is multiparty. */ boolean isMultiparty(); - - /** - * Gets the call substate for this session. - * - * @return the call substate for this session. - */ - int getCallSubstate(); } diff --git a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl new file mode 100644 index 000000000000..b5cdd9a250d1 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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.internal.telephony; + +import android.os.Bundle; + +/** + * Interface used to interact with the CarrierConfigLoader + */ +interface ICarrierConfigLoader { + + Bundle getConfigForSubId(int subId); + + void reloadCarrierConfigForSubId(int subId); + + void updateConfigForPhoneId(int phoneId, String simState); +} diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl index cea62babe9a8..cbedb95d2ea5 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -44,5 +44,6 @@ oneway interface IPhoneStateListener { void onDataConnectionRealTimeInfoChanged(in DataConnectionRealTimeInfo dcRtInfo); void onVoLteServiceStateChanged(in VoLteServiceState lteState); void onOemHookRawEvent(in byte[] rawData); + void onCarrierNetworkChange(in boolean active); } diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 7d8a8d66582e..76b69cea8ae6 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -69,4 +69,5 @@ interface ITelephonyRegistry { void notifyVoLteServiceStateChanged(in VoLteServiceState lteState); void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData); void notifySubscriptionInfoChanged(); + void notifyCarrierNetworkChange(in boolean active); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 082e8bbb3ff9..12541d8e6aaa 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -69,6 +69,14 @@ public interface RILConstants { int SS_MODIFIED_TO_USSD = 25; /* SS request modified to USSD */ int SUBSCRIPTION_NOT_SUPPORTED = 26; /* Subscription not supported */ int SS_MODIFIED_TO_SS = 27; /* SS request modified to different SS request */ + int SIM_ALREADY_POWERED_OFF = 29; /* SAP: 0x03, Error card aleready powered off */ + int SIM_ALREADY_POWERED_ON = 30; /* SAP: 0x05, Error card already powered on */ + int SIM_DATA_NOT_AVAILABLE = 31; /* SAP: 0x06, Error data not available */ + int SIM_SAP_CONNECT_FAILURE = 32; + int SIM_SAP_MSG_SIZE_TOO_LARGE = 33; + int SIM_SAP_MSG_SIZE_TOO_SMALL = 34; + int SIM_SAP_CONNECT_OK_CALL_ONGOING = 35; + int LCE_NOT_SUPPORTED = 36; /* Link Capacity Estimation (LCE) not supported */ /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */ @@ -135,6 +143,11 @@ public interface RILConstants { int NV_CONFIG_ERASE_RESET = 2; int NV_CONFIG_FACTORY_RESET = 3; + /* LCE service related constants. */ + int LCE_NOT_AVAILABLE = -1; + int LCE_STOPPED = 0; + int LCE_ACTIVE = 1; + /* cat include/telephony/ril.h | \ egrep '^#define' | \ @@ -307,6 +320,9 @@ cat include/telephony/ril.h | \ int RIL_REQUEST_SHUTDOWN = 129; int RIL_REQUEST_GET_RADIO_CAPABILITY = 130; int RIL_REQUEST_SET_RADIO_CAPABILITY = 131; + int RIL_REQUEST_START_LCE = 132; + int RIL_REQUEST_STOP_LCE = 133; + int RIL_REQUEST_PULL_LCEDATA = 134; int RIL_UNSOL_RESPONSE_BASE = 1000; int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000; @@ -354,4 +370,5 @@ cat include/telephony/ril.h | \ int RIL_UNSOL_RADIO_CAPABILITY = 1042; int RIL_UNSOL_ON_SS = 1043; int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044; + int RIL_UNSOL_LCEDATA_RECV = 1045; } diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index e78750cc05be..5a8c7ff9b749 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -16,6 +16,7 @@ package android.test.mock; +import android.annotation.NonNull; import android.app.PackageInstallObserver; import android.content.ComponentName; import android.content.Intent; @@ -51,6 +52,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.UserHandle; +import android.os.storage.VolumeInfo; import java.util.List; @@ -498,6 +500,18 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** {@hide} */ + @Override + public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) { + throw new UnsupportedOperationException(); + } + + /** {@hide} */ + @Override + public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) { + throw new UnsupportedOperationException(); + } + @Override public String getInstallerPackageName(String packageName) { throw new UnsupportedOperationException(); diff --git a/tests/LockTaskTests/Android.mk b/tests/LockTaskTests/Android.mk new file mode 100644 index 000000000000..ed5864304b67 --- /dev/null +++ b/tests/LockTaskTests/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/priv-app + +LOCAL_PACKAGE_NAME := LockTaskTests +LOCAL_CERTIFICATE := platform + +LOCAL_SRC_FILES := $(call all-Iaidl-files-under, src) $(call all-java-files-under, src) + +include $(BUILD_PACKAGE) + +# Use the following include to make our test apk. +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/LockTaskTests/AndroidManifest.xml b/tests/LockTaskTests/AndroidManifest.xml new file mode 100644 index 000000000000..f88744e42084 --- /dev/null +++ b/tests/LockTaskTests/AndroidManifest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.example.locktasktests" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk + android:minSdkVersion="22" + android:targetSdkVersion="22" /> + <uses-permission android:name="android.permission.INTERNET"/> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" + android:allowBackup="true" > + <activity + android:name="com.google.android.example.locktasktests.MainActivity" + android:label="@string/app_name" + android:screenOrientation="portrait" + android:theme="@style/AppTheme" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name="com.google.android.example.locktasktests.LockDefaultActivity" + android:label="@string/title_activity_default" + android:taskAffinity="" + android:documentLaunchMode="always" + android:lockTaskMode="lockTaskModeDefault" > + </activity> + <activity + android:name="com.google.android.example.locktasktests.LockTaskNeverActivity" + android:label="@string/title_activity_never" + android:taskAffinity="" + android:documentLaunchMode="always" + android:lockTaskMode="lockTaskModeNever" > + </activity> + <activity + android:name="com.google.android.example.locktasktests.LockWhitelistedActivity" + android:label="@string/title_activity_whitelist" + android:taskAffinity="" + android:documentLaunchMode="always" + android:lockTaskMode="lockTaskModeIfWhitelisted" > + </activity> + <activity + android:name="com.google.android.example.locktasktests.LockAtLaunchActivity" + android:label="@string/title_activity_always" + android:taskAffinity="" + android:documentLaunchMode="always" + android:lockTaskMode="lockTaskModeAlways" > + </activity> + </application> + +</manifest> diff --git a/tests/LockTaskTests/res/drawable-hdpi/ic_launcher.png b/tests/LockTaskTests/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..288b66551d1e --- /dev/null +++ b/tests/LockTaskTests/res/drawable-hdpi/ic_launcher.png diff --git a/tests/LockTaskTests/res/drawable-mdpi/ic_launcher.png b/tests/LockTaskTests/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..6ae570b4db4d --- /dev/null +++ b/tests/LockTaskTests/res/drawable-mdpi/ic_launcher.png diff --git a/tests/LockTaskTests/res/drawable-xhdpi/ic_launcher.png b/tests/LockTaskTests/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..d4fb7cd9d868 --- /dev/null +++ b/tests/LockTaskTests/res/drawable-xhdpi/ic_launcher.png diff --git a/tests/LockTaskTests/res/drawable-xxhdpi/ic_launcher.png b/tests/LockTaskTests/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..85a6081587e2 --- /dev/null +++ b/tests/LockTaskTests/res/drawable-xxhdpi/ic_launcher.png diff --git a/tests/LockTaskTests/res/layout/activity_launch.xml b/tests/LockTaskTests/res/layout/activity_launch.xml new file mode 100644 index 000000000000..b619743a5c7b --- /dev/null +++ b/tests/LockTaskTests/res/layout/activity_launch.xml @@ -0,0 +1,32 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root_launch" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context="com.google.android.example.locktasktests.LaunchActivity" > + + <Button + android:id="@+id/button_try_lock" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:onClick="onTryLock" + android:text="@string/try_lock" /> + <Button + android:id="@+id/button_try_unlock" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:onClick="onTryUnlock" + android:text="@string/try_unlock" /> + <Button + android:id="@+id/button_launch_main" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:onClick="onLaunchMain" + android:text="@string/launch_main" /> + +</LinearLayout> diff --git a/tests/LockTaskTests/res/layout/activity_main.xml b/tests/LockTaskTests/res/layout/activity_main.xml new file mode 100644 index 000000000000..c2e93c4d3881 --- /dev/null +++ b/tests/LockTaskTests/res/layout/activity_main.xml @@ -0,0 +1,41 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/root_launch" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context="com.google.android.example.locktasktests.MainActivity" > + <Button + android:id="@+id/button_default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="onButtonPressed" + android:text="@string/launch_default" /> + <Button + android:id="@+id/button_never" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="onButtonPressed" + android:text="@string/launch_never" /> + <Button + android:id="@+id/button_whitelist" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="onButtonPressed" + android:text="@string/launch_whitelist" /> + <Button + android:id="@+id/button_always" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="onButtonPressed" + android:text="@string/launch_always" /> + +</LinearLayout> diff --git a/tests/LockTaskTests/res/values-v11/styles.xml b/tests/LockTaskTests/res/values-v11/styles.xml new file mode 100644 index 000000000000..3c02242ad044 --- /dev/null +++ b/tests/LockTaskTests/res/values-v11/styles.xml @@ -0,0 +1,11 @@ +<resources> + + <!-- + Base application theme for API 11+. This theme completely replaces + AppBaseTheme from res/values/styles.xml on API 11+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light"> + <!-- API 11 theme customizations can go here. --> + </style> + +</resources> diff --git a/tests/LockTaskTests/res/values-v14/styles.xml b/tests/LockTaskTests/res/values-v14/styles.xml new file mode 100644 index 000000000000..a91fd0372b20 --- /dev/null +++ b/tests/LockTaskTests/res/values-v14/styles.xml @@ -0,0 +1,12 @@ +<resources> + + <!-- + Base application theme for API 14+. This theme completely replaces + AppBaseTheme from BOTH res/values/styles.xml and + res/values-v11/styles.xml on API 14+ devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- API 14 theme customizations can go here. --> + </style> + +</resources> diff --git a/tests/LockTaskTests/res/values-w820dp/dimens.xml b/tests/LockTaskTests/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..f3e70203b90a --- /dev/null +++ b/tests/LockTaskTests/res/values-w820dp/dimens.xml @@ -0,0 +1,10 @@ +<resources> + + <!-- + Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). + --> + <dimen name="activity_horizontal_margin">64dp</dimen> + +</resources> diff --git a/tests/LockTaskTests/res/values/dimens.xml b/tests/LockTaskTests/res/values/dimens.xml new file mode 100644 index 000000000000..55c1e5908c7e --- /dev/null +++ b/tests/LockTaskTests/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/tests/LockTaskTests/res/values/strings.xml b/tests/LockTaskTests/res/values/strings.xml new file mode 100644 index 000000000000..ae7768ed6dfc --- /dev/null +++ b/tests/LockTaskTests/res/values/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Lock Task Tests</string> + <string name="title_activity_default">LockDefaultActivity</string> + <string name="title_activity_never">LockTaskNeverActivity</string> + <string name="title_activity_whitelist">LockWhitelistedActivity</string> + <string name="title_activity_always">LockAtLaunchActivity</string> + <string name="launch_default">android:lockTaskMode=\n + \"lockTaskModeDefault\"\n + Pinnable from Overview.</string> + <string name="launch_never">android:lockTaskMode=\n + \"lockTaskModeNever\"\n + Not Lockable or Pinnable.</string> + <string name="launch_whitelist">android:lockTaskMode=\n\"lockTaskModeIfWhitelisted\"\n + Lockable if whitelisted, Pinnable.\n + Use SampleDeviceOwner app to set whitelist.</string> + <string name="launch_always">android:lockTaskMode=\n + \"lockTaskModeAlways\"\n + Launches into lock mode.</string> + <string name="launch_main">launch MainActivity (as activity)"</string> + <string name="try_lock">Call startLockMode()</string> + <string name="try_unlock">Call stopLockMode()</string> + +</resources> diff --git a/tests/LockTaskTests/res/values/styles.xml b/tests/LockTaskTests/res/values/styles.xml new file mode 100644 index 000000000000..6ce89c7ba439 --- /dev/null +++ b/tests/LockTaskTests/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/tests/LockTaskTests/src/com/google/android/example/locktasktests/LaunchActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LaunchActivity.java new file mode 100644 index 000000000000..1fc53cbaec37 --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LaunchActivity.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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.google.android.example.locktasktests; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; + +public class LaunchActivity extends Activity { + + EditText mEditText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_launch); + setBackgroundOnLockTaskMode(); + } + + @Override + public void onResume() { + super.onResume(); + setBackgroundOnLockTaskMode(); + } + + public void onTryLock(View view) { + startLockTask(); + setBackgroundOnLockTaskMode(); + } + + public void onTryUnlock(View view) { + stopLockTask(); + setBackgroundOnLockTaskMode(); + } + + public void onLaunchMain(View view) { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + } + + private void setBackgroundOnLockTaskMode() { + ActivityManager activityManager = + (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + final int color = + activityManager.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE ? + 0xFFFFC0C0 : 0xFFFFFFFF; + findViewById(R.id.root_launch).setBackgroundColor(color); + } +} diff --git a/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockAtLaunchActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockAtLaunchActivity.java new file mode 100644 index 000000000000..4390c945aa77 --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockAtLaunchActivity.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 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.google.android.example.locktasktests; + +public class LockAtLaunchActivity extends LaunchActivity { +} diff --git a/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockDefaultActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockDefaultActivity.java new file mode 100644 index 000000000000..7e57ab7500f8 --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockDefaultActivity.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 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.google.android.example.locktasktests; + +public class LockDefaultActivity extends LaunchActivity { +} diff --git a/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockTaskNeverActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockTaskNeverActivity.java new file mode 100644 index 000000000000..69c2cbc626d6 --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockTaskNeverActivity.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 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.google.android.example.locktasktests; + +public class LockTaskNeverActivity extends LaunchActivity { +} diff --git a/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockWhitelistedActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockWhitelistedActivity.java new file mode 100644 index 000000000000..387baa2dd309 --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/LockWhitelistedActivity.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 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.google.android.example.locktasktests; + +public class LockWhitelistedActivity extends LaunchActivity { +} diff --git a/tests/LockTaskTests/src/com/google/android/example/locktasktests/MainActivity.java b/tests/LockTaskTests/src/com/google/android/example/locktasktests/MainActivity.java new file mode 100644 index 000000000000..82fac036ab2a --- /dev/null +++ b/tests/LockTaskTests/src/com/google/android/example/locktasktests/MainActivity.java @@ -0,0 +1,58 @@ + +package com.google.android.example.locktasktests; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +public class MainActivity extends Activity { + + private final static String TAG = "LockTaskTests"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + setBackgroundOnLockTaskMode(); + } + + @Override + public void onResume() { + super.onResume(); + setBackgroundOnLockTaskMode(); + } + + public void onButtonPressed(View v) { + Class activity = null; + switch (v.getId()) { + case R.id.button_default: + activity = LockDefaultActivity.class; + break; + case R.id.button_never: + activity = LockTaskNeverActivity.class; + break; + case R.id.button_whitelist: + activity = LockWhitelistedActivity.class; + break; + case R.id.button_always: + activity = LockAtLaunchActivity.class; + break; + } + Intent intent = new Intent(this, activity); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + private void setBackgroundOnLockTaskMode() { + ActivityManager activityManager = + (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + final int color = + activityManager.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE ? + 0xFFFFC0C0 : 0xFFFFFFFF; + findViewById(R.id.root_launch).setBackgroundColor(color); + } +} diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 0622dc6288c1..05034c395e3a 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -27,6 +27,7 @@ main := Main.cpp sources := \ BigBuffer.cpp \ BinaryResourceParser.cpp \ + BinaryXmlPullParser.cpp \ BindingXmlPullParser.cpp \ ConfigDescription.cpp \ Files.cpp \ @@ -51,7 +52,9 @@ sources := \ ScopedXmlPullParser.cpp \ SourceXmlPullParser.cpp \ XliffXmlPullParser.cpp \ - XmlFlattener.cpp + XmlFlattener.cpp \ + ZipEntry.cpp \ + ZipFile.cpp testSources := \ BigBuffer_test.cpp \ @@ -63,6 +66,7 @@ testSources := \ Locale_test.cpp \ ManifestParser_test.cpp \ Maybe_test.cpp \ + NameMangler_test.cpp \ ResourceParser_test.cpp \ Resource_test.cpp \ ResourceTable_test.cpp \ diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp index 3eb96bcaaa57..71016c18e974 100644 --- a/tools/aapt2/BinaryResourceParser.cpp +++ b/tools/aapt2/BinaryResourceParser.cpp @@ -17,6 +17,7 @@ #include "BinaryResourceParser.h" #include "Logger.h" #include "ResChunkPullParser.h" +#include "Resolver.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceTypeExtensions.h" @@ -33,28 +34,14 @@ namespace aapt { using namespace android; -template <typename T> -inline static const T* convertTo(const ResChunk_header* chunk) { - if (chunk->headerSize < sizeof(T)) { - return nullptr; - } - return reinterpret_cast<const T*>(chunk); -} - -inline static const uint8_t* getChunkData(const ResChunk_header& chunk) { - return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize; -} - -inline static size_t getChunkDataLen(const ResChunk_header& chunk) { - return chunk.size - chunk.headerSize; -} - /* * Visitor that converts a reference's resource ID to a resource name, * given a mapping from resource ID to resource name. */ struct ReferenceIdToNameVisitor : ValueVisitor { - ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>& cache) : mCache(cache) { + ReferenceIdToNameVisitor(const std::shared_ptr<Resolver>& resolver, + std::map<ResourceId, ResourceName>* cache) : + mResolver(resolver), mCache(cache) { } void visit(Reference& reference, ValueVisitorArgs&) override { @@ -104,24 +91,39 @@ private: return; } - auto cacheIter = mCache.find(reference.id); - if (cacheIter == std::end(mCache)) { - Logger::note() << "failed to find " << reference.id << std::endl; - } else { + auto cacheIter = mCache->find(reference.id); + if (cacheIter != mCache->end()) { reference.name = cacheIter->second; reference.id = 0; + } else { + const android::ResTable& table = mResolver->getResTable(); + android::ResTable::resource_name resourceName; + if (table.getResourceName(reference.id.id, false, &resourceName)) { + const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, + resourceName.typeLen)); + assert(type); + reference.name.package.assign(resourceName.package, resourceName.packageLen); + reference.name.type = *type; + reference.name.entry.assign(resourceName.name, resourceName.nameLen); + reference.id = 0; + + // Add to cache. + mCache->insert({reference.id, reference.name}); + } } } - const std::map<ResourceId, ResourceName>& mCache; + std::shared_ptr<Resolver> mResolver; + std::map<ResourceId, ResourceName>* mCache; }; -BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table, +BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver, const Source& source, const void* data, size_t len) : - mTable(table), mSource(source), mData(data), mDataLen(len) { + mTable(table), mResolver(resolver), mSource(source), mData(data), mDataLen(len) { } bool BinaryResourceParser::parse() { @@ -421,7 +423,7 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { // Now go through the table and change resource ID references to // symbolic references. - ReferenceIdToNameVisitor visitor(mIdIndex); + ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex); for (auto& type : *mTable) { for (auto& entry : type->entries) { for (auto& configValue : entry->values) { @@ -676,7 +678,8 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0; + std::unique_ptr<Style> style = util::make_unique<Style>(isWeak); if (map->parent.ident == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. @@ -759,7 +762,17 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa const ResTable_map_entry* map) { std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); for (const ResTable_map& mapEntry : map) { - styleable->entries.emplace_back(mapEntry.name.ident); + if (mapEntry.name.ident == 0) { + // The map entry's key (attribute) is not set. This must be + // a symbol reference, so resolve it. + ResourceNameRef symbol; + bool result = getSymbol(&mapEntry.name.ident, &symbol); + assert(result); + styleable->entries.emplace_back(symbol); + } else { + // The map entry's key (attribute) is a regular reference. + styleable->entries.emplace_back(mapEntry.name.ident); + } } return styleable; } diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h index 92680786d9eb..f95a0c8b6a68 100644 --- a/tools/aapt2/BinaryResourceParser.h +++ b/tools/aapt2/BinaryResourceParser.h @@ -17,6 +17,7 @@ #ifndef AAPT_BINARY_RESOURCE_PARSER_H #define AAPT_BINARY_RESOURCE_PARSER_H +#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Source.h" @@ -41,7 +42,9 @@ public: * Creates a parser, which will read `len` bytes from `data`, and * add any resources parsed to `table`. `source` is for logging purposes. */ - BinaryResourceParser(std::shared_ptr<ResourceTable> table, const Source& source, + BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver, + const Source& source, const void* data, size_t len); BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. @@ -89,6 +92,8 @@ private: std::shared_ptr<ResourceTable> mTable; + std::shared_ptr<Resolver> mResolver; + const Source mSource; const void* mData; diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp new file mode 100644 index 000000000000..7a07c06c41e1 --- /dev/null +++ b/tools/aapt2/BinaryXmlPullParser.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "BinaryXmlPullParser.h" + +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { + +static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) { + switch (code) { + case android::ResXMLParser::START_DOCUMENT: + return XmlPullParser::Event::kStartDocument; + case android::ResXMLParser::END_DOCUMENT: + return XmlPullParser::Event::kEndDocument; + case android::ResXMLParser::START_NAMESPACE: + return XmlPullParser::Event::kStartNamespace; + case android::ResXMLParser::END_NAMESPACE: + return XmlPullParser::Event::kEndNamespace; + case android::ResXMLParser::START_TAG: + return XmlPullParser::Event::kStartElement; + case android::ResXMLParser::END_TAG: + return XmlPullParser::Event::kEndElement; + case android::ResXMLParser::TEXT: + return XmlPullParser::Event::kText; + default: + break; + } + return XmlPullParser::Event::kBadDocument; +} + +BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser) + : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(), + mDepth(0) { +} + +XmlPullParser::Event BinaryXmlPullParser::next() { + mStr1.clear(); + mStr2.clear(); + mAttributes.clear(); + + android::ResXMLParser::event_code_t code; + if (mHasComment) { + mHasComment = false; + code = mParser->getEventType(); + } else { + code = mParser->next(); + if (code != android::ResXMLParser::BAD_DOCUMENT) { + size_t len; + const char16_t* comment = mParser->getComment(&len); + if (comment) { + mHasComment = true; + mStr1.assign(comment, len); + return XmlPullParser::Event::kComment; + } + } + } + + size_t len; + const char16_t* data; + mEvent = codeToEvent(code); + switch (mEvent) { + case Event::kStartNamespace: + case Event::kEndNamespace: + data = mParser->getNamespacePrefix(&len); + mStr1.assign(data, len); + data = mParser->getNamespaceUri(&len); + mStr2.assign(data, len); + break; + + case Event::kStartElement: + copyAttributes(); + // fallthrough + + case Event::kEndElement: + data = mParser->getElementNamespace(&len); + mStr1.assign(data, len); + data = mParser->getElementName(&len); + mStr2.assign(data, len); + break; + + case Event::kText: + data = mParser->getText(&len); + mStr1.assign(data, len); + break; + + default: + break; + } + return mEvent; +} + +XmlPullParser::Event BinaryXmlPullParser::getEvent() const { + if (mHasComment) { + return XmlPullParser::Event::kComment; + } + return mEvent; +} + +const std::string& BinaryXmlPullParser::getLastError() const { + return sEmpty8; +} + +const std::u16string& BinaryXmlPullParser::getComment() const { + if (mHasComment) { + return mStr1; + } + return sEmpty; +} + +size_t BinaryXmlPullParser::getLineNumber() const { + return mParser->getLineNumber(); +} + +size_t BinaryXmlPullParser::getDepth() const { + return mDepth; +} + +const std::u16string& BinaryXmlPullParser::getText() const { + if (!mHasComment && mEvent == XmlPullParser::Event::kText) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || + mEvent == XmlPullParser::Event::kEndNamespace)) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getNamespaceUri() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || + mEvent == XmlPullParser::Event::kEndNamespace)) { + return mStr2; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getElementNamespace() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || + mEvent == XmlPullParser::Event::kEndElement)) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getElementName() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || + mEvent == XmlPullParser::Event::kEndElement)) { + return mStr2; + } + return sEmpty; +} + +size_t BinaryXmlPullParser::getAttributeCount() const { + return mAttributes.size(); +} + +XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const { + return mAttributes.begin(); +} + +XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const { + return mAttributes.end(); +} + +void BinaryXmlPullParser::copyAttributes() { + const size_t attrCount = mParser->getAttributeCount(); + if (attrCount > 0) { + mAttributes.reserve(attrCount); + for (size_t i = 0; i < attrCount; i++) { + XmlPullParser::Attribute attr; + size_t len; + const char16_t* str = mParser->getAttributeNamespace(i, &len); + attr.namespaceUri.assign(str, len); + str = mParser->getAttributeName(i, &len); + attr.name.assign(str, len); + str = mParser->getAttributeStringValue(i, &len); + attr.value.assign(str, len); + mAttributes.push_back(std::move(attr)); + } + } +} + +} // namespace aapt diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h new file mode 100644 index 000000000000..2d4256a17496 --- /dev/null +++ b/tools/aapt2/BinaryXmlPullParser.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_BINARY_XML_PULL_PARSER_H +#define AAPT_BINARY_XML_PULL_PARSER_H + +#include "XmlPullParser.h" + +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { + +/** + * Wraps a ResTable into the canonical XmlPullParser interface. + */ +class BinaryXmlPullParser : public XmlPullParser { +public: + BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser); + BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete; + + Event getEvent() const; + const std::string& getLastError() const; + Event next(); + + const std::u16string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; + + const std::u16string& getText() const; + + const std::u16string& getNamespacePrefix() const; + const std::u16string& getNamespaceUri() const; + + const std::u16string& getElementNamespace() const; + const std::u16string& getElementName() const; + + const_iterator beginAttributes() const; + const_iterator endAttributes() const; + size_t getAttributeCount() const; + +private: + void copyAttributes(); + + std::shared_ptr<android::ResXMLTree> mParser; + std::u16string mStr1; + std::u16string mStr2; + std::vector<Attribute> mAttributes; + Event mEvent; + bool mHasComment; + const std::u16string sEmpty; + const std::string sEmpty8; + size_t mDepth; +}; + +} // namespace aapt + +#endif // AAPT_BINARY_XML_PULL_PARSER_H diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp index b1ee8e7bcd4c..3b2ff510bb62 100644 --- a/tools/aapt2/Flag.cpp +++ b/tools/aapt2/Flag.cpp @@ -16,6 +16,7 @@ struct Flag { std::function<void(const StringPiece&)> action; bool required; bool* flagResult; + bool flagValueWhenSet; bool parsed; }; @@ -25,21 +26,22 @@ static std::vector<std::string> sArgs; void optionalFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action) { sFlags.push_back( - Flag{ name.toString(), description.toString(), action, false, nullptr, false }); + Flag{ name.toString(), description.toString(), action, false, nullptr, false, false }); } void requiredFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action) { sFlags.push_back( - Flag{ name.toString(), description.toString(), action, true, nullptr, false }); + Flag{ name.toString(), description.toString(), action, true, nullptr, false, false }); } -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) { - sFlags.push_back( - Flag{ name.toString(), description.toString(), {}, false, result, false }); +void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, + bool* result) { + sFlags.push_back(Flag{ + name.toString(), description.toString(), {}, false, result, resultWhenSet, false }); } -static void usageAndDie(const StringPiece& command) { +void usageAndDie(const StringPiece& command) { std::cerr << command << " [options]"; for (const Flag& flag : sFlags) { if (flag.required) { @@ -73,7 +75,7 @@ void parse(int argc, char** argv, const StringPiece& command) { match = true; flag.parsed = true; if (flag.flagResult) { - *flag.flagResult = true; + *flag.flagResult = flag.flagValueWhenSet; } else { i++; if (i >= argc) { diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h index 32f5f2cb61cb..4745c355d8cd 100644 --- a/tools/aapt2/Flag.h +++ b/tools/aapt2/Flag.h @@ -16,7 +16,10 @@ void requiredFlag(const StringPiece& name, const StringPiece& description, void optionalFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action); -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result); +void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, + bool* result); + +void usageAndDie(const StringPiece& command); void parse(int argc, char** argv, const StringPiece& command); diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp index 779a346f9289..3f92f18d29c1 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/JavaClassGenerator.cpp @@ -15,6 +15,7 @@ */ #include "JavaClassGenerator.h" +#include "NameMangler.h" #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -31,7 +32,7 @@ namespace aapt { // The number of attributes to emit per line in a Styleable array. constexpr size_t kAttribsPerLine = 4; -JavaClassGenerator::JavaClassGenerator(std::shared_ptr<const ResourceTable> table, +JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options) : mTable(table), mOptions(options) { } @@ -79,42 +80,18 @@ static std::u16string transform(const StringPiece16& symbol) { return output; } -bool JavaClassGenerator::generateType(std::ostream& out, const ResourceTableType& type, - size_t packageId) { - const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - - for (const auto& entry : type.entries) { - ResourceId id = { packageId, type.typeId, entry->entryId }; - assert(id.isValid()); - - if (!isValidSymbol(entry->name)) { - std::stringstream err; - err << "invalid symbol name '" - << StringPiece16(entry->name) - << "'"; - mError = err.str(); - return false; - } - - out << " " - << "public static" << finalModifier - << " int " << transform(entry->name) << " = " << id << ";" << std::endl; - } - return true; -} - struct GenArgs : ValueVisitorArgs { - GenArgs(std::ostream& o, const ResourceEntry& e) : out(o), entry(e) { + GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) { } - std::ostream& out; - const ResourceEntry& entry; + std::ostream* out; + std::u16string* entryName; }; void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) { const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - std::ostream& out = static_cast<GenArgs&>(a).out; - const ResourceEntry& entry = static_cast<GenArgs&>(a).entry; + std::ostream* out = static_cast<GenArgs&>(a).out; + std::u16string* entryName = static_cast<GenArgs&>(a).entryName; // This must be sorted by resource ID. std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes; @@ -127,59 +104,86 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) std::sort(sortedAttributes.begin(), sortedAttributes.end()); // First we emit the array containing the IDs of each attribute. - out << " " - << "public static final int[] " << transform(entry.name) << " = {"; + *out << " " + << "public static final int[] " << transform(*entryName) << " = {"; const size_t attrCount = sortedAttributes.size(); for (size_t i = 0; i < attrCount; i++) { if (i % kAttribsPerLine == 0) { - out << std::endl << " "; + *out << std::endl << " "; } - out << sortedAttributes[i].first; + *out << sortedAttributes[i].first; if (i != attrCount - 1) { - out << ", "; + *out << ", "; } } - out << std::endl << " };" << std::endl; + *out << std::endl << " };" << std::endl; // Now we emit the indices into the array. for (size_t i = 0; i < attrCount; i++) { - out << " " - << "public static" << finalModifier - << " int " << transform(entry.name) << "_" << transform(sortedAttributes[i].second) - << " = " << i << ";" << std::endl; + *out << " " + << "public static" << finalModifier + << " int " << transform(*entryName) << "_" << transform(sortedAttributes[i].second) + << " = " << i << ";" << std::endl; } } -bool JavaClassGenerator::generate(std::ostream& out) { +bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId, + const ResourceTableType& type, std::ostream& out) { + const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; + + std::u16string unmangledPackage; + std::u16string unmangledName; + for (const auto& entry : type.entries) { + ResourceId id = { packageId, type.typeId, entry->entryId }; + assert(id.isValid()); + + unmangledName = entry->name; + if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) { + // The entry name was mangled, and we successfully unmangled it. + // Check that we want to emit this symbol. + if (package != unmangledPackage) { + // Skip the entry if it doesn't belong to the package we're writing. + continue; + } + } else { + if (package != mTable->getPackage()) { + // We are processing a mangled package name, + // but this is a non-mangled resource. + continue; + } + } + + if (!isValidSymbol(unmangledName)) { + ResourceNameRef resourceName = { package, type.type, unmangledName }; + std::stringstream err; + err << "invalid symbol name '" << resourceName << "'"; + mError = err.str(); + return false; + } + + if (type.type == ResourceType::kStyleable) { + assert(!entry->values.empty()); + entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName }); + } else { + out << " " << "public static" << finalModifier + << " int " << transform(unmangledName) << " = " << id << ";" << std::endl; + } + } + return true; +} + +bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) { const size_t packageId = mTable->getPackageId(); - generateHeader(out, mTable->getPackage()); + generateHeader(out, package); out << "public final class R {" << std::endl; for (const auto& type : *mTable) { out << " public static final class " << type->type << " {" << std::endl; - bool result; - if (type->type == ResourceType::kStyleable) { - for (const auto& entry : type->entries) { - assert(!entry->values.empty()); - if (!isValidSymbol(entry->name)) { - std::stringstream err; - err << "invalid symbol name '" - << StringPiece16(entry->name) - << "'"; - mError = err.str(); - return false; - } - entry->values.front().value->accept(*this, GenArgs{ out, *entry }); - } - } else { - result = generateType(out, *type, packageId); - } - - if (!result) { + if (!generateType(package, packageId, *type, out)) { return false; } out << " }" << std::endl; diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h index 5b8e50035a9c..f8b9ee3f1fc8 100644 --- a/tools/aapt2/JavaClassGenerator.h +++ b/tools/aapt2/JavaClassGenerator.h @@ -41,12 +41,16 @@ public: bool useFinal = true; }; - JavaClassGenerator(std::shared_ptr<const ResourceTable> table, Options options); + JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options); /* - * Writes the R.java file to `out`. Returns true on success. + * Writes the R.java file to `out`. Only symbols belonging to `package` are written. + * All symbols technically belong to a single package, but linked libraries will + * have their names mangled, denoting that they came from a different package. + * We need to generate these symbols in a separate file. + * Returns true on success. */ - bool generate(std::ostream& out); + bool generate(const std::u16string& package, std::ostream& out); /* * ConstValueVisitor implementation. @@ -56,7 +60,8 @@ public: const std::string& getError() const; private: - bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId); + bool generateType(const std::u16string& package, size_t packageId, + const ResourceTableType& type, std::ostream& out); std::shared_ptr<const ResourceTable> mTable; Options mOptions; diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index 32050e30fd8a..96bb10b9232c 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -15,6 +15,8 @@ */ #include "JavaClassGenerator.h" +#include "Linker.h" +#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Util.h" @@ -47,7 +49,7 @@ TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { JavaClassGenerator generator(mTable, {}); std::stringstream out; - EXPECT_FALSE(generator.generate(out)); + EXPECT_FALSE(generator.generate(mTable->getPackage(), out)); } TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { @@ -69,7 +71,7 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { JavaClassGenerator generator(mTable, {}); std::stringstream out; - EXPECT_TRUE(generator.generate(out)); + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); std::string output = out.str(); EXPECT_NE(std::string::npos, @@ -82,4 +84,33 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { output.find("public static final int hey_dude_cool_attr = 0;")); } +TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { + ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, + ResourceId{ 0x01, 0x02, 0x0000 })); + ResourceTable table; + table.setPackage(u"com.lib"); + ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {}, + SourceLine{ "lib.xml", 33 }, util::make_unique<Id>())); + ASSERT_TRUE(mTable->merge(std::move(table))); + + std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(mTable, + std::make_shared<const android::AssetManager>()); + Linker linker(mTable, resolver); + ASSERT_TRUE(linker.linkAndValidate()); + + JavaClassGenerator generator(mTable, {}); + + std::stringstream out; + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("int foo =")); + EXPECT_EQ(std::string::npos, output.find("int test =")); + + out.str(""); + EXPECT_TRUE(generator.generate(u"com.lib", out)); + output = out.str(); + EXPECT_NE(std::string::npos, output.find("int test =")); + EXPECT_EQ(std::string::npos, output.find("int foo =")); +} + } // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp index 1cfb2975b1dd..4346c8bb35b2 100644 --- a/tools/aapt2/Linker.cpp +++ b/tools/aapt2/Linker.cpp @@ -128,6 +128,20 @@ const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const { void Linker::visit(Reference& reference, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); + if (!reference.name.isValid()) { + // We can't have a completely bad reference. + assert(reference.id.isValid()); + + // This reference has no name but has an ID. + // It is a really bad error to have no name and have the same + // package ID. + assert(reference.id.packageId() != mTable->getPackageId()); + + // The reference goes outside this package, let it stay as a + // resource ID because it will not change. + return; + } + Maybe<ResourceId> result = mResolver->findId(reference.name); if (!result) { addUnresolvedSymbol(reference.name, args.source); @@ -206,7 +220,7 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine void Linker::visit(Style& style, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); - if (style.parent.name.isValid()) { + if (style.parent.name.isValid() || style.parent.id.isValid()) { visit(style.parent, a); } diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp index b1e201b3ec1b..4d2d360d441a 100644 --- a/tools/aapt2/Linker_test.cpp +++ b/tools/aapt2/Linker_test.cpp @@ -30,6 +30,7 @@ struct LinkerTest : public ::testing::Test { virtual void SetUp() override { mTable = std::make_shared<ResourceTable>(); mTable->setPackage(u"android"); + mTable->setPackageId(0x01); mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>( mTable, std::make_shared<android::AssetManager>())); @@ -75,7 +76,7 @@ TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) { } TEST_F(LinkerTest, EscapeAndConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123")) @@ -91,7 +92,7 @@ TEST_F(LinkerTest, EscapeAndConvertRawString) { } TEST_F(LinkerTest, FailToConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?")) @@ -103,7 +104,7 @@ TEST_F(LinkerTest, FailToConvertRawString) { } TEST_F(LinkerTest, ConvertRawStringToString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"string" }, util::make_unique<RawString>( @@ -122,7 +123,7 @@ TEST_F(LinkerTest, ConvertRawStringToString) { } TEST_F(LinkerTest, ConvertRawStringToFlags) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple")) @@ -140,4 +141,12 @@ TEST_F(LinkerTest, ConvertRawStringToFlags) { EXPECT_EQ(bin->value.data, 1u | 2u); } +TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) { + ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }, + util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 }))); + + ASSERT_TRUE(mLinker->linkAndValidate()); + EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); +} + } // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 87127fd3a75b..03b9ba4f86ce 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -17,6 +17,7 @@ #include "AppInfo.h" #include "BigBuffer.h" #include "BinaryResourceParser.h" +#include "BinaryXmlPullParser.h" #include "BindingXmlPullParser.h" #include "Files.h" #include "Flag.h" @@ -34,6 +35,7 @@ #include "TableFlattener.h" #include "Util.h" #include "XmlFlattener.h" +#include "ZipFile.h" #include <algorithm> #include <androidfw/AssetManager.h> @@ -44,8 +46,11 @@ #include <iostream> #include <sstream> #include <sys/stat.h> +#include <unordered_set> #include <utils/Errors.h> +constexpr const char* kAaptVersionStr = "2.0-alpha"; + using namespace aapt; void printTable(const ResourceTable& table) { @@ -96,17 +101,6 @@ void printStringPool(const StringPool& pool) { } } -std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename, - ResourceType type, const ConfigDescription& config) { - std::stringstream path; - path << "res/" << type; - if (config != ConfigDescription{}) { - path << "-" << config; - } - path << "/" << filename; - return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str()))); -} - /** * Collect files from 'root', filtering out any files that do not * match the FileFilter 'filter'. @@ -148,30 +142,6 @@ bool walkTree(const Source& root, const FileFilter& filter, return !error; } -bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) { - std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); - if (!ifs) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - std::streampos fsize = ifs.tellg(); - ifs.seekg(0, std::ios::end); - fsize = ifs.tellg() - fsize; - ifs.seekg(0, std::ios::beg); - - assert(fsize >= 0); - size_t dataSize = static_cast<size_t>(fsize); - char* buf = new char[dataSize]; - ifs.read(buf, dataSize); - - BinaryResourceParser parser(table, source, buf, dataSize); - bool result = parser.parse(); - - delete [] buf; - return result; -} - bool loadResTable(android::ResTable* table, const Source& source) { std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); if (!ifs) { @@ -195,7 +165,7 @@ bool loadResTable(android::ResTable* table, const Source& source) { return result; } -void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { +void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { for (auto& type : *table) { if (type->type != ResourceType::kStyle) { continue; @@ -251,10 +221,12 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { {}, // Create a copy of the original style. - std::unique_ptr<Value>(configValue.value->clone()) + std::unique_ptr<Value>(configValue.value->clone( + &table->getValueStringPool())) }; Style& newStyle = static_cast<Style&>(*value.value); + newStyle.weak = true; // Move the recorded stripped attributes into this new style. std::move(stripped.begin(), stripped.end(), @@ -285,88 +257,106 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { } } -bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source, - const ResourceName& name, const ConfigDescription& config) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; - return false; +struct CompileItem { + Source source; + ResourceName name; + ConfigDescription config; + std::string extension; +}; + +struct LinkItem { + Source source; + std::string apkPath; +}; + +std::string buildFileReference(const CompileItem& item) { + std::stringstream path; + path << "res/" << item.name.type; + if (item.config != ConfigDescription{}) { + path << "-" << item.config; } + path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension; + return path.str(); +} - std::set<size_t> sdkLevels; +bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) { + StringPool& pool = table->getValueStringPool(); + StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item))); + return table->addResource(item.name, item.config, item.source.line(0), + util::make_unique<FileReference>(ref)); +} - SourceXmlPullParser parser(in); - while (XmlPullParser::isGoodEvent(parser.next())) { - if (parser.getEvent() != XmlPullParser::Event::kStartElement) { - continue; - } +struct AaptOptions { + enum class Phase { + Link, + Compile, + }; - const auto endIter = parser.endAttributes(); - for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) { - if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") { - size_t sdkLevel = findAttributeSdkLevel(iter->name); - if (sdkLevel > 1) { - sdkLevels.insert(sdkLevel); - } - } + // The phase to process. + Phase phase; - ResourceNameRef refName; - bool create = false; - bool privateRef = false; - if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) && - create) { - table->addResource(refName, {}, source.line(parser.getLineNumber()), - util::make_unique<Id>()); - } - } - } + // Details about the app. + AppInfo appInfo; - for (size_t level : sdkLevels) { - Logger::note(source) - << "creating v" << level << " versioned file." - << std::endl; - ConfigDescription newConfig = config; - newConfig.sdkVersion = level; - - std::unique_ptr<FileReference> fileResource = makeFileReference( - table->getValueStringPool(), - util::utf16ToUtf8(name.entry) + ".xml", - name.type, - newConfig); - table->addResource(name, newConfig, source.line(0), std::move(fileResource)); - } - return true; -} + // The location of the manifest file. + Source manifest; -struct CompileItem { - Source source; - ResourceName name; - ConfigDescription config; - std::string extension; + // The APK files to link. + std::vector<Source> input; + + // The libraries these files may reference. + std::vector<Source> libraries; + + // Output path. This can be a directory or file + // depending on the phase. + Source output; + + // Directory in which to write binding xml files. + Source bindingOutput; + + // Directory to in which to generate R.java. + Maybe<Source> generateJavaClass; + + // Whether to output verbose details about + // compilation. + bool verbose = false; + + // Whether or not to auto-version styles or layouts + // referencing attributes defined in a newer SDK + // level than the style or layout is defined for. + bool versionStylesAndLayouts = true; }; -bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, - const Source& outputSource, std::queue<CompileItem>* queue) { + +bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) { std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { Logger::error(item.source) << strerror(errno) << std::endl; return false; } + BigBuffer outBuffer(1024); + + // No resolver, since we are not compiling attributes here. + XmlFlattener flattener(table, {}); + + XmlFlattener::Options xmlOptions; + if (options.versionStylesAndLayouts) { + // We strip attributes that do not belong in this version of the resource. + // Non-version qualified resources have an implicit version 1 requirement. + xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; + } + std::shared_ptr<BindingXmlPullParser> binding; - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); + std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in); if (item.name.type == ResourceType::kLayout) { - binding = std::make_shared<BindingXmlPullParser>(xmlParser); - xmlParser = binding; + // Layouts may have defined bindings, so we need to make sure they get processed. + binding = std::make_shared<BindingXmlPullParser>(parser); + parser = binding; } - BigBuffer outBuffer(1024); - XmlFlattener flattener(resolver); - - // We strip attributes that do not belong in this version of the resource. - // Non-version qualified resources have an implicit version 1 requirement. - XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 }; - Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options); + Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions); if (!minStrippedSdk) { return false; } @@ -376,24 +366,29 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, // with the version of the smallest SDK version stripped. CompileItem newWork = item; newWork.config.sdkVersion = minStrippedSdk.value(); - queue->push(newWork); + outQueue->push(newWork); } - std::ofstream out(outputSource.path, std::ofstream::binary); - if (!out) { - Logger::error(outputSource) << strerror(errno) << std::endl; + // Write the resulting compiled XML file to the output APK. + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk." + << std::endl; return false; } - if (!util::writeAll(out, outBuffer)) { - Logger::error(outputSource) << strerror(errno) << std::endl; - return false; - } + if (binding && !options.bindingOutput.path.empty()) { + // We generated a binding xml file, write it out. + Source bindingOutput = options.bindingOutput; + appendPath(&bindingOutput.path, buildFileReference(item)); + + if (!mkdirs(bindingOutput.path)) { + Logger::error(bindingOutput) << strerror(errno) << std::endl; + return false; + } + + appendPath(&bindingOutput.path, "bind.xml"); - if (binding) { - // We generated a binding xml file, write it out beside the output file. - Source bindingOutput = outputSource; - bindingOutput.path += ".bind.xml"; std::ofstream bout(bindingOutput.path); if (!bout) { Logger::error(bindingOutput) << strerror(errno) << std::endl; @@ -408,100 +403,68 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, return true; } -bool compilePng(const Source& source, const Source& output) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; +bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, + const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) { + std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); + if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { return false; } - std::ofstream out(output.path, std::ofstream::binary); - if (!out) { - Logger::error(output) << strerror(errno) << std::endl; + std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree); + + BigBuffer outBuffer(1024); + XmlFlattener flattener({}, resolver); + if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) { return false; } - std::string err; - Png png; - if (!png.process(source, in, out, {}, &err)) { - Logger::error(source) << err << std::endl; + if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) != + android::NO_ERROR) { + Logger::error(options.output) << "failed to write linked file '" << item.source + << "' to apk." << std::endl; return false; } return true; } -bool copyFile(const Source& source, const Source& output) { - std::ifstream in(source.path, std::ifstream::binary); +bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { + std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { - Logger::error(source) << strerror(errno) << std::endl; + Logger::error(item.source) << strerror(errno) << std::endl; return false; } - std::ofstream out(output.path, std::ofstream::binary); - if (!out) { - Logger::error(output) << strerror(errno) << std::endl; + BigBuffer outBuffer(4096); + std::string err; + Png png; + if (!png.process(item.source, in, &outBuffer, {}, &err)) { + Logger::error(item.source) << err << std::endl; return false; } - if (out << in.rdbuf()) { - Logger::error(output) << strerror(errno) << std::endl; - return true; + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write compiled '" << item.source + << "' to apk." << std::endl; + return false; } - return false; + return true; } -struct AaptOptions { - enum class Phase { - Full, - Collect, - Link, - Compile, - Manifest - }; - - // The phase to process. - Phase phase; - - // Details about the app. - AppInfo appInfo; - - // The location of the manifest file. - Source manifest; - - // The source directories to walk and find resource files. - std::vector<Source> sourceDirs; - - // The resource files to process and collect. - std::vector<Source> collectFiles; - - // The binary table files to link. - std::vector<Source> linkFiles; - - // The resource files to compile. - std::vector<Source> compileFiles; - - // The libraries these files may reference. - std::vector<Source> libraries; - - // Output path. This can be a directory or file - // depending on the phase. - Source output; - - // Directory to in which to generate R.java. - Maybe<Source> generateJavaClass; - - // Whether to output verbose details about - // compilation. - bool verbose = false; -}; - -bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, - const AaptOptions& options) { - Source outSource = options.output; - appendPath(&outSource.path, "AndroidManifest.xml"); +bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { + if (outApk->add(item.source.path.data(), buildFileReference(item).data(), + ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk." + << std::endl; + return false; + } + return true; +} +bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, + ZipFile* outApk) { if (options.verbose) { - Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl; + Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; } std::ifstream in(options.manifest.path, std::ifstream::binary); @@ -512,23 +475,16 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, BigBuffer outBuffer(1024); std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - XmlFlattener flattener(resolver); + XmlFlattener flattener({}, resolver); - Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer, - XmlFlattener::Options{}); - if (!result) { + if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) { return false; } - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]); - uint8_t* p = data.get(); - for (const auto& b : outBuffer) { - memcpy(p, b.buffer.get(), b.size); - p += b.size; - } + std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); android::ResXMLTree tree; - if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) { + if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) { return false; } @@ -537,14 +493,10 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, return false; } - std::ofstream out(outSource.path, std::ofstream::binary); - if (!out) { - Logger::error(outSource) << strerror(errno) << std::endl; - return false; - } - - if (!util::writeAll(out, outBuffer)) { - Logger::error(outSource) << strerror(errno) << std::endl; + if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml", + ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk." + << std::endl; return false; } return true; @@ -562,10 +514,20 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) { return parser.parse(source, pullParser, outInfo); } +static void printCommandsAndDie() { + std::cerr << "The following commands are supported:" << std::endl << std::endl; + std::cerr << "compile compiles a subset of resources" << std::endl; + std::cerr << "link links together compiled resources and libraries" << std::endl; + std::cerr << std::endl; + std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." + << std::endl; + exit(1); +} + static AaptOptions prepareArgs(int argc, char** argv) { if (argc < 2) { - std::cerr << "no command specified." << std::endl; - exit(1); + std::cerr << "no command specified." << std::endl << std::endl; + printCommandsAndDie(); } const StringPiece command(argv[1]); @@ -574,32 +536,32 @@ static AaptOptions prepareArgs(int argc, char** argv) { AaptOptions options; - StringPiece outputDescription = "place output in file"; - if (command == "package") { - options.phase = AaptOptions::Phase::Full; - outputDescription = "place output in directory"; - } else if (command == "collect") { - options.phase = AaptOptions::Phase::Collect; + if (command == "--version" || command == "version") { + std::cout << kAaptVersionStr << std::endl; + exit(0); } else if (command == "link") { options.phase = AaptOptions::Phase::Link; } else if (command == "compile") { options.phase = AaptOptions::Phase::Compile; - outputDescription = "place output in directory"; - } else if (command == "manifest") { - options.phase = AaptOptions::Phase::Manifest; - outputDescription = "place AndroidManifest.xml in directory"; } else { - std::cerr << "invalid command '" << command << "'." << std::endl; - exit(1); + std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; + printCommandsAndDie(); } - if (options.phase == AaptOptions::Phase::Full) { - flag::requiredFlag("-S", "add a directory in which to find resources", + if (options.phase == AaptOptions::Phase::Compile) { + flag::requiredFlag("--package", "Android package name", + [&options](const StringPiece& arg) { + options.appInfo.package = util::utf8ToUtf16(arg); + }); + flag::optionalFlag("--binding", "Output directory for binding XML files", [&options](const StringPiece& arg) { - options.sourceDirs.push_back(Source{ arg.toString() }); + options.bindingOutput = Source{ arg.toString() }; }); + flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", + false, &options.versionStylesAndLayouts); - flag::requiredFlag("-M", "path to AndroidManifest.xml", + } else if (options.phase == AaptOptions::Phase::Link) { + flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", [&options](const StringPiece& arg) { options.manifest = Source{ arg.toString() }; }); @@ -613,35 +575,16 @@ static AaptOptions prepareArgs(int argc, char** argv) { [&options](const StringPiece& arg) { options.generateJavaClass = Source{ arg.toString() }; }); - - } else { - if (options.phase != AaptOptions::Phase::Manifest) { - flag::requiredFlag("--package", "Android package name", - [&options](const StringPiece& arg) { - options.appInfo.package = util::utf8ToUtf16(arg); - }); - } - - if (options.phase != AaptOptions::Phase::Collect) { - flag::optionalFlag("-I", "add an Android APK to link against", - [&options](const StringPiece& arg) { - options.libraries.push_back(Source{ arg.toString() }); - }); - } - - if (options.phase == AaptOptions::Phase::Link) { - flag::optionalFlag("--java", "directory in which to generate R.java", - [&options](const StringPiece& arg) { - options.generateJavaClass = Source{ arg.toString() }; - }); - } } // Common flags for all steps. - flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) { + flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { options.output = Source{ arg.toString() }; }); - flag::optionalSwitch("-v", "enables verbose logging", &options.verbose); + + bool help = false; + flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose); + flag::optionalSwitch("-h", "displays this help menu", true, &help); // Build the command string for output (eg. "aapt2 compile"). std::string fullCommand = "aapt2"; @@ -651,28 +594,18 @@ static AaptOptions prepareArgs(int argc, char** argv) { // Actually read the command line flags. flag::parse(argc, argv, fullCommand); + if (help) { + flag::usageAndDie(fullCommand); + } + // Copy all the remaining arguments. - if (options.phase == AaptOptions::Phase::Collect) { - for (const std::string& arg : flag::getArgs()) { - options.collectFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Compile) { - for (const std::string& arg : flag::getArgs()) { - options.compileFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Link) { - for (const std::string& arg : flag::getArgs()) { - options.linkFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Manifest) { - if (!flag::getArgs().empty()) { - options.manifest = Source{ flag::getArgs()[0] }; - } + for (const std::string& arg : flag::getArgs()) { + options.input.push_back(Source{ arg }); } return options; } -static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source, +static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source, const ConfigDescription& config) { std::ifstream in(source.path, std::ifstream::binary); if (!in) { @@ -738,115 +671,91 @@ static Maybe<ResourcePathData> extractResourcePathData(const Source& source) { }; } -bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver) { - const bool versionStyles = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Link); - const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Link); - const bool compileFiles = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Compile); - const bool flattenTable = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Collect || - options->phase == AaptOptions::Phase::Link); - const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect; - - // Build the output table path. - Source outputTable = options->output; - if (options->phase == AaptOptions::Phase::Full) { - appendPath(&outputTable.path, "resources.arsc"); - } - - bool error = false; - std::queue<CompileItem> compileQueue; +bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const TableFlattener::Options& flattenerOptions, ZipFile* outApk) { + if (table->begin() != table->end()) { + BigBuffer buffer(1024); + TableFlattener flattener(flattenerOptions); + if (!flattener.flatten(&buffer, *table)) { + Logger::error() << "failed to flatten resource table." << std::endl; + return false; + } - // If source directories were specified, walk them looking for resource files. - if (!options->sourceDirs.empty()) { - const char* customIgnore = getenv("ANDROID_AAPT_IGNORE"); - FileFilter fileFilter; - if (customIgnore && customIgnore[0]) { - fileFilter.setPattern(customIgnore); - } else { - fileFilter.setPattern( - "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"); + if (options.verbose) { + Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) + << std::endl; } - for (const Source& source : options->sourceDirs) { - if (!walkTree(source, fileFilter, &options->collectFiles)) { - return false; - } + if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) != + android::NO_ERROR) { + Logger::note(options.output) << "failed to store resource table." << std::endl; + return false; } } + return true; +} - // Load all binary resource tables. - for (const Source& source : options->linkFiles) { - error |= !loadBinaryResourceTable(table, source); - } +static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate | + ZipFile::kOpenReadWrite; - if (error) { - return false; - } +bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, + const std::shared_ptr<Resolver>& resolver) { + std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles; + std::unordered_set<std::u16string> linkedPackages; - // Collect all the resource files. - // Need to parse the resource type/config/filename. - for (const Source& source : options->collectFiles) { - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { + // Populate the linkedPackages with our own. + linkedPackages.insert(options.appInfo.package); + + // Load all APK files. + for (const Source& source : options.input) { + std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); + if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { + Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; return false; } - const ResourcePathData& pathData = maybePathData.value(); - if (pathData.resourceDir == u"values") { - if (options->verbose) { - Logger::note(source) << "collecting values..." << std::endl; - } - - error |= !collectValues(table, source, pathData.config); - continue; - } + std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." - << std::endl; + ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); + if (!entry) { + Logger::error(source) << "missing 'resources.arsc'." << std::endl; return false; } - ResourceName resourceName = { table->getPackage(), *type, pathData.name }; + void* uncompressedData = zipFile->uncompress(entry); + assert(uncompressedData); - // Add the file name to the resource table. - std::unique_ptr<FileReference> fileReference = makeFileReference( - table->getValueStringPool(), - util::utf16ToUtf8(pathData.name) + "." + pathData.extension, - *type, pathData.config); - error |= !table->addResource(resourceName, pathData.config, source.line(0), - std::move(fileReference)); - - if (pathData.extension == "xml") { - error |= !collectXml(table, source, resourceName, pathData.config); + BinaryResourceParser parser(table, resolver, source, uncompressedData, + entry->getUncompressedLen()); + if (!parser.parse()) { + free(uncompressedData); + return false; } + free(uncompressedData); - compileQueue.push( - CompileItem{ source, resourceName, pathData.config, pathData.extension }); - } + // Keep track of where this table came from. + apkFiles[table] = std::move(zipFile); - if (error) { - return false; + // Add the package to the set of linked packages. + linkedPackages.insert(table->getPackage()); } - // Version all styles referencing attributes outside of their specified SDK version. - if (versionStyles) { - versionStylesForCompat(table); - } + for (auto& p : apkFiles) { + const std::shared_ptr<ResourceTable>& inTable = p.first; - // Verify that all references are valid. - Linker linker(table, resolver); - if (!linker.linkAndValidate()) { - return false; + if (!outTable->merge(std::move(*inTable))) { + return false; + } } - // Verify that all symbols exist. - if (verifyNoMissingSymbols) { + { + // Now that everything is merged, let's link it. + Linker linker(outTable, resolver); + if (!linker.linkAndValidate()) { + return false; + } + + // Verify that all symbols exist. const auto& unresolvedRefs = linker.getUnresolvedReferences(); if (!unresolvedRefs.empty()) { for (const auto& entry : unresolvedRefs) { @@ -859,143 +768,192 @@ bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table, } } - // Compile files. - if (compileFiles) { - // First process any input compile files. - for (const Source& source : options->compileFiles) { - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { - return false; - } + // Open the output APK file for writing. + ZipFile outApk; + if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { + Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; + return false; + } - const ResourcePathData& pathData = maybePathData.value(); - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir - << "'." << std::endl; - return false; + if (!compileManifest(options, resolver, &outApk)) { + return false; + } + + for (auto& p : apkFiles) { + std::unique_ptr<ZipFile>& zipFile = p.second; + + // TODO(adamlesinski): Get list of files to read when processing config filter. + + const int numEntries = zipFile->getNumEntries(); + for (int i = 0; i < numEntries; i++) { + ZipEntry* entry = zipFile->getEntryByIndex(i); + assert(entry); + + StringPiece filename = entry->getFileName(); + if (!util::stringStartsWith<char>(filename, "res/")) { + continue; } - ResourceName resourceName = { table->getPackage(), *type, pathData.name }; - compileQueue.push( - CompileItem{ source, resourceName, pathData.config, pathData.extension }); - } + if (util::stringEndsWith<char>(filename, ".xml")) { + void* uncompressedData = zipFile->uncompress(entry); + assert(uncompressedData); - // Now process the actual compile queue. - for (; !compileQueue.empty(); compileQueue.pop()) { - const CompileItem& item = compileQueue.front(); + LinkItem item = { Source{ filename.toString() }, filename.toString() }; - // Create the output directory path from the resource type and config. - std::stringstream outputPath; - outputPath << item.name.type; - if (item.config != ConfigDescription{}) { - outputPath << "-" << item.config.toString(); + if (!linkXml(options, resolver, item, uncompressedData, + entry->getUncompressedLen(), &outApk)) { + Logger::error(options.output) << "failed to link '" << filename << "'." + << std::endl; + return false; + } + } else { + if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to copy '" << filename << "'." + << std::endl; + return false; + } } + } + } - Source outSource = options->output; - appendPath(&outSource.path, "res"); - appendPath(&outSource.path, outputPath.str()); + // Generate the Java class file. + if (options.generateJavaClass) { + JavaClassGenerator generator(outTable, {}); - // Make the directory. - if (!mkdirs(outSource.path)) { - Logger::error(outSource) << strerror(errno) << std::endl; - return false; + for (const std::u16string& package : linkedPackages) { + Source outPath = options.generateJavaClass.value(); + + // Build the output directory from the package name. + // Eg. com.android.app -> com/android/app + const std::string packageUtf8 = util::utf16ToUtf8(package); + for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { + appendPath(&outPath.path, part); } - // Add the file name to the directory path. - appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension); + if (!mkdirs(outPath.path)) { + Logger::error(outPath) << strerror(errno) << std::endl; + return false; + } - if (item.extension == "xml") { - if (options->verbose) { - Logger::note(outSource) << "compiling XML file." << std::endl; - } + appendPath(&outPath.path, "R.java"); - error |= !compileXml(resolver, item, outSource, &compileQueue); - } else if (item.extension == "png" || item.extension == "9.png") { - if (options->verbose) { - Logger::note(outSource) << "compiling png file." << std::endl; - } + if (options.verbose) { + Logger::note(outPath) << "writing Java symbols." << std::endl; + } - error |= !compilePng(item.source, outSource); - } else { - error |= !copyFile(item.source, outSource); + std::ofstream fout(outPath.path); + if (!fout) { + Logger::error(outPath) << strerror(errno) << std::endl; + return false; } - } - if (error) { - return false; + if (!generator.generate(package, fout)) { + Logger::error(outPath) << generator.getError() << "." << std::endl; + return false; + } } } - // Compile and validate the AndroidManifest.xml. - if (!options->manifest.path.empty()) { - if (!compileAndroidManifest(resolver, *options)) { - return false; - } + // Flatten the resource table. + TableFlattener::Options flattenerOptions; + flattenerOptions.useExtendedChunks = false; + if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { + return false; } - // Generate the Java class file. - if (options->generateJavaClass) { - Source outPath = options->generateJavaClass.value(); - if (options->verbose) { - Logger::note() << "writing symbols to " << outPath << "." << std::endl; - } + outApk.flush(); + return true; +} - // Build the output directory from the package name. - // Eg. com.android.app -> com/android/app - const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage()); - for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { - appendPath(&outPath.path, part); - } +bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver) { + std::queue<CompileItem> compileQueue; + bool error = false; - if (!mkdirs(outPath.path)) { - Logger::error(outPath) << strerror(errno) << std::endl; + // Compile all the resource files passed in on the command line. + for (const Source& source : options.input) { + // Need to parse the resource type/config/filename. + Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); + if (!maybePathData) { return false; } - appendPath(&outPath.path, "R.java"); + const ResourcePathData& pathData = maybePathData.value(); + if (pathData.resourceDir == u"values") { + // The file is in the values directory, which means its contents will + // go into the resource table. + if (options.verbose) { + Logger::note(source) << "compiling values." << std::endl; + } - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } + error |= !compileValues(table, source, pathData.config); + } else { + // The file is in a directory like 'layout' or 'drawable'. Find out + // the type. + const ResourceType* type = parseResourceType(pathData.resourceDir); + if (!type) { + Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." + << std::endl; + return false; + } - JavaClassGenerator generator(table, {}); - if (!generator.generate(fout)) { - Logger::error(outPath) << generator.getError() << "." << std::endl; - return false; + compileQueue.push(CompileItem{ + source, + ResourceName{ table->getPackage(), *type, pathData.name }, + pathData.config, + pathData.extension + }); } } - // Flatten the resource table. - if (flattenTable && table->begin() != table->end()) { - BigBuffer buffer(1024); - TableFlattener::Options tableOptions; - tableOptions.useExtendedChunks = useExtendedChunks; - TableFlattener flattener(tableOptions); - if (!flattener.flatten(&buffer, *table)) { - Logger::error() << "failed to flatten resource table." << std::endl; - return false; - } + if (error) { + return false; + } - if (options->verbose) { - Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) - << std::endl; - } + // Version all styles referencing attributes outside of their specified SDK version. + if (options.versionStylesAndLayouts) { + versionStylesForCompat(table); + } - std::ofstream fout(outputTable.path, std::ofstream::binary); - if (!fout) { - Logger::error(outputTable) << strerror(errno) << "." << std::endl; - return false; - } + // Open the output APK file for writing. + ZipFile outApk; + if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { + Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; + return false; + } - if (!util::writeAll(fout, buffer)) { - Logger::error(outputTable) << strerror(errno) << "." << std::endl; - return false; + // Compile each file. + for (; !compileQueue.empty(); compileQueue.pop()) { + const CompileItem& item = compileQueue.front(); + + // Add the file name to the resource table. + error |= !addFileReference(table, item); + + if (item.extension == "xml") { + error |= !compileXml(options, table, item, &compileQueue, &outApk); + } else if (item.extension == "png" || item.extension == "9.png") { + error |= !compilePng(options, item, &outApk); + } else { + error |= !copyFile(options, item, &outApk); } - fout.flush(); } + + if (error) { + return false; + } + + // Link and assign resource IDs. + Linker linker(table, resolver); + if (!linker.linkAndValidate()) { + return false; + } + + // Flatten the resource table. + if (!writeResourceTable(options, table, {}, &outApk)) { + return false; + } + + outApk.flush(); return true; } @@ -1057,10 +1015,16 @@ int main(int argc, char** argv) { // Make the resolver that will cache IDs for us. std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries); - // Do the work. - if (!doAll(&options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; + if (options.phase == AaptOptions::Phase::Compile) { + if (!compile(options, table, resolver)) { + Logger::error() << "aapt exiting with failures." << std::endl; + return 1; + } + } else if (options.phase == AaptOptions::Phase::Link) { + if (!link(options, table, resolver)) { + Logger::error() << "aapt exiting with failures." << std::endl; + return 1; + } } return 0; } diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h new file mode 100644 index 000000000000..1e15e2071e65 --- /dev/null +++ b/tools/aapt2/NameMangler.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_NAME_MANGLER_H +#define AAPT_NAME_MANGLER_H + +#include <string> + +namespace aapt { + +struct NameMangler { + /** + * Mangles the name in `outName` with the `package` and stores the mangled + * result in `outName`. The mangled name should contain symbols that are + * illegal to define in XML, so that there will never be name mangling + * collisions. + */ + static void mangle(const std::u16string& package, std::u16string* outName) { + *outName = package + u"$" + *outName; + } + + /** + * Unmangles the name in `outName`, storing the correct name back in `outName` + * and the package in `outPackage`. Returns true if the name was unmangled or + * false if the name was never mangled to begin with. + */ + static bool unmangle(std::u16string* outName, std::u16string* outPackage) { + size_t pivot = outName->find(u'$'); + if (pivot == std::string::npos) { + return false; + } + + outPackage->assign(outName->data(), pivot); + outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1)); + return true; + } +}; + +} // namespace aapt + +#endif // AAPT_NAME_MANGLER_H diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp new file mode 100644 index 000000000000..6103655c15e0 --- /dev/null +++ b/tools/aapt2/NameMangler_test.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "NameMangler.h" + +#include <gtest/gtest.h> +#include <string> + +namespace aapt { + +TEST(NameManglerTest, MangleName) { + std::u16string package = u"android.appcompat"; + std::u16string name = u"Platform.AppCompat"; + + NameMangler::mangle(package, &name); + EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat"); + + std::u16string newPackage; + ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage)); + EXPECT_EQ(name, u"Platform.AppCompat"); + EXPECT_EQ(newPackage, u"android.appcompat"); +} + +TEST(NameManglerTest, IgnoreUnmangledName) { + std::u16string package; + std::u16string name = u"foo_bar"; + + EXPECT_FALSE(NameMangler::unmangle(&name, &package)); + EXPECT_EQ(name, u"foo_bar"); +} + +} // namespace aapt diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp index 76120ac24601..4e9b68e8c95f 100644 --- a/tools/aapt2/Png.cpp +++ b/tools/aapt2/Png.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "BigBuffer.h" #include "Logger.h" #include "Png.h" #include "Source.h" @@ -85,17 +86,12 @@ static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t l } static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { - std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr)); - if (!output->write(reinterpret_cast<const char*>(data), length)) { - png_error(writePtr, strerror(errno)); - } + BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); + png_bytep buf = outBuffer->nextBlock<png_byte>(length); + memcpy(buf, data, length); } -static void flushDataToStream(png_structp writePtr) { - std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr)); - if (!output->flush()) { - png_error(writePtr, strerror(errno)); - } +static void flushDataToStream(png_structp /*writePtr*/) { } static void logWarning(png_structp readPtr, png_const_charp warningMessage) { @@ -1196,7 +1192,7 @@ getout: } -bool Png::process(const Source& source, std::istream& input, std::ostream& output, +bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer, const Options& options, std::string* outError) { png_byte signature[kPngSignatureSize]; @@ -1262,7 +1258,7 @@ bool Png::process(const Source& source, std::istream& input, std::ostream& outpu png_set_error_fn(writePtr, nullptr, nullptr, logWarning); // Set the write function to write to std::ostream. - png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream); + png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger, outError)) { diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h index bc8075424abc..4577ab89d2d9 100644 --- a/tools/aapt2/Png.h +++ b/tools/aapt2/Png.h @@ -17,6 +17,7 @@ #ifndef AAPT_PNG_H #define AAPT_PNG_H +#include "BigBuffer.h" #include "Source.h" #include <iostream> @@ -29,7 +30,7 @@ struct Png { int grayScaleTolerance = 0; }; - bool process(const Source& source, std::istream& input, std::ostream& output, + bool process(const Source& source, std::istream& input, BigBuffer* outBuffer, const Options& options, std::string* outError); }; diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h index 7366c89968df..1426ed23a5c7 100644 --- a/tools/aapt2/ResChunkPullParser.h +++ b/tools/aapt2/ResChunkPullParser.h @@ -74,6 +74,22 @@ private: std::string mLastError; }; +template <typename T> +inline static const T* convertTo(const android::ResChunk_header* chunk) { + if (chunk->headerSize < sizeof(T)) { + return nullptr; + } + return reinterpret_cast<const T*>(chunk); +} + +inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) { + return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize; +} + +inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) { + return chunk.size - chunk.headerSize; +} + // // Implementation // diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/Resolver.cpp index 93b5e98b1359..ae006abba66d 100644 --- a/tools/aapt2/Resolver.cpp +++ b/tools/aapt2/Resolver.cpp @@ -15,6 +15,7 @@ */ #include "Maybe.h" +#include "NameMangler.h" #include "Resolver.h" #include "Resource.h" #include "ResourceTable.h" @@ -31,6 +32,12 @@ namespace aapt { Resolver::Resolver(std::shared_ptr<const ResourceTable> table, std::shared_ptr<const android::AssetManager> sources) : mTable(table), mSources(sources) { + const android::ResTable& resTable = mSources->getResources(false); + const size_t packageCount = resTable.getBasePackageCount(); + for (size_t i = 0; i < packageCount; i++) { + std::u16string packageName = resTable.getBasePackageName(i).string(); + mIncludedPackages.insert(std::move(packageName)); + } } Maybe<ResourceId> Resolver::findId(const ResourceName& name) { @@ -47,9 +54,31 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { return Entry{ cacheIter->second.id, cacheIter->second.attr.get() }; } + ResourceName mangledName; + const ResourceName* nameToSearch = &name; + if (name.package != mTable->getPackage()) { + // This may be a reference to an included resource or + // to a mangled resource. + if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) { + // This is not in our included set, so mangle the name and + // check for that. + mangledName.entry = name.entry; + NameMangler::mangle(name.package, &mangledName.entry); + mangledName.package = mTable->getPackage(); + mangledName.type = name.type; + nameToSearch = &mangledName; + } else { + const CacheEntry* cacheEntry = buildCacheEntry(name); + if (cacheEntry) { + return Entry{ cacheEntry->id, cacheEntry->attr.get() }; + } + return {}; + } + } + const ResourceTableType* type; const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(name); + std::tie(type, entry) = mTable->findResource(*nameToSearch); if (type && entry) { Entry result = {}; if (mTable->getPackageId() != ResourceTable::kUnsetPackageId && @@ -65,11 +94,6 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { } return result; } - - const CacheEntry* cacheEntry = buildCacheEntry(name); - if (cacheEntry) { - return Entry{ cacheEntry->id, cacheEntry->attr.get() }; - } return {}; } diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h index 90a8cd975d73..cb2234d860c1 100644 --- a/tools/aapt2/Resolver.h +++ b/tools/aapt2/Resolver.h @@ -26,6 +26,7 @@ #include <androidfw/ResourceTypes.h> #include <memory> #include <vector> +#include <unordered_set> namespace aapt { @@ -94,6 +95,7 @@ private: std::shared_ptr<const ResourceTable> mTable; std::shared_ptr<const android::AssetManager> mSources; std::map<ResourceName, CacheEntry> mCache; + std::unordered_set<std::u16string> mIncludedPackages; }; inline const std::u16string& Resolver::getDefaultPackage() const { diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 4d2c64c0a375..f928acd7c8d7 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -193,8 +193,7 @@ inline ::std::ostream& operator<<(::std::ostream& out, // ResourceType implementation. // -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceType& val) { +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) { return out << toString(val); } @@ -221,6 +220,14 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const { != std::tie(rhs.package, rhs.type, rhs.entry); } +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; +} + + // // ResourceNameRef implementation. // @@ -264,8 +271,7 @@ inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const { != std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceNameRef& name) { +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) { if (!name.package.empty()) { out << name.package << ":"; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 4c9618725720..943892dff024 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -22,6 +22,8 @@ #include "Util.h" #include "XliffXmlPullParser.h" +#include <sstream> + namespace aapt { void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage, @@ -107,6 +109,71 @@ bool ResourceParser::tryParseAttributeReference(const StringPiece16& str, return false; } +/* + * Style parent's are a bit different. We accept the following formats: + * + * @[package:]style/<entry> + * ?[package:]style/<entry> + * <package>:[style/]<entry> + * [package:style/]<entry> + */ +bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference, + std::string* outError) { + if (str.empty()) { + return true; + } + + StringPiece16 name = str; + + bool hasLeadingIdentifiers = false; + bool privateRef = false; + + // Skip over these identifiers. A style's parent is a normal reference. + if (name.data()[0] == u'@' || name.data()[0] == u'?') { + hasLeadingIdentifiers = true; + name = name.substr(1, name.size() - 1); + if (name.data()[0] == u'*') { + privateRef = true; + name = name.substr(1, name.size() - 1); + } + } + + ResourceNameRef ref; + ref.type = ResourceType::kStyle; + + StringPiece16 typeStr; + extractResourceName(name, &ref.package, &typeStr, &ref.entry); + if (!typeStr.empty()) { + // If we have a type, make sure it is a Style. + const ResourceType* parsedType = parseResourceType(typeStr); + if (!parsedType || *parsedType != ResourceType::kStyle) { + std::stringstream err; + err << "invalid resource type '" << typeStr << "' for parent of style"; + *outError = err.str(); + return false; + } + } else { + // No type was defined, this should not have a leading identifier. + if (hasLeadingIdentifiers) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return false; + } + } + + if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return false; + } + + outReference->name = ref.toResourceName(); + outReference->privateReference = privateRef; + return true; +} + std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str, const StringPiece16& defaultPackage, bool* outCreate) { @@ -885,15 +952,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) { const SourceLine source = mSource.line(parser->getLineNumber()); - std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false); + ResourceName actualName = resourceName.toResourceName(); + std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false); if (!attr) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(attr)); + return mTable->addResource(actualName, mConfig, source, std::move(attr)); } std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, - const ResourceNameRef& resourceName, + ResourceName* resourceName, bool weak) { uint32_t typeMask = 0; @@ -911,6 +979,18 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, } } + // If this is a declaration, the package name may be in the name. Separate these out. + // Eg. <attr name="android:text" /> + // No format attribute is allowed. + if (weak && formatAttrIter == endAttrIter) { + StringPiece16 package, type, name; + extractResourceName(resourceName->entry, &package, &type, &name); + if (type.empty() && !package.empty()) { + resourceName->package = package.toString(); + resourceName->entry = name.toString(); + } + } + std::vector<Attribute::Symbol> items; bool error = false; @@ -1079,31 +1159,15 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) { const SourceLine source = mSource.line(parser->getLineNumber()); - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); const auto endAttrIter = parser->endAttributes(); const auto parentAttrIter = parser->findAttribute(u"", u"parent"); if (parentAttrIter != endAttrIter) { - ResourceNameRef ref; - bool create = false; - bool privateRef = false; - if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) { - if (create) { - mLogger.error(source.line) - << "parent of style can not be an ID." - << std::endl; - return false; - } - style->parent.name = ref.toResourceName(); - style->parent.privateReference = privateRef; - } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) { - style->parent.name = ref.toResourceName(); - } else { - // TODO(adamlesinski): Try parsing without the '@' or '?'. - // Also, make sure to check the entry name for weird symbols. - style->parent.name = ResourceName { - {}, ResourceType::kStyle, parentAttrIter->value - }; + std::string errStr; + if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) { + mLogger.error(source.line) << errStr << "." << std::endl; + return false; } if (style->parent.name.package.empty()) { @@ -1277,15 +1341,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, } // Copy because our iterator will be invalidated. - std::u16string attrName = attrIter->value; - - ResourceNameRef attrResourceName = { + ResourceName attrResourceName = { mTable->getPackage(), ResourceType::kAttr, - attrName + attrIter->value }; - std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true); + std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true); if (!attr) { success = false; continue; @@ -1293,9 +1355,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, styleable->entries.emplace_back(attrResourceName); - success &= mTable->addResource(attrResourceName, mConfig, - mSource.line(childParser.getLineNumber()), - std::move(attr)); + // The package may have been corrected to another package. If that is so, + // we don't add the declaration. + if (attrResourceName.package == mTable->getPackage()) { + success &= mTable->addResource(attrResourceName, mConfig, + mSource.line(childParser.getLineNumber()), + std::move(attr)); + } } else if (elementName != u"eat-comment" && elementName != u"skip") { mLogger.error(childParser.getLineNumber()) diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 96bba4febf99..52194bd02f99 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -64,6 +64,17 @@ public: ResourceNameRef* outReference); /* + * Returns true if the string `str` was parsed as a valid reference to a style. + * The format for a style parent is slightly more flexible than a normal reference: + * + * @[package:]style/<entry> or + * ?[package:]style/<entry> or + * <package>:[style/]<entry> + */ + static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference, + std::string* outError); + + /* * Returns a Reference object if the string was parsed as a resource or attribute reference, * ( @[+][package:]type/name | ?[package:]type/name ) * assigning defaultPackage if the package was not present in the string, and setting @@ -166,7 +177,7 @@ private: bool parsePublic(XmlPullParser* parser, const StringPiece16& name); bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName); std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser, - const ResourceNameRef& resourceName, + ResourceName* resourceName, bool weak); bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, Attribute::Symbol* outSymbol); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 5afbaf4618e0..63352decbb11 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -94,6 +94,31 @@ TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { &privateRef)); } +TEST(ResourceParserReferenceTest, ParseStyleParentReference) { + Reference ref; + std::string errStr; + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); +} + struct ResourceParserTest : public ::testing::Test { virtual void SetUp() override { mTable = std::make_shared<ResourceTable>(); @@ -283,7 +308,7 @@ TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { TEST_F(ResourceParserTest, ParseStyle) { std::stringstream input; - input << "<style name=\"foo\" parent=\"fu\">" << std::endl + input << "<style name=\"foo\" parent=\"@style/fu\">" << std::endl << " <item name=\"bar\">#ffffffff</item>" << std::endl << " <item name=\"bat\">@string/hey</item>" << std::endl << " <item name=\"baz\"><b>hey</b></item>" << std::endl @@ -304,6 +329,17 @@ TEST_F(ResourceParserTest, ParseStyle) { (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); } +TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { + std::stringstream input; + input << "<style name=\"foo\" parent=\"com.app:Theme\"/>" << std::endl; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>( + ResourceName{ u"android", ResourceType::kStyle, u"foo" }); + ASSERT_NE(style, nullptr); + EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::stringstream input; input << "<string name=\"foo\">@+id/bar</string>" << std::endl; diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 794090d0b74c..02be6516f570 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -16,6 +16,7 @@ #include "ConfigDescription.h" #include "Logger.h" +#include "NameMangler.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Util.h" @@ -311,6 +312,71 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res return true; } +bool ResourceTable::merge(ResourceTable&& other) { + const bool mangleNames = mPackage != other.getPackage(); + std::u16string mangledName; + + for (auto& otherType : other) { + std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type); + if (type->publicStatus.isPublic && otherType->publicStatus.isPublic && + type->typeId != otherType->typeId) { + Logger::error() << "can not merge type '" << type->type << "': conflicting public IDs " + << "(" << type->typeId << " vs " << otherType->typeId << ")." + << std::endl; + return false; + } + + for (auto& otherEntry : otherType->entries) { + const std::u16string* nameToAdd = &otherEntry->name; + if (mangleNames) { + mangledName = otherEntry->name; + NameMangler::mangle(other.getPackage(), &mangledName); + nameToAdd = &mangledName; + } + + std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd); + if (entry->publicStatus.isPublic && otherEntry->publicStatus.isPublic && + entry->entryId != otherEntry->entryId) { + Logger::error() << "can not merge entry '" << type->type << "/" << entry->name + << "': conflicting public IDs " + << "(" << entry->entryId << " vs " << entry->entryId << ")." + << std::endl; + return false; + } + + for (ResourceConfigValue& otherValue : otherEntry->values) { + auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), + otherValue.config, compareConfigs); + if (iter != entry->values.end() && iter->config == otherValue.config) { + int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value); + if (collisionResult > 0) { + // Take the incoming value. + iter->source = std::move(otherValue.source); + iter->comment = std::move(otherValue.comment); + iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)); + } else if (collisionResult == 0) { + ResourceNameRef resourceName = { mPackage, type->type, entry->name }; + Logger::error(otherValue.source) + << "resource '" << resourceName << "' has a conflicting value for " + << "configuration (" << otherValue.config << ")." + << std::endl; + Logger::note(iter->source) << "originally defined here." << std::endl; + return false; + } + } else { + entry->values.insert(iter, ResourceConfigValue{ + otherValue.config, + std::move(otherValue.source), + std::move(otherValue.comment), + std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)), + }); + } + } + } + } + return true; +} + std::tuple<const ResourceTableType*, const ResourceEntry*> ResourceTable::findResource(const ResourceNameRef& name) const { if (name.package != mPackage) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 57b52131958e..3591d11b2282 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -148,6 +148,12 @@ public: bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source); + /* + * Merges the resources from `other` into this table, mangling the names of the resources + * if `other` has a different package name. + */ + bool merge(ResourceTable&& other); + /** * Returns the string pool used by this ResourceTable. * Values that reference strings should use this pool to create diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 785ea1546580..06d8699730de 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -31,7 +31,7 @@ struct TestValue : public Value { TestValue(StringPiece16 str) : value(str.toString()) { } - TestValue* clone() const override { + TestValue* clone(StringPool* /*newPool*/) const override { return new TestValue(value); } @@ -48,7 +48,7 @@ struct TestWeakValue : public Value { return true; } - TestWeakValue* clone() const override { + TestWeakValue* clone(StringPool* /*newPool*/) const override { return new TestWeakValue(); } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 60ef1a8e4d0a..3a6d65d1d96d 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -39,8 +39,8 @@ bool Item::isItem() const { RawString::RawString(const StringPool::Ref& ref) : value(ref) { } -RawString* RawString::clone() const { - return new RawString(value); +RawString* RawString::clone(StringPool* newPool) const { + return new RawString(newPool->makeRef(*value)); } bool RawString::flatten(android::Res_value& outValue) const { @@ -71,7 +71,7 @@ bool Reference::flatten(android::Res_value& outValue) const { return true; } -Reference* Reference::clone() const { +Reference* Reference::clone(StringPool* /*newPool*/) const { Reference* ref = new Reference(); ref->referenceType = referenceType; ref->name = name; @@ -106,7 +106,7 @@ bool Id::flatten(android::Res_value& out) const { return true; } -Id* Id::clone() const { +Id* Id::clone(StringPool* /*newPool*/) const { return new Id(); } @@ -128,8 +128,8 @@ bool String::flatten(android::Res_value& outValue) const { return true; } -String* String::clone() const { - return new String(value); +String* String::clone(StringPool* newPool) const { + return new String(newPool->makeRef(*value)); } void String::print(std::ostream& out) const { @@ -149,8 +149,8 @@ bool StyledString::flatten(android::Res_value& outValue) const { return true; } -StyledString* StyledString::clone() const { - return new StyledString(value); +StyledString* StyledString::clone(StringPool* newPool) const { + return new StyledString(newPool->makeRef(value)); } void StyledString::print(std::ostream& out) const { @@ -170,8 +170,8 @@ bool FileReference::flatten(android::Res_value& outValue) const { return true; } -FileReference* FileReference::clone() const { - return new FileReference(path); +FileReference* FileReference::clone(StringPool* newPool) const { + return new FileReference(newPool->makeRef(*path)); } void FileReference::print(std::ostream& out) const { @@ -186,7 +186,7 @@ bool BinaryPrimitive::flatten(android::Res_value& outValue) const { return true; } -BinaryPrimitive* BinaryPrimitive::clone() const { +BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { return new BinaryPrimitive(value); } @@ -227,7 +227,7 @@ bool Sentinel::flatten(android::Res_value& outValue) const { return true; } -Sentinel* Sentinel::clone() const { +Sentinel* Sentinel::clone(StringPool* /*newPool*/) const { return new Sentinel(); } @@ -243,7 +243,7 @@ bool Attribute::isWeak() const { return weak; } -Attribute* Attribute::clone() const { +Attribute* Attribute::clone(StringPool* /*newPool*/) const { Attribute* attr = new Attribute(weak); attr->typeMask = typeMask; std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols)); @@ -371,13 +371,20 @@ static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& return out << s.symbol.name.entry << "=" << s.value; } -Style* Style::clone() const { - Style* style = new Style(); +Style::Style(bool weak) : weak(weak) { +} + +bool Style::isWeak() const { + return weak; +} + +Style* Style::clone(StringPool* newPool) const { + Style* style = new Style(weak); style->parent = parent; for (auto& entry : entries) { style->entries.push_back(Entry{ entry.key, - std::unique_ptr<Item>(entry.value->clone()) + std::unique_ptr<Item>(entry.value->clone(newPool)) }); } return style; @@ -399,10 +406,10 @@ static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value return out; } -Array* Array::clone() const { +Array* Array::clone(StringPool* newPool) const { Array* array = new Array(); for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->clone())); + array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool))); } return array; } @@ -413,12 +420,12 @@ void Array::print(std::ostream& out) const { << "]"; } -Plural* Plural::clone() const { +Plural* Plural::clone(StringPool* newPool) const { Plural* p = new Plural(); const size_t count = values.size(); for (size_t i = 0; i < count; i++) { if (values[i]) { - p->values[i] = std::unique_ptr<Item>(values[i]->clone()); + p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool)); } } return p; @@ -432,7 +439,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite return out << *item; } -Styleable* Styleable::clone() const { +Styleable* Styleable::clone(StringPool* /*newPool*/) const { Styleable* styleable = new Styleable(); std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries)); return styleable; diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index f25bcf08b330..e3352f3ee612 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -63,7 +63,7 @@ struct Value { /** * Clone the value. */ - virtual Value* clone() const = 0; + virtual Value* clone(StringPool* newPool) const = 0; /** * Human readable printout of this value. @@ -92,7 +92,7 @@ struct Item : public Value { /** * Clone the Item. */ - virtual Item* clone() const override = 0; + virtual Item* clone(StringPool* newPool) const override = 0; /** * Fills in an android::Res_value structure with this Item's binary representation. @@ -132,7 +132,7 @@ struct Reference : public BaseItem<Reference> { Reference(const ResourceId& i, Type type = Type::kResource); bool flatten(android::Res_value& outValue) const override; - Reference* clone() const override; + Reference* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -142,7 +142,7 @@ struct Reference : public BaseItem<Reference> { struct Id : public BaseItem<Id> { bool isWeak() const override; bool flatten(android::Res_value& out) const override; - Id* clone() const override; + Id* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -157,7 +157,7 @@ struct RawString : public BaseItem<RawString> { RawString(const StringPool::Ref& ref); bool flatten(android::Res_value& outValue) const override; - RawString* clone() const override; + RawString* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -167,7 +167,7 @@ struct String : public BaseItem<String> { String(const StringPool::Ref& ref); bool flatten(android::Res_value& outValue) const override; - String* clone() const override; + String* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -177,7 +177,7 @@ struct StyledString : public BaseItem<StyledString> { StyledString(const StringPool::StyleRef& ref); bool flatten(android::Res_value& outValue) const override; - StyledString* clone() const override; + StyledString* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -188,7 +188,7 @@ struct FileReference : public BaseItem<FileReference> { FileReference(const StringPool::Ref& path); bool flatten(android::Res_value& outValue) const override; - FileReference* clone() const override; + FileReference* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -202,8 +202,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { BinaryPrimitive(const android::Res_value& val); bool flatten(android::Res_value& outValue) const override; - BinaryPrimitive* clone() const override; - void print(::std::ostream& out) const override; + BinaryPrimitive* clone(StringPool* newPool) const override; + void print(std::ostream& out) const override; }; /** @@ -214,8 +214,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { struct Sentinel : public BaseItem<Sentinel> { bool isWeak() const override; bool flatten(android::Res_value& outValue) const override; - Sentinel* clone() const override; - void print(::std::ostream& out) const override; + Sentinel* clone(StringPool* newPool) const override; + void print(std::ostream& out) const override; }; struct Attribute : public BaseValue<Attribute> { @@ -233,7 +233,7 @@ struct Attribute : public BaseValue<Attribute> { Attribute(bool w, uint32_t t = 0u); bool isWeak() const override; - virtual Attribute* clone() const override; + virtual Attribute* clone(StringPool* newPool) const override; virtual void print(std::ostream& out) const override; }; @@ -243,17 +243,20 @@ struct Style : public BaseValue<Style> { std::unique_ptr<Item> value; }; + bool weak; Reference parent; std::vector<Entry> entries; - Style* clone() const override; + Style(bool weak); + bool isWeak() const override; + Style* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; struct Array : public BaseValue<Array> { std::vector<std::unique_ptr<Item>> items; - Array* clone() const override; + Array* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -270,14 +273,14 @@ struct Plural : public BaseValue<Plural> { std::array<std::unique_ptr<Item>, Count> values; - Plural* clone() const override; + Plural* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; struct Styleable : public BaseValue<Styleable> { std::vector<Reference> entries; - Styleable* clone() const override; + Styleable* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index b159a0076b02..b983a53f4273 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -175,6 +175,25 @@ StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& return StyleRef(styleEntry); } +StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) { + Entry* entry = new Entry(); + entry->value = *ref.mEntry->str; + entry->context = ref.mEntry->str.mEntry->context; + entry->index = mStrings.size(); + entry->ref = 0; + mStrings.emplace_back(entry); + mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry)); + + StyleEntry* styleEntry = new StyleEntry(); + styleEntry->str = Ref(entry); + for (const Span& span : ref.mEntry->spans) { + styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar }); + } + styleEntry->ref = 0; + mStyles.emplace_back(styleEntry); + return StyleRef(styleEntry); +} + void StringPool::merge(StringPool&& pool) { mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end()); pool.mIndexedStrings.clear(); @@ -266,7 +285,7 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { header->stringCount = pool.size(); header->flags |= android::ResStringPool_header::UTF8_FLAG; - uint32_t* indices = out->nextBlock<uint32_t>(pool.size()); + uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; uint32_t* styleIndices = nullptr; if (!pool.mStyles.empty()) { diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 2aa5b65412e6..64772a4ae487 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -158,6 +158,12 @@ public: StyleRef makeRef(const StyleString& str, const Context& context); /** + * Adds a style from another string pool. Returns a reference to the + * style in the string pool. + */ + StyleRef makeRef(const StyleRef& ref); + + /** * Moves pool into this one without coalescing strings. When this * function returns, pool will be empty. */ diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 85d101a76301..9552937d4ad4 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -162,6 +162,16 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { EXPECT_NE(ref.getIndex(), styleRef.getIndex()); } +TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { + StringPool pool; + BigBuffer buffer(1024); + StringPool::flattenUtf8(&buffer, pool); + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + android::ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); +} + constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。"; TEST(StringPoolTest, FlattenUtf8) { @@ -183,16 +193,10 @@ TEST(StringPoolTest, FlattenUtf8) { BigBuffer buffer(1024); StringPool::flattenUtf8(&buffer, pool); - uint8_t* data = new uint8_t[buffer.size()]; - uint8_t* p = data; - for (const auto& b : buffer) { - memcpy(p, b.buffer.get(), b.size); - p += b.size; - } - + std::unique_ptr<uint8_t[]> data = util::copy(buffer); { - ResStringPool test; - ASSERT_TRUE(test.setTo(data, buffer.size()) == NO_ERROR); + android::ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); EXPECT_EQ(util::getString(test, 0), u"hello"); EXPECT_EQ(util::getString(test, 1), u"goodbye"); @@ -214,7 +218,6 @@ TEST(StringPoolTest, FlattenUtf8) { EXPECT_EQ(ResStringPool_span::END, span->name.index); } - delete[] data; } } // namespace aapt diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp index b6ca6d5d3b50..dd6f63ad3f65 100644 --- a/tools/aapt2/XmlFlattener.cpp +++ b/tools/aapt2/XmlFlattener.cpp @@ -35,6 +35,10 @@ namespace aapt { +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; +constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; +constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; + struct AttributeValueFlattener : ValueVisitor { struct Args : ValueVisitorArgs { Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV, @@ -95,7 +99,9 @@ static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { return a.resourceId < id; } -XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) { +XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver) : + mTable(table), mResolver(resolver) { } /** @@ -190,28 +196,50 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, uint32_t nextAttributeId = 0; const auto endAttrIter = parser->endAttributes(); for (auto attrIter = parser->beginAttributes(); - attrIter != endAttrIter; - ++attrIter) { + attrIter != endAttrIter; + ++attrIter) { uint32_t id; StringPool::Ref nameRef; const Attribute* attr = nullptr; - if (attrIter->namespaceUri.empty()) { + + if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) { + size_t sdkVersion = findAttributeSdkLevel(attrIter->name); + if (sdkVersion > options.maxSdkAttribute.value()) { + // We will silently omit this attribute + smallestStrippedAttributeSdk = + std::min(smallestStrippedAttributeSdk, sdkVersion); + continue; + } + } + + ResourceNameRef genIdName; + bool create = false; + bool privateRef = false; + if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName, + &create, &privateRef) && create) { + mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()), + util::make_unique<Id>()); + } + + + StringPiece16 package; + if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) { + StringPiece16 schemaPrefix = kSchemaPrefix; + package = attrIter->namespaceUri; + package = package.substr(schemaPrefix.size(), + package.size() - schemaPrefix.size()); + } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) { + package = mResolver->getDefaultPackage(); + } + + if (package.empty() || !mResolver) { // Attributes that have no resource ID (because they don't belong to a // package) should appear after those that do have resource IDs. Assign // them some/ integer value that will appear after. id = 0x80000000u | nextAttributeId++; nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); - } else { - StringPiece16 package; - if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") { - package = mResolver->getDefaultPackage(); - } else { - // TODO(adamlesinski): Extract package from namespace. - // The package name appears like so: - // http://schemas.android.com/apk/res/<package name> - package = u"android"; - } + } else { // Find the Attribute object via our Resolver. ResourceName attrName = { package.toString(), ResourceType::kAttr, attrIter->name }; @@ -236,16 +264,6 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, continue; } - if (options.maxSdkAttribute && package == u"android") { - size_t sdkVersion = findAttributeSdkLevel(attrIter->name); - if (sdkVersion > options.maxSdkAttribute.value()) { - // We will silently omit this attribute - smallestStrippedAttributeSdk = - std::min(smallestStrippedAttributeSdk, sdkVersion); - continue; - } - } - id = result.value().id.id; attr = result.value().attr; diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h index abf64ab9d0ee..540a5ef00282 100644 --- a/tools/aapt2/XmlFlattener.h +++ b/tools/aapt2/XmlFlattener.h @@ -45,7 +45,8 @@ public: * Creates a flattener with a Resolver to resolve references * and attributes. */ - XmlFlattener(const std::shared_ptr<Resolver>& resolver); + XmlFlattener(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver); XmlFlattener(const XmlFlattener&) = delete; // Not copyable. @@ -60,6 +61,7 @@ public: BigBuffer* outBuffer, Options options); private: + std::shared_ptr<ResourceTable> mTable; std::shared_ptr<Resolver> mResolver; }; diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp index 6e248475749a..a7d7ac6c267a 100644 --- a/tools/aapt2/XmlFlattener_test.cpp +++ b/tools/aapt2/XmlFlattener_test.cpp @@ -47,7 +47,7 @@ public: table->addResource(ResourceName{ {}, ResourceType::kId, u"test" }, ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>()); - mFlattener = std::make_shared<XmlFlattener>( + mFlattener = std::make_shared<XmlFlattener>(nullptr, std::make_shared<Resolver>(table, std::make_shared<AssetManager>())); } diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp new file mode 100644 index 000000000000..ad5d84ad9393 --- /dev/null +++ b/tools/aapt2/ZipEntry.cpp @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "ZipEntry.h" +#include <utils/Log.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +namespace aapt { + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //ALOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + ALOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + ALOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + ALOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //ALOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + ALOGW("warning: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */, + const ZipEntry* pEntry) +{ + mCDE = pEntry->mCDE; + // Check whether we got all the memory needed. + if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) || + (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) || + (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) { + return NO_MEMORY; + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + ALOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + ALOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + ALOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + ALOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + ALOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + ALOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + ALOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + ALOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + ALOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + ALOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + ALOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#if !defined(_WIN32) + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#if !defined(_WIN32) + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + ALOGD(" LocalFileHeader contents:\n"); + ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + ALOGD(" CentralDirEntry contents:\n"); + ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + ALOGD(" comment: '%s'\n", mFileComment); +} + +/* + * Copy-assignment operator for CentralDirEntry. + */ +ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) { + if (this == &src) { + return *this; + } + + // Free up old data. + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + + // Copy scalars. + mVersionMadeBy = src.mVersionMadeBy; + mVersionToExtract = src.mVersionToExtract; + mGPBitFlag = src.mGPBitFlag; + mCompressionMethod = src.mCompressionMethod; + mLastModFileTime = src.mLastModFileTime; + mLastModFileDate = src.mLastModFileDate; + mCRC32 = src.mCRC32; + mCompressedSize = src.mCompressedSize; + mUncompressedSize = src.mUncompressedSize; + mFileNameLength = src.mFileNameLength; + mExtraFieldLength = src.mExtraFieldLength; + mFileCommentLength = src.mFileCommentLength; + mDiskNumberStart = src.mDiskNumberStart; + mInternalAttrs = src.mInternalAttrs; + mExternalAttrs = src.mExternalAttrs; + mLocalHeaderRelOffset = src.mLocalHeaderRelOffset; + + // Copy strings, if necessary. + if (mFileNameLength > 0) { + mFileName = new unsigned char[mFileNameLength + 1]; + if (mFileName != NULL) + strcpy((char*)mFileName, (char*)src.mFileName); + } else { + mFileName = NULL; + } + if (mFileCommentLength > 0) { + mFileComment = new unsigned char[mFileCommentLength + 1]; + if (mFileComment != NULL) + strcpy((char*)mFileComment, (char*)src.mFileComment); + } else { + mFileComment = NULL; + } + if (mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mExtraField = new unsigned char[mExtraFieldLength + 1]; + if (mExtraField != NULL) + memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1); + } else { + mExtraField = NULL; + } + + return *this; +} + +} // namespace aapt diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h new file mode 100644 index 000000000000..d048a3ed734e --- /dev/null +++ b/tools/aapt2/ZipEntry.h @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace aapt { + +using android::status_t; + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + CentralDirEntry& operator=(const CentralDirEntry& src); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace aapt + +#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp new file mode 100644 index 000000000000..41e59cf6240e --- /dev/null +++ b/tools/aapt2/ZipFile.cpp @@ -0,0 +1,1305 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include <androidfw/ZipUtils.h> +#include <utils/Log.h> + +#include "ZipFile.h" +#include "Util.h" + +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include <memory.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +namespace aapt { + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + ALOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + ALOGD("fopen failed: %d\n", err); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* + * Return the Nth entry in the archive. + */ +ZipEntry* ZipFile::getEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + ALOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + ALOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + ALOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + ALOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + ALOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + ALOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + ALOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + ALOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + ALOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.push_back(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + ALOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + ALOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + ALOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + +status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod, + ZipEntry** ppEntry) { + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry); +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + ALOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + ALOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + ALOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + ALOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + ALOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.push_back(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.push_back(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + ALOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + ALOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + ALOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + ALOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + ALOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + ALOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.erase(mEntries.begin() + i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + ALOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + ALOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + ALOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + ALOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + ALOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + ALOGD(" EndOfCentralDir contents:\n"); + ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + +} // namespace aapt diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h new file mode 100644 index 000000000000..9cbd1fa9adba --- /dev/null +++ b/tools/aapt2/ZipFile.h @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include "BigBuffer.h" +#include "ZipEntry.h" + +#include <stdio.h> +#include <utils/Errors.h> +#include <vector> + +namespace aapt { + +using android::status_t; + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + status_t add(const BigBuffer& data, const char* storageName, + int compressionMethod, ZipEntry** ppEntry); + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + std::vector<ZipEntry*> mEntries; +}; + +}; // namespace aapt + +#endif // __LIBS_ZIPFILE_H diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile index f296dc1f1b2a..5a2a1d167f1e 100644 --- a/tools/aapt2/data/Makefile +++ b/tools/aapt2/data/Makefile @@ -14,6 +14,7 @@ FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediat LOCAL_PACKAGE := com.android.app LOCAL_RESOURCE_DIR := res +LOCAL_LIBS := lib/out/package.apk LOCAL_OUT := out LOCAL_GEN := out/gen @@ -21,13 +22,12 @@ LOCAL_GEN := out/gen # AAPT2 custom rules. ## -PRIVATE_ARSC := $(LOCAL_OUT)/resources.arsc PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk # Eg: framework.apk, etc. -PRIVATE_LIBS := $(FRAMEWORK) -$(info PRIVATE_LIBS = $(PRIVATE_LIBS)) +PRIVATE_INCLUDES := $(FRAMEWORK) +$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES)) # Eg: gen/com/android/app/R.java PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java @@ -42,56 +42,32 @@ PRIVATE_RESOURCE_TYPES := \ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) $(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) -# Eg: drawable, drawable-xhdpi, layout -PRIVATE_NON_VALUE_RESOURCE_TYPES := $(filter-out values%,$(PRIVATE_RESOURCE_TYPES)) -$(info PRIVATE_NON_VALUE_RESOURCE_TYPES = $(PRIVATE_NON_VALUE_RESOURCE_TYPES)) - -# Eg: out/values-v4.table, out/drawable-xhdpi.table -PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.table,$(PRIVATE_RESOURCE_TYPES)) +# Eg: out/values-v4.apk, out/drawable-xhdpi.apk +PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) $(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) -# Eg: out/res/layout/main.xml, out/res/drawable/icon.png -PRIVATE_INTERMEDIATE_FILES := $(patsubst $(LOCAL_RESOURCE_DIR)/%,$(LOCAL_OUT)/res/%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))) -$(info PRIVATE_INTERMEDIATE_FILES = $(PRIVATE_INTERMEDIATE_FILES)) - # Generates rules for collect phase. # $1: Resource type (values-v4) -# returns: out/values-v4.table: res/values-v4/styles.xml res/values-v4/colors.xml +# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml define make-collect-rule -$(LOCAL_OUT)/$1.table: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) - $(AAPT) collect --package $(LOCAL_PACKAGE) -o $$@ $$^ +$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) + $(AAPT) compile --package $(LOCAL_PACKAGE) --binding $(LOCAL_GEN) -o $$@ $$^ endef -# Collect: out/values-v4.table <- res/values-v4/styles.xml res/values-v4/colors.xml +# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) -# Link: out/resources.arsc <- out/values-v4.table out/drawable-v4.table -$(PRIVATE_ARSC): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) - $(AAPT) link --package $(LOCAL_PACKAGE) $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) - -# Compile Manifest: out/AndroidManifest.xml <- AndroidManifest.xml out/resources.arsc -$(LOCAL_OUT)/AndroidManifest.xml: AndroidManifest.xml $(PRIVATE_ARSC) $(PRIVATE_LIBS) - $(AAPT) manifest -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) AndroidManifest.xml - -# Generates rules for compile phase. -# $1: resource file (res/drawable/icon.png) -# returns: out/res/drawable/icon.png: res/drawable/icon.png out/resources.arsc -define make-compile-rule -$1: $(patsubst $(LOCAL_OUT)/res/%,$(LOCAL_RESOURCE_DIR)/%,$1) $(PRIVATE_ARSC) $(PRIVATE_LIBS) - $(AAPT) compile --package $(LOCAL_PACKAGE) -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) $$< -endef - -# Compile: out/res/drawable-xhdpi/icon.png <- res/drawable-xhdpi/icon.png -$(foreach f,$(PRIVATE_INTERMEDIATE_FILES),$(eval $(call make-compile-rule,$f))) +# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk +$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) # R.java: gen/com/android/app/R.java <- out/resources.arsc # No action since R.java is generated when out/resources.arsc is. -$(PRIVATE_R_JAVA): $(PRIVATE_ARSC) +$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) # Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* -$(PRIVATE_APK_ALIGNED): $(PRIVATE_ARSC) $(PRIVATE_INTERMEDIATE_FILES) $(LOCAL_OUT)/AndroidManifest.xml - cd $(LOCAL_OUT); $(ZIP) $(patsubst $(LOCAL_OUT)/%,%,$(PRIVATE_APK_UNALIGNED)) $(patsubst $(LOCAL_OUT)/%,%,$^) - $(ZIPALIGN) $(PRIVATE_APK_UNALIGNED) $@ +$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) + $(ZIPALIGN) $< $@ # Create the out directory if needed. dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) @@ -100,7 +76,7 @@ dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) java: $(PRIVATE_R_JAVA) .PHONY: assemble -assemble: $(LOCAL_OUT)/package.apk +assemble: $(PRIVATE_APK_ALIGNED) .PHONY: all all: assemble java diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml new file mode 100644 index 000000000000..c1612e5549fd --- /dev/null +++ b/tools/aapt2/data/lib/AndroidManifest.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.appcompat"/> diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile new file mode 100644 index 000000000000..2897ff135b30 --- /dev/null +++ b/tools/aapt2/data/lib/Makefile @@ -0,0 +1,81 @@ +## +# Environment dependent variables +## + +AAPT := aapt2 +ZIPALIGN := zipalign 4 +FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk + +## +# Project depenedent variables +## + +LOCAL_PACKAGE := android.appcompat +LOCAL_RESOURCE_DIR := res +LOCAL_OUT := out +LOCAL_GEN := out/gen + +## +# AAPT2 custom rules. +## + +PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk +PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk + +# Eg: framework.apk, etc. +PRIVATE_LIBS := $(FRAMEWORK) +$(info PRIVATE_LIBS = $(PRIVATE_LIBS)) + +# Eg: gen/com/android/app/R.java +PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java +$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA)) + +# Eg: res/drawable/icon.png, res/values/styles.xml +PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f) +$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES)) + +# Eg: drawable, values, layouts +PRIVATE_RESOURCE_TYPES := \ + $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) +$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) + +# Eg: out/values-v4.apk, out/drawable-xhdpi.apk +PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) +$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) + +# Generates rules for collect phase. +# $1: Resource type (values-v4) +# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml +define make-collect-rule +$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) + $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^ +endef + +# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml +$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) + +# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk +$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) + +# R.java: gen/com/android/app/R.java <- out/resources.arsc +# No action since R.java is generated when out/resources.arsc is. +$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) + +# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* +$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) + $(ZIPALIGN) $< $@ + +# Create the out directory if needed. +dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) + +.PHONY: java +java: $(PRIVATE_R_JAVA) + +.PHONY: assemble +assemble: $(PRIVATE_APK_ALIGNED) + +.PHONY: all +all: assemble java + +.DEFAULT_GOAL := all diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml new file mode 100644 index 000000000000..adb5c4fcd004 --- /dev/null +++ b/tools/aapt2/data/lib/res/values/styles.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Platform.AppCompat" parent="@android:style/Theme"> + <item name="android:windowNoTitle">true</item> + </style> +</resources> diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml index 71ce388fdbbf..c5dd27650af7 100644 --- a/tools/aapt2/data/res/values/styles.xml +++ b/tools/aapt2/data/res/values/styles.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="App" parent="android:Theme.Material"> + <style name="App" parent="android.appcompat:Platform.AppCompat"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot index a92405d9146d..4741952d2332 100644 --- a/tools/aapt2/process.dot +++ b/tools/aapt2/process.dot @@ -19,6 +19,13 @@ digraph aapt { res_layout_fr_main_xml [label="res/layout-fr/main.xml"]; res_values_fr_strings_xml [label="res/values-fr/strings.xml"]; + lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green]; + lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green]; + lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green]; + lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green]; + lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green]; + out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"]; + out_package -> package_default; out_fr_package -> package_fr; @@ -26,6 +33,7 @@ digraph aapt { package_default -> out_table_aligned; package_default -> out_res_layout_main_xml; package_default -> out_res_layout_v21_main_xml [color=red]; + package_default -> out_res_layout_lib_main_xml; package_fr [shape=box,label="Assemble",color=blue]; package_fr -> out_table_fr_aligned; @@ -44,6 +52,7 @@ digraph aapt { link_tables [shape=box,label="Link",color=blue]; link_tables -> out_values_table; link_tables -> out_layout_table; + link_tables -> lib_apk_resources_arsc; out_values_table -> compile_values; @@ -61,10 +70,11 @@ digraph aapt { link_fr_tables [shape=box,label="Link",color=blue]; link_fr_tables -> out_values_fr_table; link_fr_tables -> out_layout_fr_table; + link_fr_tables -> lib_apk_resources_arsc; out_values_fr_table -> compile_values_fr; - compile_values_fr [shape=box,label="Compile",color=blue]; + compile_values_fr [shape=box,label="Collect",color=blue]; compile_values_fr -> res_values_fr_strings_xml; out_layout_fr_table -> collect_xml_fr; @@ -89,4 +99,10 @@ digraph aapt { compile_res_layout_fr_main_xml -> res_layout_fr_main_xml; compile_res_layout_fr_main_xml -> out_table_fr_aligned; + + out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml; + + compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue]; + compile_res_layout_lib_main_xml -> out_table_aligned; + compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml; } diff --git a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java index aabd3f1fbd4e..f7654ce231b2 100644 --- a/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java +++ b/tools/layoutlib/bridge/src/android/app/Fragment_Delegate.java @@ -16,7 +16,7 @@ package android.app; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.Context; @@ -30,19 +30,19 @@ import android.os.Bundle; * * The methods being re-implemented are the ones responsible for instantiating Fragment objects. * Because the classes of these objects are found in the project, these methods need access to - * {@link IProjectCallback} object. They are however static methods, so the callback is set - * before the inflation through {@link #setProjectCallback(IProjectCallback)}. + * {@link LayoutlibCallback} object. They are however static methods, so the callback is set + * before the inflation through {@link #setLayoutlibCallback(LayoutlibCallback)}. */ public class Fragment_Delegate { - private static IProjectCallback sProjectCallback; + private static LayoutlibCallback sLayoutlibCallback; /** - * Sets the current {@link IProjectCallback} to be used to instantiate classes coming + * Sets the current {@link LayoutlibCallback} to be used to instantiate classes coming * from the project being rendered. */ - public static void setProjectCallback(IProjectCallback projectCallback) { - sProjectCallback = projectCallback; + public static void setLayoutlibCallback(LayoutlibCallback layoutlibCallback) { + sLayoutlibCallback = layoutlibCallback; } /** @@ -62,17 +62,17 @@ public class Fragment_Delegate { * This is currently just used to get its ClassLoader. * @param fname The class name of the fragment to instantiate. * @param args Bundle of arguments to supply to the fragment, which it - * can retrieve with {@link #getArguments()}. May be null. + * can retrieve with {@link Fragment#getArguments()}. May be null. * @return Returns a new fragment instance. - * @throws InstantiationException If there is a failure in instantiating + * @throws Fragment.InstantiationException If there is a failure in instantiating * the given fragment class. This is a runtime exception; it is not * normally expected to happen. */ @LayoutlibDelegate /*package*/ static Fragment instantiate(Context context, String fname, Bundle args) { try { - if (sProjectCallback != null) { - Fragment f = (Fragment) sProjectCallback.loadView(fname, + if (sLayoutlibCallback != null) { + Fragment f = (Fragment) sLayoutlibCallback.loadView(fname, new Class[0], new Object[0]); if (args != null) { diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java index 96ca25059afa..2c2c6723f981 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -16,8 +16,8 @@ package android.content.res; -import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; @@ -49,7 +49,7 @@ import java.io.InputStream; public final class BridgeResources extends Resources { private BridgeContext mContext; - private IProjectCallback mProjectCallback; + private LayoutlibCallback mLayoutlibCallback; private boolean[] mPlatformResourceFlag = new boolean[1]; private TypedValue mTmpValue = new TypedValue(); @@ -94,12 +94,12 @@ public final class BridgeResources extends Resources { AssetManager assets, DisplayMetrics metrics, Configuration config, - IProjectCallback projectCallback) { + LayoutlibCallback layoutlibCallback) { return Resources.mSystem = new BridgeResources(context, assets, metrics, config, - projectCallback); + layoutlibCallback); } /** @@ -109,16 +109,16 @@ public final class BridgeResources extends Resources { public static void disposeSystem() { if (Resources.mSystem instanceof BridgeResources) { ((BridgeResources)(Resources.mSystem)).mContext = null; - ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; + ((BridgeResources)(Resources.mSystem)).mLayoutlibCallback = null; } Resources.mSystem = null; } private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics, - Configuration config, IProjectCallback projectCallback) { + Configuration config, LayoutlibCallback layoutlibCallback) { super(assets, metrics, config); mContext = context; - mProjectCallback = projectCallback; + mLayoutlibCallback = layoutlibCallback; } public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) { @@ -138,8 +138,8 @@ public final class BridgeResources extends Resources { } // didn't find a match in the framework? look in the project. - if (mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceId(id); + if (mLayoutlibCallback != null) { + resourceInfo = mLayoutlibCallback.resolveResourceId(id); if (resourceInfo != null) { platformResFlag_out[0] = false; @@ -154,11 +154,6 @@ public final class BridgeResources extends Resources { } @Override - public Drawable getDrawable(int id) throws NotFoundException { - return getDrawable(id, null); - } - - @Override public Drawable getDrawable(int id, Theme theme) { Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); @@ -257,7 +252,7 @@ public final class BridgeResources extends Resources { try { // check if the current parser can provide us with a custom parser. if (mPlatformResourceFlag[0] == false) { - parser = mProjectCallback.getParser(value); + parser = mLayoutlibCallback.getParser(value); } // create a new one manually if needed. @@ -692,8 +687,8 @@ public final class BridgeResources extends Resources { Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); // if the name is unknown in the framework, get it from the custom view loader. - if (resourceInfo == null && mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceId(id); + if (resourceInfo == null && mLayoutlibCallback != null) { + resourceInfo = mLayoutlibCallback.resolveResourceId(id); } String message = null; diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index 18036927363f..7d4271bc7688 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -16,6 +16,7 @@ package android.content.res; +import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.AttrResourceValue; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; @@ -40,9 +41,12 @@ import android.view.LayoutInflater_Delegate; import android.view.ViewGroup.LayoutParams; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Map; +import static com.android.ide.common.rendering.api.RenderResources.*; + /** * Custom implementation of TypedArray to handle non compiled resources. */ @@ -56,6 +60,11 @@ public final class BridgeTypedArray extends TypedArray { private final String[] mNames; private final boolean[] mIsFramework; + // Contains ids that are @empty. We still store null in mResourceData for that index, since we + // want to save on the check against empty, each time a resource value is requested. + @Nullable + private int[] mEmptyIds; + public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { super(resources, null, null, 0); @@ -90,19 +99,32 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; + ArrayList<Integer> emptyIds = null; for (int i = 0; i < mResourceData.length; i++) { ResourceValue data = mResourceData[i]; if (data != null) { - if (RenderResources.REFERENCE_NULL.equals(data.getValue())) { - // No need to store this resource value. This saves needless checking for - // "@null" every time an attribute is requested. + String dataValue = data.getValue(); + if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) { + mResourceData[i] = null; + } else if (REFERENCE_EMPTY.equals(dataValue)) { mResourceData[i] = null; + if (emptyIds == null) { + emptyIds = new ArrayList<Integer>(4); + } + emptyIds.add(i); } else { count++; } } } + if (emptyIds != null) { + mEmptyIds = new int[emptyIds.size()]; + for (int i = 0; i < emptyIds.size(); i++) { + mEmptyIds[i] = emptyIds.get(i); + } + } + // allocate the table with an extra to store the size mIndices = new int[count+1]; mIndices[0] = count; @@ -624,7 +646,7 @@ public final class BridgeTypedArray extends TypedArray { if (isFrameworkId) { idValue = Bridge.getResourceId(ResourceType.ID, idName); } else { - idValue = mContext.getProjectCallback().getResourceId(ResourceType.ID, idName); + idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName); } return idValue == null ? defValue : idValue; } @@ -644,7 +666,7 @@ public final class BridgeTypedArray extends TypedArray { idValue = Bridge.getResourceId(resValue.getResourceType(), resValue.getName()); } else { - idValue = mContext.getProjectCallback().getResourceId( + idValue = mContext.getLayoutlibCallback().getResourceId( resValue.getResourceType(), resValue.getName()); } @@ -748,6 +770,12 @@ public final class BridgeTypedArray extends TypedArray { return index >= 0 && index < mResourceData.length && mResourceData[index] != null; } + @Override + public boolean hasValueOrEmpty(int index) { + return hasValue(index) || index >= 0 && index < mResourceData.length && + mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0; + } + /** * Retrieve the raw TypedValue for the attribute at <var>index</var> * and return a temporary object holding its data. This object is only diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java index 691339e2d2ed..138b2d5fca3d 100644 --- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java @@ -69,8 +69,8 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { // this is not an attribute in the android namespace, we query the customviewloader, if // the namespaces match. - if (mContext.getProjectCallback().getNamespace().equals(ns)) { - Integer v = mContext.getProjectCallback().getResourceId(ResourceType.ATTR, name); + if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) { + Integer v = mContext.getLayoutlibCallback().getResourceId(ResourceType.ATTR, name); if (v != null) { return v.intValue(); } @@ -273,7 +273,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { if (mPlatformFile || resource.isFramework()) { id = Bridge.getResourceId(resource.getResourceType(), resource.getName()); } else { - id = mContext.getProjectCallback().getResourceId( + id = mContext.getLayoutlibCallback().getResourceId( resource.getResourceType(), resource.getName()); } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 80036e5ecd61..9eea66338402 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -16,7 +16,7 @@ package android.view; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.MergeCookie; import com.android.ide.common.rendering.api.ResourceReference; @@ -46,7 +46,7 @@ import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext; */ public final class BridgeInflater extends LayoutInflater { - private final IProjectCallback mProjectCallback; + private final LayoutlibCallback mLayoutlibCallback; private boolean mIsInMerge = false; private ResourceReference mResourceReference; @@ -64,21 +64,21 @@ public final class BridgeInflater extends LayoutInflater { super(original, newContext); newContext = getBaseContext(newContext); if (newContext instanceof BridgeContext) { - mProjectCallback = ((BridgeContext) newContext).getProjectCallback(); + mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback(); } else { - mProjectCallback = null; + mLayoutlibCallback = null; } } /** - * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. + * Instantiate a new BridgeInflater with an {@link LayoutlibCallback} object. * * @param context The Android application context. - * @param projectCallback the {@link IProjectCallback} object. + * @param layoutlibCallback the {@link LayoutlibCallback} object. */ - public BridgeInflater(Context context, IProjectCallback projectCallback) { + public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) { super(context); - mProjectCallback = projectCallback; + mLayoutlibCallback = layoutlibCallback; mConstructorArgs[0] = context; } @@ -167,12 +167,13 @@ public final class BridgeInflater extends LayoutInflater { ResourceValue value = null; + @SuppressWarnings("deprecation") Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource); if (layoutInfo != null) { value = bridgeContext.getRenderResources().getFrameworkResource( ResourceType.LAYOUT, layoutInfo.getSecond()); } else { - layoutInfo = mProjectCallback.resolveResourceId(resource); + layoutInfo = mLayoutlibCallback.resolveResourceId(resource); if (layoutInfo != null) { value = bridgeContext.getRenderResources().getProjectResource( @@ -203,7 +204,7 @@ public final class BridgeInflater extends LayoutInflater { } private View loadCustomView(String name, AttributeSet attrs) throws Exception { - if (mProjectCallback != null) { + if (mLayoutlibCallback != null) { // first get the classname in case it's not the node name if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); @@ -211,7 +212,7 @@ public final class BridgeInflater extends LayoutInflater { mConstructorArgs[1] = attrs; - Object customView = mProjectCallback.loadView(name, mConstructorSignature, + Object customView = mLayoutlibCallback.loadView(name, mConstructorSignature, mConstructorArgs); if (customView instanceof View) { diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 818940d768dd..82012c1e2767 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -376,6 +376,10 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void setForcedDisplayScalingMode(int displayId, int mode) { + } + + @Override public void setInTouchMode(boolean arg0) throws RemoteException { // TODO Auto-generated method stub } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 8e74ce1527b6..1da8bb4db849 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -19,8 +19,8 @@ package com.android.layoutlib.bridge.android; import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.AssetRepository; import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; @@ -114,7 +114,7 @@ public final class BridgeContext extends Context { private final RenderResources mRenderResources; private final Configuration mConfig; private final ApplicationInfo mApplicationInfo; - private final IProjectCallback mProjectCallback; + private final LayoutlibCallback mLayoutlibCallback; private final WindowManager mWindowManager; private final DisplayManager mDisplayManager; @@ -148,13 +148,13 @@ public final class BridgeContext extends Context { public BridgeContext(Object projectKey, DisplayMetrics metrics, RenderResources renderResources, AssetRepository assets, - IProjectCallback projectCallback, + LayoutlibCallback layoutlibCallback, Configuration config, int targetSdkVersion, boolean hasRtlSupport) { mProjectKey = projectKey; mMetrics = metrics; - mProjectCallback = projectCallback; + mLayoutlibCallback = layoutlibCallback; mRenderResources = renderResources; mConfig = config; @@ -173,7 +173,7 @@ public final class BridgeContext extends Context { /** * Initializes the {@link Resources} singleton to be linked to this {@link Context}, its - * {@link DisplayMetrics}, {@link Configuration}, and {@link IProjectCallback}. + * {@link DisplayMetrics}, {@link Configuration}, and {@link LayoutlibCallback}. * * @see #disposeResources() */ @@ -185,7 +185,7 @@ public final class BridgeContext extends Context { assetManager, mMetrics, mConfig, - mProjectCallback); + mLayoutlibCallback); mTheme = mSystemResources.newTheme(); } @@ -224,8 +224,8 @@ public final class BridgeContext extends Context { return mMetrics; } - public IProjectCallback getProjectCallback() { - return mProjectCallback; + public LayoutlibCallback getLayoutlibCallback() { + return mLayoutlibCallback; } public RenderResources getRenderResources() { @@ -284,7 +284,7 @@ public final class BridgeContext extends Context { Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resid); boolean isFrameworkRes = true; if (resourceInfo == null) { - resourceInfo = mProjectCallback.resolveResourceId(resid); + resourceInfo = mLayoutlibCallback.resolveResourceId(resid); isFrameworkRes = false; } @@ -340,8 +340,8 @@ public final class BridgeContext extends Context { } // didn't find a match in the framework? look in the project. - if (mProjectCallback != null) { - resourceInfo = mProjectCallback.resolveResourceId(id); + if (mLayoutlibCallback != null) { + resourceInfo = mLayoutlibCallback.resolveResourceId(id); if (resourceInfo != null) { return new ResourceReference(resourceInfo.getSecond(), false); @@ -439,9 +439,9 @@ public final class BridgeContext extends Context { private ILayoutPullParser getParser(ResourceReference resource) { ILayoutPullParser parser; if (resource instanceof ResourceValue) { - parser = mProjectCallback.getParser((ResourceValue) resource); + parser = mLayoutlibCallback.getParser((ResourceValue) resource); } else { - parser = mProjectCallback.getParser(resource.getName()); + parser = mLayoutlibCallback.getParser(resource.getName()); } return parser; } @@ -694,7 +694,7 @@ public final class BridgeContext extends Context { boolean isFrameworkRes = true; Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); if (value == null) { - value = mProjectCallback.resolveResourceId(defStyleRes); + value = mLayoutlibCallback.resolveResourceId(defStyleRes); isFrameworkRes = false; } @@ -732,7 +732,7 @@ public final class BridgeContext extends Context { } } - String appNamespace = mProjectCallback.getNamespace(); + String appNamespace = mLayoutlibCallback.getNamespace(); if (attributeList != null) { for (int index = 0 ; index < attributeList.size() ; index++) { @@ -875,7 +875,7 @@ public final class BridgeContext extends Context { if (resolvedResource != null) { isFramework = true; } else { - resolvedResource = mProjectCallback.resolveResourceId(attr); + resolvedResource = mLayoutlibCallback.resolveResourceId(attr); } if (resolvedResource != null) { @@ -901,7 +901,7 @@ public final class BridgeContext extends Context { return Pair.of(info.getSecond(), Boolean.TRUE); } - info = mProjectCallback.resolveResourceId(attr); + info = mLayoutlibCallback.resolveResourceId(attr); if (info != null) { return Pair.of(info.getSecond(), Boolean.FALSE); } @@ -953,8 +953,8 @@ public final class BridgeContext extends Context { // getResourceId creates a new resource id if an existing resource id isn't found. So, we // check for the existence of the resource before calling it. if (getRenderResources().getProjectResource(resType, resName) != null) { - if (mProjectCallback != null) { - Integer value = mProjectCallback.getResourceId(resType, resName); + if (mLayoutlibCallback != null) { + Integer value = mLayoutlibCallback.getResourceId(resType, resName); if (value != null) { return value; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java index b72329af2a9d..9273ac248a13 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -18,8 +18,8 @@ package com.android.layoutlib.bridge.android.support; import com.android.annotations.NonNull; import com.android.annotations.Nullable; -import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.SessionParams; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; @@ -30,7 +30,6 @@ import android.view.View; import android.widget.LinearLayout; import java.lang.reflect.Method; -import java.util.HashMap; import static com.android.layoutlib.bridge.util.ReflectionUtils.*; @@ -61,7 +60,7 @@ public class RecyclerViewUtil { public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, @NonNull SessionParams params) { try { - setLayoutManager(recyclerView, context, params.getProjectCallback()); + setLayoutManager(recyclerView, context, params.getLayoutlibCallback()); Object adapter = createAdapter(params); setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); } catch (ReflectionException e) { @@ -71,7 +70,7 @@ public class RecyclerViewUtil { } private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, - @NonNull IProjectCallback callback) throws ReflectionException { + @NonNull LayoutlibCallback callback) throws ReflectionException { Object cookie = context.getCookie(recyclerView); assert cookie == null || cookie instanceof LayoutManagerType || cookie instanceof String; if (!(cookie instanceof LayoutManagerType)) { @@ -90,7 +89,7 @@ public class RecyclerViewUtil { @Nullable private static Object createLayoutManager(@Nullable LayoutManagerType type, - @NonNull Context context, @NonNull IProjectCallback callback) + @NonNull Context context, @NonNull LayoutlibCallback callback) throws ReflectionException { if (type == null) { type = LayoutManagerType.getDefault(); @@ -109,7 +108,7 @@ public class RecyclerViewUtil { return null; } try { - return params.getProjectCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]); + return params.getLayoutlibCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]); } catch (Exception e) { throw new ReflectionException(e); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java index ee57067007d1..dd1f661a276b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java @@ -70,7 +70,7 @@ public class AppCompatActionBar extends BridgeActionBar { try { Class[] constructorParams = {View.class}; Object[] constructorArgs = {getDecorContent()}; - mWindowDecorActionBar = params.getProjectCallback().loadView(WINDOW_ACTION_BAR_CLASS, + mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS, constructorParams, constructorArgs); mWindowActionBarClass = mWindowDecorActionBar == null ? null : diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java index 2a83ea165ab0..3d1a9b9f0804 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java @@ -51,7 +51,7 @@ public abstract class BridgeActionBar { @NonNull ViewGroup parentView) { mBridgeContext = context; mParams = params; - mCallback = params.getProjectCallback().getActionBarCallback(); + mCallback = params.getLayoutlibCallback().getActionBarCallback(); ResourceValue layoutName = getLayoutResource(context); if (layoutName == null) { throw new RuntimeException("Unable to find the layout for Action Bar."); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 127cb72a0440..c708316cec1f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -16,10 +16,6 @@ package com.android.layoutlib.bridge.impl; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; -import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; - import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderParams; @@ -43,6 +39,10 @@ import android.view.inputmethod.InputMethodManager_Accessor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + /** * Base class for rendering action. * @@ -120,7 +120,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso // build the context mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, - mParams.getAssets(), mParams.getProjectCallback(), getConfiguration(), + mParams.getAssets(), mParams.getLayoutlibCallback(), getConfiguration(), mParams.getTargetSdkVersion(), mParams.isRtlSupported()); setUp(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 7c11284d3a84..d957259130f9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -16,20 +16,13 @@ package com.android.layoutlib.bridge.impl; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; -import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; -import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; - import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ResourceReference; @@ -54,13 +47,13 @@ import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; -import com.android.layoutlib.bridge.bars.BridgeActionBar; import com.android.layoutlib.bridge.bars.AppCompatActionBar; +import com.android.layoutlib.bridge.bars.BridgeActionBar; import com.android.layoutlib.bridge.bars.Config; +import com.android.layoutlib.bridge.bars.FrameworkActionBar; import com.android.layoutlib.bridge.bars.NavigationBar; import com.android.layoutlib.bridge.bars.StatusBar; import com.android.layoutlib.bridge.bars.TitleBar; -import com.android.layoutlib.bridge.bars.FrameworkActionBar; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.Density; @@ -116,6 +109,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; +import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; +import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; + /** * Class implementing the render session. * <p/> @@ -219,7 +219,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { WindowManagerGlobal_Delegate.setWindowManagerService(iwm); // build the inflater and parser. - mInflater = new BridgeInflater(context, params.getProjectCallback()); + mInflater = new BridgeInflater(context, params.getLayoutlibCallback()); context.setBridgeInflater(mInflater); mBlockParser = new BridgeXmlBlockParser( @@ -401,7 +401,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // Sets the project callback (custom view loader) to the fragment delegate so that // it can instantiate the custom Fragment. - Fragment_Delegate.setProjectCallback(params.getProjectCallback()); + Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback()); String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG); boolean isPreference = "PreferenceScreen".equals(rootTag); @@ -416,13 +416,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // done with the parser, pop it. context.popParser(); - Fragment_Delegate.setProjectCallback(null); + Fragment_Delegate.setLayoutlibCallback(null); // set the AttachInfo on the root view. AttachInfo_Accessor.setAttachInfo(mViewRoot); // post-inflate process. For now this supports TabHost/TabWidget - postInflateProcess(view, params.getProjectCallback(), isPreference ? view : null); + postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null); // get the background drawable if (mWindowBackground != null) { @@ -686,7 +686,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { animationResource = context.getRenderResources().getProjectResource( ResourceType.ANIMATOR, animationName); if (animationResource != null) { - animationId = context.getProjectCallback().getResourceId( + animationId = context.getLayoutlibCallback().getResourceId( ResourceType.ANIMATOR, animationName); } } @@ -1257,17 +1257,17 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically * based on the content of the {@link FrameLayout}. * @param view the root view to process. - * @param projectCallback callback to the project. + * @param layoutlibCallback callback to the project. * @param skip the view and it's children are not processed. */ @SuppressWarnings("deprecation") // For the use of Pair - private void postInflateProcess(View view, IProjectCallback projectCallback, View skip) + private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip) throws PostInflateException { if (view == skip) { return; } if (view instanceof TabHost) { - setupTabHost((TabHost) view, projectCallback); + setupTabHost((TabHost) view, layoutlibCallback); } else if (view instanceof QuickContactBadge) { QuickContactBadge badge = (QuickContactBadge) view; badge.setImageToDefault(); @@ -1286,8 +1286,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // if there was no adapter binding, trying to get it from the call back. if (binding == null) { - binding = params.getProjectCallback().getAdapterBinding(listRef, - context.getViewKey(view), view); + binding = layoutlibCallback.getAdapterBinding( + listRef, context.getViewKey(view), view); } if (binding != null) { @@ -1303,7 +1303,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { for (int i = 0; i < count; i++) { Pair<View, Boolean> pair = context.inflateView( binding.getHeaderAt(i), - list, false /*attachToRoot*/, skipCallbackParser); + list, false, skipCallbackParser); if (pair.getFirst() != null) { list.addHeaderView(pair.getFirst()); } @@ -1315,7 +1315,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { for (int i = 0; i < count; i++) { Pair<View, Boolean> pair = context.inflateView( binding.getFooterAt(i), - list, false /*attachToRoot*/, skipCallbackParser); + list, false, skipCallbackParser); if (pair.getFirst() != null) { list.addFooterView(pair.getFirst()); } @@ -1326,17 +1326,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (view instanceof ExpandableListView) { ((ExpandableListView) view).setAdapter( - new FakeExpandableAdapter( - listRef, binding, params.getProjectCallback())); + new FakeExpandableAdapter(listRef, binding, layoutlibCallback)); } else { ((AbsListView) view).setAdapter( - new FakeAdapter( - listRef, binding, params.getProjectCallback())); + new FakeAdapter(listRef, binding, layoutlibCallback)); } } else if (view instanceof AbsSpinner) { ((AbsSpinner) view).setAdapter( - new FakeAdapter( - listRef, binding, params.getProjectCallback())); + new FakeAdapter(listRef, binding, layoutlibCallback)); } } } @@ -1347,7 +1344,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { final int count = group.getChildCount(); for (int c = 0; c < count; c++) { View child = group.getChildAt(c); - postInflateProcess(child, projectCallback, skip); + postInflateProcess(child, layoutlibCallback, skip); } } } @@ -1371,10 +1368,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * Sets up a {@link TabHost} object. * @param tabHost the TabHost to setup. - * @param projectCallback The project callback object to access the project R class. + * @param layoutlibCallback The project callback object to access the project R class. * @throws PostInflateException */ - private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) + private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback) throws PostInflateException { // look for the TabWidget, and the FrameLayout. They have their own specific names View v = tabHost.findViewById(android.R.id.tabs); @@ -1417,8 +1414,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (count == 0) { // Create a dummy child to get a single tab - TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label", - tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details)) + TabSpec spec = tabHost.newTabSpec("tag") + .setIndicator("Tab Label", tabHost.getResources() + .getDrawable(android.R.drawable.ic_menu_info_details, null)) .setContent(new TabHost.TabContentFactory() { @Override public View createTabContent(String tag) { @@ -1434,7 +1432,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { @SuppressWarnings("ConstantConditions") // child cannot be null. int id = child.getId(); @SuppressWarnings("deprecation") - Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); + Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id); String name; if (resource != null) { name = resource.getSecond(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java index 6c998afaa0ac..9aab340909fd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java @@ -17,7 +17,7 @@ package com.android.layoutlib.bridge.impl.binding; import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute; @@ -40,7 +40,7 @@ import android.widget.TextView; public class AdapterHelper { static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent, - IProjectCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) { + LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) { // we don't care about recycling here because we never scroll. DataBindingItem dataBindingItem = item.getDataBindingItem(); @@ -65,7 +65,7 @@ public class AdapterHelper { } private static void fillView(BridgeContext context, View view, AdapterItem item, - AdapterItem parentItem, IProjectCallback callback, ResourceReference adapterRef) { + AdapterItem parentItem, LayoutlibCallback callback, ResourceReference adapterRef) { if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java index 9a13f5a36a89..142eac172502 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java @@ -18,7 +18,7 @@ package com.android.layoutlib.bridge.impl.binding; import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ResourceReference; import com.android.util.Pair; @@ -37,18 +37,17 @@ import java.util.List; * and {@link SpinnerAdapter}. * */ -@SuppressWarnings("deprecation") public class FakeAdapter extends BaseAdapter { // don't use a set because the order is important. private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>(); - private final IProjectCallback mCallback; + private final LayoutlibCallback mCallback; private final ResourceReference mAdapterRef; private final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); private boolean mSkipCallbackParser = false; public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding, - IProjectCallback callback) { + LayoutlibCallback callback) { mAdapterRef = adapterRef; mCallback = callback; @@ -111,11 +110,11 @@ public class FakeAdapter extends BaseAdapter { public View getView(int position, View convertView, ViewGroup parent) { // we don't care about recycling here because we never scroll. AdapterItem item = mItems.get(position); - Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentGroup*/, parent, - mCallback, mAdapterRef, mSkipCallbackParser); + @SuppressWarnings("deprecation") + Pair<View, Boolean> pair = AdapterHelper.getView(item, null, parent, mCallback, + mAdapterRef, mSkipCallbackParser); mSkipCallbackParser = pair.getSecond(); return pair.getFirst(); - } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java index e539579fb0ab..344b17eab039 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java @@ -18,7 +18,7 @@ package com.android.layoutlib.bridge.impl.binding; import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ResourceReference; import com.android.util.Pair; @@ -34,7 +34,7 @@ import java.util.List; @SuppressWarnings("deprecation") public class FakeExpandableAdapter implements ExpandableListAdapter, HeterogeneousExpandableList { - private final IProjectCallback mCallback; + private final LayoutlibCallback mCallback; private final ResourceReference mAdapterRef; private boolean mSkipCallbackParser = false; @@ -45,7 +45,7 @@ public class FakeExpandableAdapter implements ExpandableListAdapter, Heterogeneo private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>(); public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding, - IProjectCallback callback) { + LayoutlibCallback callback) { mAdapterRef = adapterRef; mCallback = callback; diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png Binary files differindex e38f437a0ce4..9bf302ad6906 100644 --- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java index 565e8815a987..0a5e7984a336 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -20,7 +20,7 @@ import com.android.SdkConstants; import com.android.ide.common.rendering.api.ActionBarCallback; import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.resources.ResourceType; @@ -28,10 +28,7 @@ import com.android.ide.common.resources.IntArrayWrapper; import com.android.util.Pair; import com.android.utils.ILogger; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -40,7 +37,7 @@ import java.util.Map; import com.google.android.collect.Maps; @SuppressWarnings("deprecation") // For Pair -public class LayoutLibTestCallback extends ClassLoader implements IProjectCallback { +public class LayoutLibTestCallback extends LayoutlibCallback { private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/"; private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication"; @@ -48,16 +45,16 @@ public class LayoutLibTestCallback extends ClassLoader implements IProjectCallba private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap(); private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap(); private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap(); - private final Map<String, Class<?>> mClasses = Maps.newHashMap(); private final ILogger mLog; private final ActionBarCallback mActionBarCallback = new ActionBarCallback(); + private final ClassLoader mModuleClassLoader = new ModuleClassLoader(PROJECT_CLASSES_LOCATION); public LayoutLibTestCallback(ILogger logger) { mLog = logger; } public void initResources() throws ClassNotFoundException { - Class<?> rClass = loadClass(PACKAGE_NAME + ".R"); + Class<?> rClass = mModuleClassLoader.loadClass(PACKAGE_NAME + ".R"); Class<?>[] nestedClasses = rClass.getDeclaredClasses(); for (Class<?> resClass : nestedClasses) { final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName()); @@ -91,40 +88,11 @@ public class LayoutLibTestCallback extends ClassLoader implements IProjectCallba } } - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - Class<?> aClass = mClasses.get(name); - if (aClass != null) { - return aClass; - } - String pathName = PROJECT_CLASSES_LOCATION.concat(name.replace('.', '/')).concat(".class"); - InputStream classInputStream = getClass().getResourceAsStream(pathName); - if (classInputStream == null) { - throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName); - } - byte[] data; - try { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - data = new byte[16384]; - while ((nRead = classInputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - buffer.flush(); - data = buffer.toByteArray(); - } catch (IOException e) { - // Wrap the exception with ClassNotFoundException so that caller can deal with it. - throw new ClassNotFoundException("Unable to load class " + name, e); - } - aClass = defineClass(name, data, 0, data.length); - mClasses.put(name, aClass); - return aClass; - } @Override public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) throws Exception { - Class<?> viewClass = findClass(name); + Class<?> viewClass = mModuleClassLoader.loadClass(name); Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature); viewConstructor.setAccessible(true); return viewConstructor.newInstance(constructorArgs); @@ -180,4 +148,9 @@ public class LayoutLibTestCallback extends ClassLoader implements IProjectCallba public ActionBarCallback getActionBarCallback() { return mActionBarCallback; } + + @Override + public boolean supports(int ideFeature) { + return false; + } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java new file mode 100644 index 000000000000..110f4c8b9795 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ModuleClassLoader.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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.layoutlib.bridge.intensive.setup; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import com.google.android.collect.Maps; + +/** + * The ClassLoader to load the project's classes. + */ +public class ModuleClassLoader extends ClassLoader { + + private final Map<String, Class<?>> mClasses = Maps.newHashMap(); + private final String mClassLocation; + + public ModuleClassLoader(String classLocation) { + mClassLocation = classLocation; + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class<?> aClass = mClasses.get(name); + if (aClass != null) { + return aClass; + } + String pathName = mClassLocation.concat(name.replace('.', '/')).concat(".class"); + InputStream classInputStream = getClass().getResourceAsStream(pathName); + if (classInputStream == null) { + throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName); + } + byte[] data; + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + data = new byte[16384]; // 16k + while ((nRead = classInputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + data = buffer.toByteArray(); + } catch (IOException e) { + // Wrap the exception with ClassNotFoundException so that caller can deal with it. + throw new ClassNotFoundException("Unable to load class " + name, e); + } + aClass = defineClass(name, data, 0, data.length); + mClasses.put(name, aClass); + return aClass; + } +} diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index e8a51e336f7d..5dc70bd49baf 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -261,6 +261,12 @@ public class ScanResult implements Parcelable { return freq > 4900 && freq < 5900; } + /** + * @hide + * storing the raw bytes of full result IEs + **/ + public byte[] bytes; + /** information element from beacon * @hide */ diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 4e4f9b2f7fe5..18f90d80b9a1 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -20,10 +20,16 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.DhcpInfo; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.wifi.ScanSettings; import android.net.wifi.WifiChannel; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.Handler; import android.os.HandlerThread; @@ -38,6 +44,7 @@ import android.util.SparseArray; import java.net.InetAddress; import java.util.concurrent.CountDownLatch; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; @@ -568,6 +575,7 @@ public class WifiManager { private Context mContext; IWifiManager mService; + private final int mTargetSdkVersion; private static final int INVALID_KEY = 0; private static int sListenerKey = 1; @@ -576,11 +584,17 @@ public class WifiManager { private static AsyncChannel sAsyncChannel; private static CountDownLatch sConnected; + private static ConnectivityManager sCM; private static final Object sThreadRefLock = new Object(); private static int sThreadRefCount; private static HandlerThread sHandlerThread; + @GuardedBy("sCM") + // TODO: Introduce refcounting and make this a per-process static callback, instead of a + // per-WifiManager callback. + private PinningNetworkCallback mNetworkCallback; + /** * Create a new WifiManager instance. * Applications will almost always want to use @@ -594,6 +608,7 @@ public class WifiManager { public WifiManager(Context context, IWifiManager service) { mContext = context; mService = service; + mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; init(); } @@ -740,6 +755,20 @@ public class WifiManager { * networks are disabled, and an attempt to connect to the selected * network is initiated. This may result in the asynchronous delivery * of state change events. + * <p> + * <b>Note:</b> If an application's target SDK version is + * {@link android.os.Build.VERSION_CODES#MNC} or newer, network + * communication may not use Wi-Fi even if Wi-Fi is connected; traffic may + * instead be sent through another network, such as cellular data, + * Bluetooth tethering, or Ethernet. For example, traffic will never use a + * Wi-Fi network that does not provide Internet access (e.g. a wireless + * printer), if another network that does offer Internet access (e.g. + * cellular data) is available. Applications that need to ensure that their + * network traffic uses Wi-Fi should use APIs such as + * {@link Network#bindSocket(java.net.Socket)}, + * {@link Network#openConnection(java.net.URL)}, or + * {@link ConnectivityManager#bindProcessToNetwork} to do so. + * * @param netId the ID of the network in the list of configured networks * @param disableOthers if true, disable all other networks. The way to * select a particular network to connect to is specify {@code true} @@ -747,11 +776,23 @@ public class WifiManager { * @return {@code true} if the operation succeeded */ public boolean enableNetwork(int netId, boolean disableOthers) { + final boolean pin = disableOthers && mTargetSdkVersion < Build.VERSION_CODES.MNC; + if (pin) { + registerPinningNetworkCallback(); + } + + boolean success; try { - return mService.enableNetwork(netId, disableOthers); + success = mService.enableNetwork(netId, disableOthers); } catch (RemoteException e) { - return false; + success = false; + } + + if (pin && !success) { + unregisterPinningNetworkCallback(); } + + return success; } /** @@ -1951,6 +1992,92 @@ public class WifiManager { "No permission to access and change wifi or a bad initialization"); } + private void initConnectivityManager() { + // TODO: what happens if an app calls a WifiManager API before ConnectivityManager is + // registered? Can we fix this by starting ConnectivityService before WifiService? + if (sCM == null) { + sCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (sCM == null) { + throw new IllegalStateException("Bad luck, ConnectivityService not started."); + } + } + } + + /** + * A NetworkCallback that pins the process to the first wifi network to connect. + * + * We use this to maintain compatibility with pre-M apps that call WifiManager.enableNetwork() + * to connect to a Wi-Fi network that has no Internet access, and then assume that they will be + * able to use that network because it's the system default. + * + * In order to maintain compatibility with apps that call setProcessDefaultNetwork themselves, + * we try not to set the default network unless they have already done so, and we try not to + * clear the default network unless we set it ourselves. + * + * This should maintain behaviour that's compatible with L, which would pin the whole system to + * any wifi network that was created via enableNetwork(..., true) until that network + * disconnected. + * + * Note that while this hack allows network traffic to flow, it is quite limited. For example: + * + * 1. setProcessDefaultNetwork only affects this process, so: + * - Any subprocesses spawned by this process will not be pinned to Wi-Fi. + * - If this app relies on any other apps on the device also being on Wi-Fi, that won't work + * either, because other apps on the device will not be pinned. + * 2. The behaviour of other APIs is not modified. For example: + * - getActiveNetworkInfo will return the system default network, not Wi-Fi. + * - There will be no CONNECTIVITY_ACTION broadcasts about TYPE_WIFI. + * - getProcessDefaultNetwork will not return null, so if any apps are relying on that, they + * will be surprised as well. + */ + private class PinningNetworkCallback extends NetworkCallback { + private Network mPinnedNetwork; + + @Override + public void onPreCheck(Network network) { + if (sCM.getProcessDefaultNetwork() == null && mPinnedNetwork == null) { + sCM.setProcessDefaultNetwork(network); + mPinnedNetwork = network; + Log.d(TAG, "Wifi alternate reality enabled on network " + network); + } + } + + @Override + public void onLost(Network network) { + if (network.equals(mPinnedNetwork) && network.equals(sCM.getProcessDefaultNetwork())) { + sCM.setProcessDefaultNetwork(null); + Log.d(TAG, "Wifi alternate reality disabled on network " + network); + mPinnedNetwork = null; + unregisterPinningNetworkCallback(); + } + } + } + + private void registerPinningNetworkCallback() { + initConnectivityManager(); + synchronized (sCM) { + if (mNetworkCallback == null) { + // TODO: clear all capabilities. + NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(); + mNetworkCallback = new PinningNetworkCallback(); + sCM.registerNetworkCallback(request, mNetworkCallback); + } + } + } + + private void unregisterPinningNetworkCallback() { + initConnectivityManager(); + synchronized (sCM) { + if (mNetworkCallback != null) { + sCM.unregisterNetworkCallback(mNetworkCallback); + mNetworkCallback = null; + } + } + } + /** * Connect to a network with the given configuration. The network also * gets added to the supplicant configuration. |