Merge "Add comments for RemoteCallVideoClient and RemoteCallVideoProvider." into lmp-dev
diff --git a/Android.mk b/Android.mk
index 7a8907e..79500ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -148,6 +148,7 @@
core/java/android/hardware/ISerialManager.aidl \
core/java/android/hardware/display/IDisplayManager.aidl \
core/java/android/hardware/display/IDisplayManagerCallback.aidl \
+ core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl \
core/java/android/hardware/hdmi/IHdmiControlCallback.aidl \
core/java/android/hardware/hdmi/IHdmiControlService.aidl \
core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \
@@ -325,6 +326,9 @@
media/java/android/media/routing/IMediaRouterDelegate.aidl \
media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
media/java/android/media/routing/IMediaRouterStateCallback.aidl \
+ media/java/android/media/projection/IMediaProjection.aidl \
+ media/java/android/media/projection/IMediaProjectionCallback.aidl \
+ media/java/android/media/projection/IMediaProjectionManager.aidl \
media/java/android/media/session/IActiveSessionsListener.aidl \
media/java/android/media/session/ISessionController.aidl \
media/java/android/media/session/ISessionControllerCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 633d0b2..6f7f9ee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -106,7 +106,7 @@
field public static final java.lang.String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
field public static final java.lang.String READ_PROFILE = "android.permission.READ_PROFILE";
field public static final java.lang.String READ_SMS = "android.permission.READ_SMS";
- field public static final java.lang.String READ_SOCIAL_STREAM = "android.permission.READ_SOCIAL_STREAM";
+ field public static final deprecated java.lang.String READ_SOCIAL_STREAM = "android.permission.READ_SOCIAL_STREAM";
field public static final java.lang.String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
field public static final java.lang.String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
field public static final java.lang.String READ_USER_DICTIONARY = "android.permission.READ_USER_DICTIONARY";
@@ -157,7 +157,7 @@
field public static final java.lang.String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
field public static final java.lang.String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS";
field public static final java.lang.String WRITE_SMS = "android.permission.WRITE_SMS";
- field public static final java.lang.String WRITE_SOCIAL_STREAM = "android.permission.WRITE_SOCIAL_STREAM";
+ field public static final deprecated java.lang.String WRITE_SOCIAL_STREAM = "android.permission.WRITE_SOCIAL_STREAM";
field public static final java.lang.String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
field public static final java.lang.String WRITE_USER_DICTIONARY = "android.permission.WRITE_USER_DICTIONARY";
}
@@ -4642,7 +4642,7 @@
field public android.app.Notification publicVersion;
field public android.net.Uri sound;
field public java.lang.CharSequence tickerText;
- field public android.widget.RemoteViews tickerView;
+ field public deprecated android.widget.RemoteViews tickerView;
field public long[] vibrate;
field public int visibility;
field public long when;
@@ -4743,7 +4743,7 @@
method public android.app.Notification.Builder setStyle(android.app.Notification.Style);
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
- method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -6185,10 +6185,14 @@
method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
method public boolean readRemoteRssi();
+ method public boolean requestConnectionParameterUpdate(int);
method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ field public static final int GATT_CONNECTION_BALANCED = 0; // 0x0
field public static final int GATT_CONNECTION_CONGESTED = 143; // 0x8f
+ field public static final int GATT_CONNECTION_HIGH_PRIORITY = 1; // 0x1
+ field public static final int GATT_CONNECTION_LOW_POWER = 2; // 0x2
field public static final int GATT_FAILURE = 257; // 0x101
field public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5; // 0x5
field public static final int GATT_INSUFFICIENT_ENCRYPTION = 15; // 0xf
@@ -7191,6 +7195,7 @@
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
field public static final java.lang.String LOCATION_SERVICE = "location";
+ field public static final java.lang.String MEDIA_PROJECTION_SERVICE = "media_projection";
field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
field public static final java.lang.String MEDIA_SESSION_SERVICE = "media_session";
field public static final int MODE_APPEND = 32768; // 0x8000
@@ -7213,6 +7218,7 @@
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
+ field public static final java.lang.String TV_PARENTAL_CONTROL_SERVICE = "tv_parental_control";
field public static final java.lang.String UI_MODE_SERVICE = "uimode";
field public static final java.lang.String USB_SERVICE = "usb";
field public static final java.lang.String USER_SERVICE = "user";
@@ -13107,6 +13113,7 @@
public final class DisplayManager {
method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, android.view.Surface, int);
+ method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, android.view.Surface, int, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(java.lang.String);
@@ -13116,6 +13123,7 @@
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
+ field public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 16; // 0x10
field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
}
@@ -13132,6 +13140,13 @@
method public void setSurface(android.view.Surface);
}
+ public static abstract class VirtualDisplay.Callbacks {
+ ctor public VirtualDisplay.Callbacks();
+ method public void onDisplayPaused();
+ method public void onDisplayResumed();
+ method public void onDisplayStopped();
+ }
+
}
package android.hardware.input {
@@ -13953,6 +13968,29 @@
method public android.media.AudioAttributes.Builder setUsage(int);
}
+ public class AudioDevice {
+ field public static final int DEVICE_TYPE_AUX_LINE = 19; // 0x13
+ field public static final int DEVICE_TYPE_BLUETOOTH_A2DP = 8; // 0x8
+ field public static final int DEVICE_TYPE_BLUETOOTH_SCO = 7; // 0x7
+ field public static final int DEVICE_TYPE_BUILTIN_EARPIECE = 1; // 0x1
+ field public static final int DEVICE_TYPE_BUILTIN_MIC = 15; // 0xf
+ field public static final int DEVICE_TYPE_BUILTIN_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_DOCK = 13; // 0xd
+ field public static final int DEVICE_TYPE_FM = 14; // 0xe
+ field public static final int DEVICE_TYPE_FM_TUNER = 16; // 0x10
+ field public static final int DEVICE_TYPE_HDMI = 9; // 0x9
+ field public static final int DEVICE_TYPE_HDMI_ARC = 10; // 0xa
+ field public static final int DEVICE_TYPE_LINE_ANALOG = 5; // 0x5
+ field public static final int DEVICE_TYPE_LINE_DIGITAL = 6; // 0x6
+ field public static final int DEVICE_TYPE_TELEPHONY = 18; // 0x12
+ field public static final int DEVICE_TYPE_TV_TUNER = 17; // 0x11
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int DEVICE_TYPE_USB_ACCESSORY = 12; // 0xc
+ field public static final int DEVICE_TYPE_USB_DEVICE = 11; // 0xb
+ field public static final int DEVICE_TYPE_WIRED_HEADPHONES = 4; // 0x4
+ field public static final int DEVICE_TYPE_WIRED_HEADSET = 3; // 0x3
+ }
+
public class AudioFormat {
field public static final deprecated int CHANNEL_CONFIGURATION_DEFAULT = 1; // 0x1
field public static final deprecated int CHANNEL_CONFIGURATION_INVALID = 0; // 0x0
@@ -15885,8 +15923,11 @@
public class Virtualizer extends android.media.audiofx.AudioEffect {
ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
+ method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
method public boolean getStrengthSupported();
method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
@@ -16008,6 +16049,28 @@
}
+package android.media.projection {
+
+ public final class MediaProjection {
+ method public void addCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
+ method public android.media.AudioRecord createAudioRecord(int, int, int, int);
+ method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, boolean, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
+ method public void removeCallback(android.media.projection.MediaProjection.Callback);
+ method public void stop();
+ }
+
+ public static abstract class MediaProjection.Callback {
+ ctor public MediaProjection.Callback();
+ method public void onStop();
+ }
+
+ public final class MediaProjectionManager {
+ method public android.media.projection.MediaProjection getMediaProjection(int, android.content.Intent);
+ method public android.content.Intent getScreenCaptureIntent();
+ }
+
+}
+
package android.media.routing {
public final class MediaRouteSelector implements android.os.Parcelable {
@@ -16402,10 +16465,12 @@
package android.media.tv {
- public class TvContentRating {
+ public final class TvContentRating {
ctor public TvContentRating(java.lang.String);
ctor public TvContentRating(java.lang.String, java.lang.String[]);
method public java.lang.String flattenToString();
+ method public java.lang.String getMainRating();
+ method public java.util.List<java.lang.String> getSubRatings();
method public static android.media.tv.TvContentRating unflattenFromString(java.lang.String);
field public static final java.lang.String RATING_KR_12 = "RATING_KR_12";
field public static final java.lang.String RATING_KR_15 = "RATING_KR_15";
@@ -16429,8 +16494,9 @@
method public static final android.net.Uri buildChannelLogoUri(long);
method public static final android.net.Uri buildChannelLogoUri(android.net.Uri);
method public static final android.net.Uri buildChannelUri(long);
+ method public static final android.net.Uri buildChannelUriForPassthroughTvInput(java.lang.String);
method public static final android.net.Uri buildChannelsUriForInput(java.lang.String);
- method public static final android.net.Uri buildChannelsUriForInput(java.lang.String, boolean);
+ method public static final java.lang.String buildInputId(android.content.ComponentName);
method public static final android.net.Uri buildProgramUri(long);
method public static final android.net.Uri buildProgramsUriForChannel(long);
method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri);
@@ -16445,7 +16511,6 @@
public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
method public static final java.lang.String getVideoResolution(java.lang.String);
- field public static final java.lang.String COLUMN_BROWSABLE = "browsable";
field public static final java.lang.String COLUMN_CONDITIONAL_ACCESS = "conditional_access";
field public static final java.lang.String COLUMN_DESCRIPTION = "description";
field public static final java.lang.String COLUMN_DISPLAY_NAME = "display_name";
@@ -16467,31 +16532,30 @@
field public static final int SERVICE_TYPE_AUDIO = 2; // 0x2
field public static final int SERVICE_TYPE_AUDIO_VIDEO = 1; // 0x1
field public static final int SERVICE_TYPE_OTHER = 0; // 0x0
- field public static final int TYPE_1SEG = 263168; // 0x40400
- field public static final int TYPE_ATSC_C = 197120; // 0x30200
- field public static final int TYPE_ATSC_M_H = 197376; // 0x30300
- field public static final int TYPE_ATSC_T = 196608; // 0x30000
- field public static final int TYPE_CMMB = 327936; // 0x50100
- field public static final int TYPE_DTMB = 327680; // 0x50000
- field public static final int TYPE_DVB_C = 131584; // 0x20200
- field public static final int TYPE_DVB_C2 = 131585; // 0x20201
- field public static final int TYPE_DVB_H = 131840; // 0x20300
- field public static final int TYPE_DVB_S = 131328; // 0x20100
- field public static final int TYPE_DVB_S2 = 131329; // 0x20101
- field public static final int TYPE_DVB_SH = 132096; // 0x20400
- field public static final int TYPE_DVB_T = 131072; // 0x20000
- field public static final int TYPE_DVB_T2 = 131073; // 0x20001
- field public static final int TYPE_ISDB_C = 262912; // 0x40300
- field public static final int TYPE_ISDB_S = 262656; // 0x40200
- field public static final int TYPE_ISDB_T = 262144; // 0x40000
- field public static final int TYPE_ISDB_TB = 262400; // 0x40100
+ field public static final int TYPE_1SEG = 197632; // 0x30400
+ field public static final int TYPE_ATSC_C = 131584; // 0x20200
+ field public static final int TYPE_ATSC_M_H = 131840; // 0x20300
+ field public static final int TYPE_ATSC_T = 131072; // 0x20000
+ field public static final int TYPE_CMMB = 262400; // 0x40100
+ field public static final int TYPE_DTMB = 262144; // 0x40000
+ field public static final int TYPE_DVB_C = 66048; // 0x10200
+ field public static final int TYPE_DVB_C2 = 66049; // 0x10201
+ field public static final int TYPE_DVB_H = 66304; // 0x10300
+ field public static final int TYPE_DVB_S = 65792; // 0x10100
+ field public static final int TYPE_DVB_S2 = 65793; // 0x10101
+ field public static final int TYPE_DVB_SH = 66560; // 0x10400
+ field public static final int TYPE_DVB_T = 65536; // 0x10000
+ field public static final int TYPE_DVB_T2 = 65537; // 0x10001
+ field public static final int TYPE_ISDB_C = 197376; // 0x30300
+ field public static final int TYPE_ISDB_S = 197120; // 0x30200
+ field public static final int TYPE_ISDB_T = 196608; // 0x30000
+ field public static final int TYPE_ISDB_TB = 196864; // 0x30100
field public static final int TYPE_NTSC = 1; // 0x1
field public static final int TYPE_OTHER = 0; // 0x0
field public static final int TYPE_PAL = 2; // 0x2
- field public static final int TYPE_PASSTHROUGH = 65536; // 0x10000
field public static final int TYPE_SECAM = 3; // 0x3
- field public static final int TYPE_S_DMB = 393472; // 0x60100
- field public static final int TYPE_T_DMB = 393216; // 0x60000
+ field public static final int TYPE_S_DMB = 327936; // 0x50100
+ field public static final int TYPE_T_DMB = 327680; // 0x50000
field public static final java.lang.String VIDEO_FORMAT_1080I = "VIDEO_FORMAT_1080I";
field public static final java.lang.String VIDEO_FORMAT_1080P = "VIDEO_FORMAT_1080P";
field public static final java.lang.String VIDEO_FORMAT_2160P = "VIDEO_FORMAT_2160P";
@@ -16560,21 +16624,30 @@
method public java.lang.String getParentId();
method public android.content.pm.ServiceInfo getServiceInfo();
method public int getType();
- method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
- method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
+ method public boolean isPassthroughInputType();
+ method public android.graphics.drawable.Drawable loadIcon(android.content.Context);
+ method public java.lang.CharSequence loadLabel(android.content.Context);
method public void writeToParcel(android.os.Parcel, int);
field public static final java.lang.String EXTRA_INPUT_ID = "inputId";
- field public static final int TYPE_HDMI = 1; // 0x1
- field public static final int TYPE_PASSTHROUGH = 3; // 0x3
+ field public static final int TYPE_COMPONENT = 8; // 0x8
+ field public static final int TYPE_COMPOSITE = 9; // 0x9
+ field public static final int TYPE_DISPLAY_PORT = 4; // 0x4
+ field public static final int TYPE_DVI = 6; // 0x6
+ field public static final int TYPE_HDMI = 3; // 0x3
+ field public static final int TYPE_OTHER_HARDWARE = 1; // 0x1
+ field public static final int TYPE_SCART = 5; // 0x5
+ field public static final int TYPE_SVIDEO = 10; // 0xa
field public static final int TYPE_TUNER = 2; // 0x2
+ field public static final int TYPE_VGA = 7; // 0x7
field public static final int TYPE_VIRTUAL = 0; // 0x0
}
public final class TvInputManager {
method public int getInputState(java.lang.String);
+ method public android.media.tv.TvInputInfo getTvInputInfo(java.lang.String);
method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
- method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
- method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+ method public void registerCallback(android.media.tv.TvInputManager.TvInputCallback, android.os.Handler);
+ method public void unregisterCallback(android.media.tv.TvInputManager.TvInputCallback);
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -16584,15 +16657,17 @@
field public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; // 0x2
}
- public static abstract class TvInputManager.TvInputListener {
- ctor public TvInputManager.TvInputListener();
+ public static abstract class TvInputManager.TvInputCallback {
+ ctor public TvInputManager.TvInputCallback();
+ method public void onInputAdded(java.lang.String);
+ method public void onInputRemoved(java.lang.String);
method public void onInputStateChanged(java.lang.String, int);
}
public abstract class TvInputService extends android.app.Service {
ctor public TvInputService();
method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract android.media.tv.TvInputService.Session onCreateSession();
+ method public abstract android.media.tv.TvInputService.Session onCreateSession(java.lang.String);
field public static final java.lang.String SERVICE_INTERFACE = "android.media.tv.TvInputService";
field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input";
}
@@ -16600,9 +16675,11 @@
public abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
ctor public TvInputService.Session();
method public void dispatchChannelRetuned(android.net.Uri);
+ method public void dispatchContentBlocked(android.media.tv.TvContentRating);
method public void dispatchTrackInfoChanged(java.util.List<android.media.tv.TvTrackInfo>);
method public void dispatchVideoAvailable();
method public void dispatchVideoUnavailable(int);
+ method public abstract void onContentUnblocked(android.media.tv.TvContentRating);
method public android.view.View onCreateOverlayView();
method public boolean onGenericMotionEvent(android.view.MotionEvent);
method public boolean onKeyDown(int, android.view.KeyEvent);
@@ -16614,6 +16691,7 @@
method public abstract void onSetCaptionEnabled(boolean);
method public abstract void onSetStreamVolume(float);
method public abstract boolean onSetSurface(android.view.Surface);
+ method public void onSurfaceChanged(int, int, int);
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
@@ -16621,6 +16699,19 @@
method public void setOverlayViewEnabled(boolean);
}
+ public final class TvParentalControlManager {
+ method public void addParentalControlCallback(android.media.tv.TvParentalControlManager.ParentalControlCallback, android.os.Handler);
+ method public final boolean isEnabled();
+ method public final boolean isRatingBlocked(android.media.tv.TvContentRating);
+ method public void removeParentalControlCallback(android.media.tv.TvParentalControlManager.ParentalControlCallback);
+ }
+
+ public static abstract class TvParentalControlManager.ParentalControlCallback {
+ ctor public TvParentalControlManager.ParentalControlCallback();
+ method public void onBlockedRatingsChanged();
+ method public void onEnabledChanged(boolean);
+ }
+
public final class TvTrackInfo implements android.os.Parcelable {
method public boolean containsKey(java.lang.String);
method public int describeContents();
@@ -16678,6 +16769,7 @@
public static abstract class TvView.TvInputListener {
ctor public TvView.TvInputListener();
method public void onChannelRetuned(java.lang.String, android.net.Uri);
+ method public void onContentBlocked(java.lang.String, android.media.tv.TvContentRating);
method public void onError(java.lang.String, int);
method public void onTrackInfoChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
method public void onVideoAvailable(java.lang.String);
@@ -24185,8 +24277,8 @@
field public static final java.lang.String PHOTO_FILE_ID = "data14";
}
- public static final class ContactsContract.Contacts.StreamItems implements android.provider.ContactsContract.StreamItemsColumns {
- field public static final java.lang.String CONTENT_DIRECTORY = "stream_items";
+ public static final deprecated class ContactsContract.Contacts.StreamItems implements android.provider.ContactsContract.StreamItemsColumns {
+ field public static final deprecated java.lang.String CONTENT_DIRECTORY = "stream_items";
}
protected static abstract interface ContactsContract.ContactsColumns {
@@ -24482,8 +24574,8 @@
field public static final java.lang.String DATA_ID = "data_id";
}
- public static final class ContactsContract.RawContacts.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns {
- field public static final java.lang.String CONTENT_DIRECTORY = "stream_items";
+ public static final deprecated class ContactsContract.RawContacts.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns {
+ field public static final deprecated java.lang.String CONTENT_DIRECTORY = "stream_items";
}
protected static abstract interface ContactsContract.RawContactsColumns {
@@ -24557,54 +24649,54 @@
field public static final android.net.Uri PROFILE_CONTENT_URI;
}
- public static final class ContactsContract.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns {
- field public static final java.lang.String PHOTO = "photo";
+ public static final deprecated class ContactsContract.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns {
+ field public static final deprecated java.lang.String PHOTO = "photo";
}
- protected static abstract interface ContactsContract.StreamItemPhotosColumns {
- field public static final java.lang.String PHOTO_FILE_ID = "photo_file_id";
- field public static final java.lang.String PHOTO_URI = "photo_uri";
- field public static final java.lang.String SORT_INDEX = "sort_index";
- field public static final java.lang.String STREAM_ITEM_ID = "stream_item_id";
- field public static final java.lang.String SYNC1 = "stream_item_photo_sync1";
- field public static final java.lang.String SYNC2 = "stream_item_photo_sync2";
- field public static final java.lang.String SYNC3 = "stream_item_photo_sync3";
- field public static final java.lang.String SYNC4 = "stream_item_photo_sync4";
+ protected static abstract deprecated interface ContactsContract.StreamItemPhotosColumns {
+ field public static final deprecated java.lang.String PHOTO_FILE_ID = "photo_file_id";
+ field public static final deprecated java.lang.String PHOTO_URI = "photo_uri";
+ field public static final deprecated java.lang.String SORT_INDEX = "sort_index";
+ field public static final deprecated java.lang.String STREAM_ITEM_ID = "stream_item_id";
+ field public static final deprecated java.lang.String SYNC1 = "stream_item_photo_sync1";
+ field public static final deprecated java.lang.String SYNC2 = "stream_item_photo_sync2";
+ field public static final deprecated java.lang.String SYNC3 = "stream_item_photo_sync3";
+ field public static final deprecated java.lang.String SYNC4 = "stream_item_photo_sync4";
}
- public static final class ContactsContract.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns {
- field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item";
- field public static final android.net.Uri CONTENT_LIMIT_URI;
- field public static final android.net.Uri CONTENT_PHOTO_URI;
- field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item";
- field public static final android.net.Uri CONTENT_URI;
- field public static final java.lang.String MAX_ITEMS = "max_items";
+ public static final deprecated class ContactsContract.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns {
+ field public static final deprecated java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item";
+ field public static final deprecated android.net.Uri CONTENT_LIMIT_URI;
+ field public static final deprecated android.net.Uri CONTENT_PHOTO_URI;
+ field public static final deprecated java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item";
+ field public static final deprecated android.net.Uri CONTENT_URI;
+ field public static final deprecated java.lang.String MAX_ITEMS = "max_items";
}
- public static final class ContactsContract.StreamItems.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns {
- field public static final java.lang.String CONTENT_DIRECTORY = "photo";
- field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item_photo";
- field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo";
+ public static final deprecated class ContactsContract.StreamItems.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns {
+ field public static final deprecated java.lang.String CONTENT_DIRECTORY = "photo";
+ field public static final deprecated java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item_photo";
+ field public static final deprecated java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo";
}
- protected static abstract interface ContactsContract.StreamItemsColumns {
- field public static final java.lang.String ACCOUNT_NAME = "account_name";
- field public static final java.lang.String ACCOUNT_TYPE = "account_type";
- field public static final java.lang.String COMMENTS = "comments";
- field public static final java.lang.String CONTACT_ID = "contact_id";
- field public static final java.lang.String CONTACT_LOOKUP_KEY = "contact_lookup";
- field public static final java.lang.String DATA_SET = "data_set";
- field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
- field public static final java.lang.String RAW_CONTACT_SOURCE_ID = "raw_contact_source_id";
- field public static final java.lang.String RES_ICON = "icon";
- field public static final java.lang.String RES_LABEL = "label";
- field public static final java.lang.String RES_PACKAGE = "res_package";
- field public static final java.lang.String SYNC1 = "stream_item_sync1";
- field public static final java.lang.String SYNC2 = "stream_item_sync2";
- field public static final java.lang.String SYNC3 = "stream_item_sync3";
- field public static final java.lang.String SYNC4 = "stream_item_sync4";
- field public static final java.lang.String TEXT = "text";
- field public static final java.lang.String TIMESTAMP = "timestamp";
+ protected static abstract deprecated interface ContactsContract.StreamItemsColumns {
+ field public static final deprecated java.lang.String ACCOUNT_NAME = "account_name";
+ field public static final deprecated java.lang.String ACCOUNT_TYPE = "account_type";
+ field public static final deprecated java.lang.String COMMENTS = "comments";
+ field public static final deprecated java.lang.String CONTACT_ID = "contact_id";
+ field public static final deprecated java.lang.String CONTACT_LOOKUP_KEY = "contact_lookup";
+ field public static final deprecated java.lang.String DATA_SET = "data_set";
+ field public static final deprecated java.lang.String RAW_CONTACT_ID = "raw_contact_id";
+ field public static final deprecated java.lang.String RAW_CONTACT_SOURCE_ID = "raw_contact_source_id";
+ field public static final deprecated java.lang.String RES_ICON = "icon";
+ field public static final deprecated java.lang.String RES_LABEL = "label";
+ field public static final deprecated java.lang.String RES_PACKAGE = "res_package";
+ field public static final deprecated java.lang.String SYNC1 = "stream_item_sync1";
+ field public static final deprecated java.lang.String SYNC2 = "stream_item_sync2";
+ field public static final deprecated java.lang.String SYNC3 = "stream_item_sync3";
+ field public static final deprecated java.lang.String SYNC4 = "stream_item_sync4";
+ field public static final deprecated java.lang.String TEXT = "text";
+ field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
protected static abstract interface ContactsContract.SyncColumns implements android.provider.ContactsContract.BaseSyncColumns {
@@ -28192,6 +28284,7 @@
method public final android.telecomm.Connection getParentConnection();
method public final int getState();
method public final android.telecomm.StatusHints getStatusHints();
+ method public final int getVideoState();
method public final boolean isConferenceConnection();
method public final boolean isRequestingRingback();
method protected void onAbort();
@@ -28225,6 +28318,7 @@
method public final void setRinging();
method public final void setSignal(android.os.Bundle);
method public final void setStatusHints(android.telecomm.StatusHints);
+ method public final void setVideoState(int);
method public static java.lang.String stateToString(int);
}
@@ -28345,27 +28439,29 @@
}
public class PhoneAccount implements android.os.Parcelable {
- ctor public PhoneAccount(android.content.ComponentName, java.lang.String, android.net.Uri, int);
+ ctor public PhoneAccount(android.content.ComponentName, java.lang.String);
method public int describeContents();
- method public int getCapabilities();
method public android.content.ComponentName getComponentName();
- method public android.net.Uri getHandle();
method public java.lang.String getId();
method public void writeToParcel(android.os.Parcel, int);
- field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2
- field public static final int CAPABILITY_SIM_CALL_MANAGER = 1; // 0x1
field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
field public static final android.os.Parcelable.Creator CREATOR;
}
public class PhoneAccountMetadata implements android.os.Parcelable {
- ctor public PhoneAccountMetadata(android.telecomm.PhoneAccount, int, java.lang.String, java.lang.String);
+ ctor public PhoneAccountMetadata(android.telecomm.PhoneAccount, android.net.Uri, int, int, java.lang.String, java.lang.String, boolean);
method public int describeContents();
method public android.telecomm.PhoneAccount getAccount();
+ method public int getCapabilities();
+ method public android.net.Uri getHandle();
method public android.graphics.drawable.Drawable getIcon(android.content.Context);
+ method public int getIconResId();
method public java.lang.String getLabel();
method public java.lang.String getShortDescription();
+ method public boolean isVideoCallingSupported();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2
+ field public static final int CAPABILITY_SIM_CALL_MANAGER = 1; // 0x1
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -28408,6 +28504,7 @@
method public int getHandlePresentation();
method public int getState();
method public android.telecomm.StatusHints getStatusHints();
+ method public int getVideoState();
method public void hold();
method public void playDtmf(char);
method public void postDialContinue(boolean);
@@ -28430,6 +28527,7 @@
method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
method public abstract void onStatusHintsChanged(android.telecomm.RemoteConnection, android.telecomm.StatusHints);
+ method public abstract void onVideoStateChanged(android.telecomm.RemoteConnection, int);
}
public abstract interface Response {
@@ -28473,9 +28571,10 @@
public class TelecommManager {
method public void clearAccounts(java.lang.String);
+ method public android.telecomm.PhoneAccount getDefaultOutgoingPhoneAccount();
method public java.util.List<android.telecomm.PhoneAccount> getEnabledPhoneAccounts();
method public android.telecomm.PhoneAccountMetadata getPhoneAccountMetadata(android.telecomm.PhoneAccount);
- method public void registerPhoneAccount(android.telecomm.PhoneAccount, android.telecomm.PhoneAccountMetadata);
+ method public void registerPhoneAccount(android.telecomm.PhoneAccountMetadata);
method public void unregisterPhoneAccount(android.telecomm.PhoneAccount);
}
@@ -31194,6 +31293,64 @@
method public android.text.style.TtsSpan.CardinalBuilder setNumber(java.lang.String);
}
+ public static class TtsSpan.DateBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.DateBuilder();
+ ctor public TtsSpan.DateBuilder(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer);
+ method public android.text.style.TtsSpan.DateBuilder setDay(int);
+ method public android.text.style.TtsSpan.DateBuilder setMonth(int);
+ method public android.text.style.TtsSpan.DateBuilder setWeekday(int);
+ method public android.text.style.TtsSpan.DateBuilder setYear(int);
+ }
+
+ public static class TtsSpan.DecimalBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.DecimalBuilder();
+ ctor public TtsSpan.DecimalBuilder(double, int, int);
+ ctor public TtsSpan.DecimalBuilder(java.lang.String, java.lang.String);
+ method public android.text.style.TtsSpan.DecimalBuilder setArgumentsFromDouble(double, int, int);
+ method public android.text.style.TtsSpan.DecimalBuilder setFractionalPart(java.lang.String);
+ method public android.text.style.TtsSpan.DecimalBuilder setIntegerPart(long);
+ method public android.text.style.TtsSpan.DecimalBuilder setIntegerPart(java.lang.String);
+ }
+
+ public static class TtsSpan.DigitsBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.DigitsBuilder();
+ ctor public TtsSpan.DigitsBuilder(java.lang.String);
+ method public android.text.style.TtsSpan.DigitsBuilder setDigits(java.lang.String);
+ }
+
+ public static class TtsSpan.FractionBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.FractionBuilder();
+ ctor public TtsSpan.FractionBuilder(long, long, long);
+ method public android.text.style.TtsSpan.FractionBuilder setDenominator(long);
+ method public android.text.style.TtsSpan.FractionBuilder setDenominator(java.lang.String);
+ method public android.text.style.TtsSpan.FractionBuilder setIntegerPart(long);
+ method public android.text.style.TtsSpan.FractionBuilder setIntegerPart(java.lang.String);
+ method public android.text.style.TtsSpan.FractionBuilder setNumerator(long);
+ method public android.text.style.TtsSpan.FractionBuilder setNumerator(java.lang.String);
+ }
+
+ public static class TtsSpan.MeasureBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.MeasureBuilder();
+ method public android.text.style.TtsSpan.MeasureBuilder setDenominator(long);
+ method public android.text.style.TtsSpan.MeasureBuilder setDenominator(java.lang.String);
+ method public android.text.style.TtsSpan.MeasureBuilder setFractionalPart(java.lang.String);
+ method public android.text.style.TtsSpan.MeasureBuilder setIntegerPart(long);
+ method public android.text.style.TtsSpan.MeasureBuilder setIntegerPart(java.lang.String);
+ method public android.text.style.TtsSpan.MeasureBuilder setNumber(long);
+ method public android.text.style.TtsSpan.MeasureBuilder setNumber(java.lang.String);
+ method public android.text.style.TtsSpan.MeasureBuilder setNumerator(long);
+ method public android.text.style.TtsSpan.MeasureBuilder setNumerator(java.lang.String);
+ method public android.text.style.TtsSpan.MeasureBuilder setUnit(java.lang.String);
+ }
+
+ public static class TtsSpan.OrdinalBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.OrdinalBuilder();
+ ctor public TtsSpan.OrdinalBuilder(long);
+ ctor public TtsSpan.OrdinalBuilder(java.lang.String);
+ method public android.text.style.TtsSpan.OrdinalBuilder setNumber(long);
+ method public android.text.style.TtsSpan.OrdinalBuilder setNumber(java.lang.String);
+ }
+
public static class TtsSpan.SemioticClassBuilder extends android.text.style.TtsSpan.Builder {
ctor public TtsSpan.SemioticClassBuilder(java.lang.String);
method public C setAnimacy(java.lang.String);
@@ -31208,6 +31365,19 @@
method public android.text.style.TtsSpan.TextBuilder setText(java.lang.String);
}
+ public static class TtsSpan.TimeBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.TimeBuilder();
+ ctor public TtsSpan.TimeBuilder(int, int);
+ method public android.text.style.TtsSpan.TimeBuilder setHours(int);
+ method public android.text.style.TtsSpan.TimeBuilder setMinutes(int);
+ }
+
+ public static class TtsSpan.VerbatimBuilder extends android.text.style.TtsSpan.SemioticClassBuilder {
+ ctor public TtsSpan.VerbatimBuilder();
+ ctor public TtsSpan.VerbatimBuilder(java.lang.String);
+ method public android.text.style.TtsSpan.VerbatimBuilder setVerbatim(java.lang.String);
+ }
+
public class TypefaceSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public TypefaceSpan(java.lang.String);
ctor public TypefaceSpan(android.os.Parcel);
@@ -36668,6 +36838,7 @@
method public void setWebViewClient(android.webkit.WebViewClient);
method public deprecated boolean showFindDialog(java.lang.String, boolean);
method public void stopLoading();
+ method public boolean zoomBy(float);
method public boolean zoomIn();
method public boolean zoomOut();
field public static final java.lang.String SCHEME_GEO = "geo:0,0?q=";
@@ -48474,6 +48645,13 @@
method public int getWidth();
}
+ public class IllformedLocaleException extends java.lang.RuntimeException {
+ ctor public IllformedLocaleException();
+ ctor public IllformedLocaleException(java.lang.String);
+ ctor public IllformedLocaleException(java.lang.String, int);
+ method public int getErrorIndex();
+ }
+
public class InputMismatchException extends java.util.NoSuchElementException implements java.io.Serializable {
ctor public InputMismatchException();
ctor public InputMismatchException(java.lang.String);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 739b81c..c91c90c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -192,8 +192,10 @@
public static final int OP_MUTE_MICROPHONE = 44;
/** @hide */
public static final int OP_TOAST_WINDOW = 45;
+ /** @hide Capture the device's display contents and/or audio */
+ public static final int OP_PROJECT_MEDIA = 46;
/** @hide */
- public static final int _NUM_OP = 46;
+ public static final int _NUM_OP = 47;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION =
@@ -263,6 +265,7 @@
OP_GET_USAGE_STATS,
OP_MUTE_MICROPHONE,
OP_TOAST_WINDOW,
+ OP_PROJECT_MEDIA,
};
/**
@@ -316,6 +319,7 @@
null,
null,
null,
+ null,
};
/**
@@ -367,8 +371,9 @@
"MONITOR_LOCATION",
"MONITOR_HIGH_POWER_LOCATION",
"GET_USAGE_STATS",
- "OP_MUTE_MICROPHONE",
+ "MUTE_MICROPHONE",
"TOAST_WINDOW",
+ "PROJECT_MEDIA",
};
/**
@@ -422,6 +427,7 @@
android.Manifest.permission.PACKAGE_USAGE_STATS,
null, // no permission for muting/unmuting microphone
null, // no permission for displaying toasts
+ null, // no permission for projecting media
};
/**
@@ -476,6 +482,7 @@
null, //GET_USAGE_STATS
UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
+ null, //PROJECT_MEDIA
};
/**
@@ -527,8 +534,9 @@
false, //MONITOR_LOCATION
false, //MONITOR_HIGH_POWER_LOCATION
false, //GET_USAGE_STATS
- false, // MUTE_MICROPHONE
- true, // TOAST_WINDOW
+ false, //MUTE_MICROPHONE
+ true, //TOAST_WINDOW
+ false, //PROJECT_MEDIA
};
/**
@@ -581,6 +589,7 @@
AppOpsManager.MODE_IGNORED, // OP_GET_USAGE_STATS
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
};
/**
@@ -637,6 +646,7 @@
false,
false,
false,
+ false,
};
private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cbfde14..2fd3443 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -72,8 +72,10 @@
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
+import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.media.tv.ITvInputManager;
+import android.media.tv.TvParentalControlManager;
import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
@@ -729,6 +731,11 @@
return new TvInputManager(service, UserHandle.myUserId());
}});
+ registerService(TV_PARENTAL_CONTROL_SERVICE, new ServiceFetcher() {
+ public Object getService(ContextImpl ctx) {
+ return new TvParentalControlManager(ctx);
+ }});
+
registerService(NETWORK_SCORE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new NetworkScoreManager(ctx);
@@ -752,6 +759,12 @@
return new PersistentDataBlockManager(
IPersistentDataBlockService.Stub.asInterface(b));
}});
+
+ registerService(MEDIA_PROJECTION_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new MediaProjectionManager(ctx);
+ }
+ });
}
static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 61ed631..bb9032a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -212,17 +212,22 @@
public PendingIntent fullScreenIntent;
/**
- * Text to scroll across the screen when this item is added to
- * the status bar on large and smaller devices.
+ * Text that summarizes this notification for accessibility services.
+ *
+ * As of the L release, this text is no longer shown on screen, but it is still useful to
+ * accessibility services (where it serves as an audible announcement of the notification's
+ * appearance).
*
* @see #tickerView
*/
public CharSequence tickerText;
/**
- * The view to show as the ticker in the status bar when the notification
- * is posted.
+ * Formerly, a view showing the {@link #tickerText}.
+ *
+ * No longer displayed in the status bar as of API 21.
*/
+ @Deprecated
public RemoteViews tickerView;
/**
@@ -1576,7 +1581,6 @@
} else {
sb.append("null");
}
- // TODO(dsandler): defaults take precedence over local values, so reorder the branches below
sb.append(" vibrate=");
if ((this.defaults & DEFAULT_VIBRATE) != 0) {
sb.append("default");
@@ -1620,15 +1624,35 @@
sb.append(this.mSortKey);
}
if (actions != null) {
- sb.append(" ");
+ sb.append(" actions=");
sb.append(actions.length);
- sb.append(" action");
- if (actions.length > 1) sb.append("s");
+ }
+ sb.append(" vis=");
+ sb.append(visibilityToString(this.visibility));
+ if (this.publicVersion != null) {
+ sb.append(" publicVersion=");
+ sb.append(publicVersion.toString());
}
sb.append(")");
return sb.toString();
}
+ /**
+ * {@hide}
+ */
+ public static String visibilityToString(int vis) {
+ switch (vis) {
+ case VISIBILITY_PRIVATE:
+ return "PRIVATE";
+ case VISIBILITY_PUBLIC:
+ return "PUBLIC";
+ case VISIBILITY_SECRET:
+ return "SECRET";
+ default:
+ return "UNKNOWN(" + String.valueOf(vis) + ")";
+ }
+ }
+
/** {@hide} */
public void setUser(UserHandle user) {
if (user.getIdentifier() == UserHandle.USER_ALL) {
@@ -1950,8 +1974,7 @@
}
/**
- * Set the "ticker" text which is displayed in the status bar when the notification first
- * arrives.
+ * Set the "ticker" text which is sent to accessibility services.
*
* @see Notification#tickerText
*/
@@ -1961,16 +1984,13 @@
}
/**
- * Set the text that is displayed in the status bar when the notification first
- * arrives, and also a RemoteViews object that may be displayed instead on some
- * devices.
+ * Obsolete version of {@link #setTicker(CharSequence)}.
*
- * @see Notification#tickerText
- * @see Notification#tickerView
*/
+ @Deprecated
public Builder setTicker(CharSequence tickerText, RemoteViews views) {
mTickerText = safeCharSequence(tickerText);
- mTickerView = views;
+ mTickerView = views; // we'll save it for you anyway
return this;
}
@@ -2511,15 +2531,8 @@
private RemoteViews makeTickerView() {
if (mTickerView != null) {
return mTickerView;
- } else {
- if (mContentView == null) {
- return applyStandardTemplate(mLargeIcon == null
- ? R.layout.status_bar_latest_event_ticker
- : R.layout.status_bar_latest_event_ticker_large_icon, true);
- } else {
- return null;
- }
}
+ return null; // tickers are not created by default anymore
}
private RemoteViews makeBigContentView() {
@@ -3223,6 +3236,10 @@
* {@link #setShowActionsInCompactView(int...)} you can promote up to 2 actions to be displayed
* in the standard view alongside the usual content.
*
+ * Notifications created with MediaStyle will have their category set to
+ * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different
+ * category using {@link Notification.Builder#setCategory(String) setCategory()}.
+ *
* Finally, if you attach a {@link android.media.session.MediaSession.Token} using
* {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)},
* the System UI can identify this as a notification representing an active media session
@@ -3235,9 +3252,9 @@
* .setSmallIcon(R.drawable.ic_stat_player)
* .setContentTitle("Track title") // these three lines are optional
* .setContentText("Artist - Album") // if you use
- * .setLargeIcon(albumArtBitmap)) // setMediaSession(token, true)
- * .setMediaSession(mySession, true)
- * .setStyle(<b>new Notification.MediaStyle()</b>)
+ * .setLargeIcon(albumArtBitmap)) // setMediaSession(token)
+ * .setStyle(<b>new Notification.MediaStyle()</b>
+ * .setMediaSession(mySession))
* .build();
* </pre>
*
@@ -3279,7 +3296,9 @@
public Notification buildStyled(Notification wip) {
wip.contentView = makeMediaContentView();
wip.bigContentView = makeMediaBigContentView();
-
+ if (wip.category == null) {
+ wip.category = Notification.CATEGORY_TRANSPORT;
+ }
return wip;
}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index ff2a174..a2f3050 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -113,7 +113,6 @@
if (targetSdkVersion < Build.VERSION_CODES.L) {
setIcon(0);
setTitle(R.string.time_picker_dialog_title);
- setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
}
final LayoutInflater inflater = LayoutInflater.from(themeContext);
@@ -122,6 +121,11 @@
mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
mTimePicker.setShowDoneButton(true);
+ // If time picker layout has no done button, add a dialog button.
+ if (!mTimePicker.isShowDoneButton()) {
+ setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
+ }
+
mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
@Override
public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index a287b3c..f8684e1 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -93,6 +93,25 @@
public static final int GATT_FAILURE = 0x101;
/**
+ * Connection paramter update - Use the connection paramters recommended by the
+ * Bluetooth SIG. This is the default value if no connection parameter update
+ * is requested.
+ */
+ public static final int GATT_CONNECTION_BALANCED = 0;
+
+ /**
+ * Connection paramter update - Request a high priority, low latency connection.
+ * An application should only request high priority connection paramters to transfer
+ * large amounts of data over LE quickly. Once the transfer is complete, the application
+ * should request {@link BluetoothGatt#GATT_CONNECTION_BALANCED} connectoin parameters
+ * to reduce energy use.
+ */
+ public static final int GATT_CONNECTION_HIGH_PRIORITY = 1;
+
+ /** Connection paramter update - Request low power, reduced data rate connection parameters. */
+ public static final int GATT_CONNECTION_LOW_POWER = 2;
+
+ /**
* No authentication required.
* @hide
*/
@@ -738,7 +757,7 @@
* {@link BluetoothGattCallback#onConnectionStateChange} callback will be
* invoked when the connection state changes as a result of this function.
*
- * <p>The autoConnect paramter determines whether to actively connect to
+ * <p>The autoConnect parameter determines whether to actively connect to
* the remote device, or rather passively scan and finalize the connection
* when the remote device is in range/available. Generally, the first ever
* connection to a device should be direct (autoConnect set to false) and
@@ -1287,6 +1306,38 @@
}
/**
+ * Request a connection parameter update.
+ *
+ * <p>This function will send a connection parameter update request to the
+ * remote device.
+ *
+ * @param connectionPriority Request a specific connection priority. Must be one of
+ * {@link BluetoothGatt#GATT_CONNECTION_BALANCED},
+ * {@link BluetoothGatt#GATT_CONNECTION_HIGH_PRIORITY}
+ * or {@link BluetoothGatt#GATT_CONNECTION_LOW_POWER}.
+ * @throws IllegalArgumentException If the parameters are outside of their
+ * specified range.
+ */
+ public boolean requestConnectionParameterUpdate(int connectionPriority) {
+ if (connectionPriority < GATT_CONNECTION_BALANCED ||
+ connectionPriority > GATT_CONNECTION_LOW_POWER) {
+ throw new IllegalArgumentException("connectionPriority not within valid range");
+ }
+
+ if (DBG) Log.d(TAG, "requestConnectionParameterUpdate() - params: " + connectionPriority);
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
* with {@link BluetoothProfile#GATT} as argument
*
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 6d4b9cd..533be13 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -76,6 +76,7 @@
void endReliableWrite(in int clientIf, in String address, in boolean execute);
void readRemoteRssi(in int clientIf, in String address);
void configureMTU(in int clientIf, in String address, in int mtu);
+ void connectionParameterUpdate(in int clientIf, in String address, in int connectionPriority);
void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
void unregisterServer(in int serverIf);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1dd018f..b7344b7 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2822,6 +2822,16 @@
public static final String TV_INPUT_SERVICE = "tv_input";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.tv.TvParentalControlManager} for obtaining parental
+ * control settings and listening to their changes.
+ *
+ * @see #getSystemService
+ * @see android.media.tv.TvParentalControlManager
+ */
+ public static final String TV_PARENTAL_CONTROL_SERVICE = "tv_parental_control";
+
+ /**
* {@link android.net.NetworkScoreManager} for managing network scoring.
* @see #getSystemService
* @see android.net.NetworkScoreManager
@@ -2859,6 +2869,15 @@
public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
/**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.media.projection.MediaProjectionManager} instance for managing
+ * media projection sessions.
+ * @see #getSystemService
+ * @see android.media.projection.ProjectionManager
+ */
+ public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index de2cc67..a5c2f63 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -737,19 +737,51 @@
* Values of events created by this sensors should not be used.
*
* @see #isWakeUpSensor()
- * @hide This sensor is expected to only be used by the power manager
+ * @hide This sensor is expected to only be used by the system ui
*/
public static final int TYPE_WAKE_GESTURE = 42;
/**
* A constant string describing a wake gesture sensor.
*
- * @hide This sensor is expected to only be used by the power manager
+ * @hide This sensor is expected to only be used by the system ui
* @see #TYPE_WAKE_GESTURE
*/
public static final String STRING_TYPE_WAKE_GESTURE = "android.sensor.wake_gesture";
/**
+ * A constant describing a wake gesture sensor.
+ * <p>
+ * A sensor enabling briefly turning the screen on to enable the user to
+ * glance content on screen based on a specific motion. The device should
+ * turn the screen off after a few moments.
+ * <p>
+ * When this sensor triggers, the device turns the screen on momentarily
+ * to allow the user to glance notifications or other content while the
+ * device remains locked in a non-interactive state (dozing). This behavior
+ * (briefly turning on the screen when this sensor triggers) might be deactivated
+ * by the user in the device settings. Changes in settings do not impact the
+ * behavior of the sensor: only whether the framework briefly turns the screen on
+ * when it triggers.
+ * <p>
+ * The actual gesture to be detected is not specified, and can be chosen by the manufacturer of
+ * the device. This sensor must be low power, as it is likely to be activated 24/7.
+ * Values of events created by this sensors should not be used.
+ *
+ * @see #isWakeUpSensor()
+ * @hide This sensor is expected to only be used by the system ui
+ */
+ public static final int TYPE_GLANCE_GESTURE = 43;
+
+ /**
+ * A constant string describing a wake gesture sensor.
+ *
+ * @hide This sensor is expected to only be used by the system ui
+ * @see #TYPE_GLANCE_GESTURE
+ */
+ public static final String STRING_TYPE_GLANCE_GESTURE = "android.sensor.glance_gesture";
+
+ /**
* A constant describing all sensor types.
*/
public static final int TYPE_ALL = -1;
@@ -847,6 +879,7 @@
1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
1, // SENSOR_TYPE_WAKE_GESTURE
+ 1, // SENSOR_TYPE_GLANCE_GESTURE
};
/**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ff86120..0e4b506 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -858,6 +858,7 @@
* then immediately dispatch this state via a partial result to
* the application, and the rest of the metadata via later
* partial results.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*/
public static final Key<Integer> REQUEST_PARTIAL_RESULT_COUNT =
new Key<Integer>("android.request.partialResultCount", int.class);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 79673b3..ce7a2a4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,7 +16,10 @@
package android.hardware.display;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.media.projection.MediaProjection;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Display;
@@ -188,6 +191,22 @@
*/
public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;
+
+ /**
+ * Virtual display flag: Indicates that the display is being created for
+ * the purpose of screen sharing. This implies
+ * VIRTUAL_DISPLAY_FLAG_PRIVATE. Other flags are not allowed (especially
+ * not VIRTUAL_DISPLAY_FLAG_PUBLIC or PRESENTATION).
+ *
+ * Requires screen share permission for use.
+ *
+ * While a display of this type exists, the system will show some sort of
+ * notification to the user indicating that the screen is being shared.
+ *
+ * @see #createVirtualDisplay
+ */
+ public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 1 << 4;
+
/** @hide */
public DisplayManager(Context context) {
mContext = context;
@@ -303,7 +322,7 @@
}
/**
- * Unregisters an input device listener.
+ * Unregisters a display listener.
*
* @param listener The listener to unregister.
*
@@ -425,6 +444,16 @@
/**
* Creates a virtual display.
+ *
+ * @see #createVirtualDisplay(String, int, int, int, Surface, int, VirtualDisplay.Callbacks)
+ */
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
+ return createVirtualDisplay(name, width, height, densityDpi, surface, flags, null, null);
+ }
+
+ /**
+ * Creates a virtual display.
* <p>
* The content of a virtual display is rendered to a {@link Surface} provided
* by the application.
@@ -455,17 +484,28 @@
* be rendered, or null if there is none initially.
* @param flags A combination of virtual display flags:
* {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
- * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, or {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+ * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
+ * or {@link #VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE}.
+ * @param callbacks Callbacks to call when the state of the {@link VirtualDisplay} changes
* @return The newly created virtual display, or null if the application could
* not create the virtual display.
*
* @throws SecurityException if the caller does not have permission to create
* a virtual display with the specified flags.
*/
- public VirtualDisplay createVirtualDisplay(String name,
- int width, int height, int densityDpi, Surface surface, int flags) {
- return mGlobal.createVirtualDisplay(mContext,
- name, width, height, densityDpi, surface, flags);
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ int width, int height, int densityDpi, @Nullable Surface surface, int flags,
+ @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+ return createVirtualDisplay(null,
+ name, width, height, densityDpi, surface, flags, callbacks, handler);
+ }
+
+ /** @hide */
+ public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
+ @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
+ int flags, @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+ return mGlobal.createVirtualDisplay(mContext, projection,
+ name, width, height, densityDpi, surface, flags, callbacks, handler);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a8d55e8..f2426e5 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.media.projection.MediaProjection;
+import android.media.projection.IMediaProjection;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -368,8 +370,9 @@
}
}
- public VirtualDisplay createVirtualDisplay(Context context, String name,
- int width, int height, int densityDpi, Surface surface, int flags) {
+ public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
+ String name, int width, int height, int densityDpi, Surface surface, int flags,
+ VirtualDisplay.Callbacks callbacks, Handler handler) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("name must be non-null and non-empty");
}
@@ -378,11 +381,12 @@
+ "greater than 0");
}
- Binder token = new Binder();
+ VirtualDisplayCallbacks callbackWrapper = new VirtualDisplayCallbacks(callbacks, handler);
+ IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
int displayId;
try {
- displayId = mDm.createVirtualDisplay(token, context.getPackageName(),
- name, width, height, densityDpi, surface, flags);
+ displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
+ context.getPackageName(), name, width, height, densityDpi, surface, flags);
} catch (RemoteException ex) {
Log.e(TAG, "Could not create virtual display: " + name, ex);
return null;
@@ -396,15 +400,15 @@
Log.wtf(TAG, "Could not obtain display info for newly created "
+ "virtual display: " + name);
try {
- mDm.releaseVirtualDisplay(token);
+ mDm.releaseVirtualDisplay(callbackWrapper);
} catch (RemoteException ex) {
}
return null;
}
- return new VirtualDisplay(this, display, token, surface);
+ return new VirtualDisplay(this, display, callbackWrapper, surface);
}
- public void setVirtualDisplaySurface(IBinder token, Surface surface) {
+ public void setVirtualDisplaySurface(IVirtualDisplayCallbacks token, Surface surface) {
try {
mDm.setVirtualDisplaySurface(token, surface);
} catch (RemoteException ex) {
@@ -412,7 +416,7 @@
}
}
- public void releaseVirtualDisplay(IBinder token) {
+ public void releaseVirtualDisplay(IVirtualDisplayCallbacks token) {
try {
mDm.releaseVirtualDisplay(token);
} catch (RemoteException ex) {
@@ -462,4 +466,59 @@
}
}
}
+
+ private final static class VirtualDisplayCallbacks extends IVirtualDisplayCallbacks.Stub {
+ private VirtualDisplayCallbacksDelegate mDelegate;
+
+ public VirtualDisplayCallbacks(VirtualDisplay.Callbacks callbacks, Handler handler) {
+ mDelegate = new VirtualDisplayCallbacksDelegate(callbacks, handler);
+ }
+
+ @Override // Binder call
+ public void onDisplayPaused() {
+ mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_PAUSED);
+ }
+
+ @Override // Binder call
+ public void onDisplayResumed() {
+ mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_RESUMED);
+ }
+
+ @Override // Binder call
+ public void onDisplayStopped() {
+ mDelegate.sendEmptyMessage(VirtualDisplayCallbacksDelegate.MSG_DISPLAY_STOPPED);
+ }
+ }
+
+ private final static class VirtualDisplayCallbacksDelegate extends Handler {
+ public static final int MSG_DISPLAY_PAUSED = 0;
+ public static final int MSG_DISPLAY_RESUMED = 1;
+ public static final int MSG_DISPLAY_STOPPED = 2;
+
+ private final VirtualDisplay.Callbacks mCallbacks;
+
+ public VirtualDisplayCallbacksDelegate(VirtualDisplay.Callbacks callbacks,
+ Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCallbacks == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_DISPLAY_PAUSED:
+ mCallbacks.onDisplayPaused();
+ break;
+ case MSG_DISPLAY_RESUMED:
+ mCallbacks.onDisplayResumed();
+ break;
+ case MSG_DISPLAY_STOPPED:
+ mCallbacks.onDisplayStopped();
+ break;
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 23c58c8..44ffbc4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -17,8 +17,10 @@
package android.hardware.display;
import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallbacks;
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplayStatus;
+import android.media.projection.IMediaProjection;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -57,14 +59,15 @@
// No permissions required.
WifiDisplayStatus getWifiDisplayStatus();
- // Requires CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT for certain
- // combinations of flags.
- int createVirtualDisplay(IBinder token, String packageName,
- String name, int width, int height, int densityDpi, in Surface surface, int flags);
+ // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
+ // MediaProjection token for certain combinations of flags.
+ int createVirtualDisplay(in IVirtualDisplayCallbacks callbacks,
+ in IMediaProjection projectionToken, String packageName, String name,
+ int width, int height, int densityDpi, in Surface surface, int flags);
// No permissions required but must be same Uid as the creator.
- void setVirtualDisplaySurface(in IBinder token, in Surface surface);
+ void setVirtualDisplaySurface(in IVirtualDisplayCallbacks token, in Surface surface);
// No permissions required but must be same Uid as the creator.
- void releaseVirtualDisplay(in IBinder token);
+ void releaseVirtualDisplay(in IVirtualDisplayCallbacks token);
}
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl b/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl
new file mode 100644
index 0000000..a1cdc01
--- /dev/null
+++ b/core/java/android/hardware/display/IVirtualDisplayCallbacks.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.display;
+
+/** @hide */
+oneway interface IVirtualDisplayCallbacks {
+ /**
+ * Called when the virtual display video projection has been
+ * paused by the system or when the surface has been detached
+ * by the application by calling setSurface(null).
+ * The surface will not receive any more buffers while paused.
+ */
+ void onDisplayPaused();
+
+ /**
+ * Called when the virtual display video projection has been
+ * resumed after having been paused.
+ */
+ void onDisplayResumed();
+
+ /**
+ * Called when the virtual display video projection has been
+ * stopped by the system. It will no longer receive frames
+ * and it will never be resumed. It is still the responsibility
+ * of the application to release() the virtual display.
+ */
+ void onDisplayStopped();
+}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 691d6a0..df6116b 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -35,11 +35,11 @@
public final class VirtualDisplay {
private final DisplayManagerGlobal mGlobal;
private final Display mDisplay;
- private IBinder mToken;
+ private IVirtualDisplayCallbacks mToken;
private Surface mSurface;
- VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token,
- Surface surface) {
+ VirtualDisplay(DisplayManagerGlobal global, Display display,
+ IVirtualDisplayCallbacks token, Surface surface) {
mGlobal = global;
mDisplay = display;
mToken = token;
@@ -98,4 +98,31 @@
return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
+ ", surface=" + mSurface + "}";
}
+
+ /**
+ * Interface for receiving information about a {@link VirtualDisplay}'s state changes.
+ */
+ public static abstract class Callbacks {
+ /**
+ * Called when the virtual display video projection has been
+ * paused by the system or when the surface has been detached
+ * by the application by calling setSurface(null).
+ * The surface will not receive any more buffers while paused.
+ */
+ public void onDisplayPaused() { }
+
+ /**
+ * Called when the virtual display video projection has been
+ * resumed after having been paused.
+ */
+ public void onDisplayResumed() { }
+
+ /**
+ * Called when the virtual display video projection has been
+ * stopped by the system. It will no longer receive frames
+ * and it will never be resumed. It is still the responsibility
+ * of the application to release() the virtual display.
+ */
+ public void onDisplayStopped() { }
+ }
}
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
index 0a89207..16cfe6e 100644
--- a/core/java/android/hardware/location/ActivityChangedEvent.java
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -76,4 +76,17 @@
parcel.writeInt(activityRecognitionEventArray.length);
parcel.writeTypedArray(activityRecognitionEventArray, flags);
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
index 5aeb899..190030a 100644
--- a/core/java/android/hardware/location/ActivityRecognitionEvent.java
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -75,4 +75,13 @@
parcel.writeInt(mEventType);
parcel.writeLong(mTimestampNs);
}
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Activity='%s', EventType=%s, TimestampNs=%s",
+ mActivity,
+ mEventType,
+ mTimestampNs);
+ }
}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
index a4ce4ac..5d3953a 100644
--- a/core/java/android/hardware/location/ActivityRecognitionHardware.java
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -134,8 +134,8 @@
* Called by the Activity-Recognition HAL.
*/
private void onActivityChanged(Event[] events) {
- int size = mSinks.beginBroadcast();
- if (size == 0 || events == null || events.length == 0) {
+ if (events == null || events.length == 0) {
+ Log.d(TAG, "No events to broadcast for onActivityChanged.");
return;
}
@@ -151,6 +151,7 @@
ActivityChangedEvent activityChangedEvent =
new ActivityChangedEvent(activityRecognitionEventArray);
+ int size = mSinks.beginBroadcast();
for (int i = 0; i < size; ++i) {
IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
try {
@@ -181,8 +182,8 @@
return INVALID_ACTIVITY_TYPE;
}
- int supporteActivitiesLength = mSupportedActivities.length;
- for (int i = 0; i < supporteActivitiesLength; ++i) {
+ int supportedActivitiesLength = mSupportedActivities.length;
+ for (int i = 0; i < supportedActivitiesLength; ++i) {
if (activity.equals(mSupportedActivities[i])) {
return i;
}
@@ -198,7 +199,7 @@
mContext.enforceCallingPermission(HARDWARE_PERMISSION, message);
}
- private static String[] fetchSupportedActivities() {
+ private String[] fetchSupportedActivities() {
String[] supportedActivities = nativeGetSupportedActivities();
if (supportedActivities != null) {
return supportedActivities;
@@ -211,14 +212,15 @@
static { nativeClassInit(); }
private static native void nativeClassInit();
- private static native void nativeInitialize();
- private static native void nativeRelease();
private static native boolean nativeIsSupported();
- private static native String[] nativeGetSupportedActivities();
- private static native int nativeEnableActivityEvent(
+
+ private native void nativeInitialize();
+ private native void nativeRelease();
+ private native String[] nativeGetSupportedActivities();
+ private native int nativeEnableActivityEvent(
int activityType,
int eventType,
long reportLatenceNs);
- private static native int nativeDisableActivityEvent(int activityType, int eventType);
- private static native int nativeFlush();
+ private native int nativeDisableActivityEvent(int activityType, int eventType);
+ private native int nativeFlush();
}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index bfe90e6..829d459 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1721,16 +1721,28 @@
* Querying for social stream data requires android.permission.READ_SOCIAL_STREAM
* permission.
* </p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final class StreamItems implements StreamItemsColumns {
/**
* no public constructor since this is a utility class
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
private StreamItems() {}
/**
* The directory twig for this sub-table
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_DIRECTORY = "stream_items";
}
@@ -2830,17 +2842,29 @@
* inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM
* permission.
* </p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
* No public constructor since this is a utility class
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
private StreamItems() {
}
/**
* The directory twig for this sub-table
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_DIRECTORY = "stream_items";
}
@@ -3255,18 +3279,30 @@
* </pre>
* </dd>
* </dl>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
* This utility class cannot be instantiated
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
private StreamItems() {
}
/**
* The content:// style URI for this table, which handles social network stream
* updates for the user's contacts.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "stream_items");
/**
@@ -3281,31 +3317,51 @@
* When using this URI, the stream item ID for the photo(s) must be identified
* in the {@link ContentValues} passed in.
* </p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final Uri CONTENT_PHOTO_URI = Uri.withAppendedPath(CONTENT_URI, "photo");
/**
* This URI allows the caller to query for the maximum number of stream items
* that will be stored under any single raw contact.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final Uri CONTENT_LIMIT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "stream_items_limit");
/**
* The MIME type of a directory of stream items.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item";
/**
* The MIME type of a single stream item.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item";
/**
* Queries to {@link ContactsContract.StreamItems#CONTENT_LIMIT_URI} will
* contain this column, with the value indicating the maximum number of
* stream items that will be stored under any single raw contact.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String MAX_ITEMS = "max_items";
/**
@@ -3321,28 +3377,48 @@
* requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating
* social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission.
* </p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final class StreamItemPhotos
implements BaseColumns, StreamItemPhotosColumns {
/**
* No public constructor since this is a utility class
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
private StreamItemPhotos() {
}
/**
* The directory twig for this sub-table
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_DIRECTORY = "photo";
/**
* The MIME type of a directory of stream item photos.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo";
/**
* The MIME type of a single stream item photo.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/stream_item_photo";
}
@@ -3352,7 +3428,10 @@
* Columns in the StreamItems table.
*
* @see ContactsContract.StreamItems
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
protected interface StreamItemsColumns {
/**
* A reference to the {@link android.provider.ContactsContract.Contacts#_ID}
@@ -3360,7 +3439,11 @@
*
* <p>Type: INTEGER</p>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTACT_ID = "contact_id";
/**
@@ -3369,14 +3452,22 @@
*
* <p>Type: TEXT</p>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String CONTACT_LOOKUP_KEY = "contact_lookup";
/**
* A reference to the {@link RawContacts#_ID}
* that this stream item belongs to.
* <p>Type: INTEGER</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String RAW_CONTACT_ID = "raw_contact_id";
/**
@@ -3384,7 +3475,11 @@
* this stream item. This value is only designed for use when building
* user interfaces, and should not be used to infer the owner.
* <P>Type: TEXT</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String RES_PACKAGE = "res_package";
/**
@@ -3393,7 +3488,11 @@
*
* <p>Type: TEXT</p>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String ACCOUNT_TYPE = "account_type";
/**
@@ -3402,7 +3501,11 @@
*
* <p>Type: TEXT</p>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String ACCOUNT_NAME = "account_name";
/**
@@ -3413,7 +3516,11 @@
*
* <P>Type: TEXT</P>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String DATA_SET = "data_set";
/**
@@ -3422,7 +3529,11 @@
*
* <P>Type: TEXT</P>
* <p>read-only</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String RAW_CONTACT_SOURCE_ID = "raw_contact_source_id";
/**
@@ -3430,7 +3541,11 @@
* This resource should be scoped by the {@link #RES_PACKAGE}. As this can only reference
* drawables, the "@drawable/" prefix must be omitted.
* <P>Type: TEXT</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String RES_ICON = "icon";
/**
@@ -3438,7 +3553,11 @@
* Talk". This resource should be scoped by the {@link #RES_PACKAGE}. As this can only
* reference strings, the "@string/" prefix must be omitted.
* <p>Type: TEXT</p>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String RES_LABEL = "label";
/**
@@ -3455,14 +3574,22 @@
* is unspecified, but it should not break tags.
* </P>
* <P>Type: TEXT</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String TEXT = "text";
/**
* The absolute time (milliseconds since epoch) when this stream item was
* inserted/updated.
* <P>Type: NUMBER</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String TIMESTAMP = "timestamp";
/**
@@ -3480,16 +3607,44 @@
* is unspecified, but it should not break tags.
* </P>
* <P>Type: TEXT</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String COMMENTS = "comments";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC1 = "stream_item_sync1";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC2 = "stream_item_sync2";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC3 = "stream_item_sync3";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC4 = "stream_item_sync4";
}
@@ -3662,11 +3817,19 @@
* <pre>
* </dd>
* </dl>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns {
/**
* No public constructor since this is a utility class
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
private StreamItemPhotos() {
}
@@ -3681,7 +3844,11 @@
* as an asset file.
* </p>
* <P>Type: BLOB</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String PHOTO = "photo";
}
@@ -3689,42 +3856,85 @@
* Columns in the StreamItemPhotos table.
*
* @see ContactsContract.StreamItemPhotos
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
protected interface StreamItemPhotosColumns {
/**
* A reference to the {@link StreamItems#_ID} this photo is associated with.
* <P>Type: NUMBER</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String STREAM_ITEM_ID = "stream_item_id";
/**
* An integer to use for sort order for photos in the stream item. If not
* specified, the {@link StreamItemPhotos#_ID} will be used for sorting.
* <P>Type: NUMBER</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String SORT_INDEX = "sort_index";
/**
* Photo file ID for the photo.
* See {@link ContactsContract.DisplayPhoto}.
* <P>Type: NUMBER</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String PHOTO_FILE_ID = "photo_file_id";
/**
* URI for retrieving the photo content, automatically populated. Callers
* may retrieve the photo content by opening this URI as an asset file.
* <P>Type: TEXT</P>
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
*/
+ @Deprecated
public static final String PHOTO_URI = "photo_uri";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC1 = "stream_item_photo_sync1";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC2 = "stream_item_photo_sync2";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC3 = "stream_item_photo_sync3";
- /** Generic column for use by sync adapters. */
+ /**
+ * Generic column for use by sync adapters.
+ *
+ * @deprecated - Do not use. This will not be supported in the future. In the future,
+ * cursors returned from related queries will be empty.
+ */
+ @Deprecated
public static final String SYNC4 = "stream_item_photo_sync4";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bc069ca..d6ec30b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3627,6 +3627,20 @@
*/
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
+
+ /**
+ * Whether the TV parental control is enabled.
+ * @hide
+ */
+ public static final String TV_PARENTAL_CONTROL_ENABLED = "tv_parental_control_enabled";
+
+ /**
+ * List of TV content ratings blocked by the user. (comma-delimited)
+ * @hide
+ */
+ public static final String TV_PARENTAL_CONTROL_BLOCKED_RATINGS =
+ "tv_parental_control_blocked_ratings";
+
/**
* Settings classname to launch when Settings is clicked from All
* Applications. Needed because of user testing between the old
@@ -5154,17 +5168,6 @@
public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled";
/**
- * Output of the audio to be used for system audio mode, as defined in AudioSystem.java.
- * <ul>
- * <li>DEVICE_OUT_SPDIF</li>
- * <li>DEVICE_OUT_HDMI_ARC</li>
- * <li>DEVICE_OUT_LINE</li>
- * </ul>
- * @hide
- */
- public static final String HDMI_SYSTEM_AUDIO_OUTPUT = "hdmi_system_audio_output";
-
- /**
* Whether TV will automatically turn on upon reception of the CEC command
* <Text View On> or <Image View On>. (0 = false, 1 = true)
* @hide
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 06e776d..52db223 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.service.persistentdata;
import android.os.ParcelFileDescriptor;
@@ -12,8 +28,10 @@
*/
interface IPersistentDataBlockService {
int write(in byte[] data);
- int read(out byte[] data);
+ byte[] read();
+ void wipe();
int getDataBlockSize();
+ long getMaximumDataBlockSize();
void setOemUnlockEnabled(boolean enabled);
boolean getOemUnlockEnabled();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index afcf717..42a2e57 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.service.persistentdata;
import android.os.RemoteException;
@@ -7,14 +23,16 @@
* Interface for reading and writing data blocks to a persistent partition.
*
* Allows writing one block at a time. Namely, each time
- * {@link android.service.persistentdata.PersistentDataBlockManager}.write(byte[] data)
+ * {@link PersistentDataBlockManager#write(byte[])}
* is called, it will overwite the data that was previously written on the block.
*
* Clients can query the size of the currently written block via
- * {@link android.service.persistentdata.PersistentDataBlockManager}.getTotalDataSize().
+ * {@link PersistentDataBlockManager#getDataBlockSize()}.
*
- * Clients can any number of bytes from the currently written block up to its total size by invoking
- * {@link android.service.persistentdata.PersistentDataBlockManager}.read(byte[] data).
+ * Clients can query the maximum size for a block via
+ *
+ * Clients can read the currently written block by invoking
+ * {@link PersistentDataBlockManager#read()}.
*
* @hide
*/
@@ -30,41 +48,69 @@
* Writes {@code data} to the persistent partition. Previously written data
* will be overwritten. This data will persist across factory resets.
*
+ * Returns the number of bytes written or -1 on error. If the block is too big
+ * to fit on the partition, returns -MAX_BLOCK_SIZE.
+ *
* @param data the data to write
*/
- public void write(byte[] data) {
+ public int write(byte[] data) {
try {
- sService.write(data);
+ return sService.write(data);
} catch (RemoteException e) {
onError("writing data");
- }
- }
-
- /**
- * Tries to read {@code data.length} bytes into {@code data}. Call {@code getDataBlockSize()}
- * to determine the total size of the block currently residing in the persistent partition.
- *
- * @param data the buffer in which to read the data
- * @return the actual number of bytes read
- */
- public int read(byte[] data) {
- try {
- return sService.read(data);
- } catch (RemoteException e) {
- onError("reading data");
return -1;
}
}
/**
+ * Returns the data block stored on the persistent partition.
+ */
+ public byte[] read() {
+ try {
+ return sService.read();
+ } catch (RemoteException e) {
+ onError("reading data");
+ return null;
+ }
+ }
+
+ /**
* Retrieves the size of the block currently written to the persistent partition.
+ *
+ * Return -1 on error.
*/
public int getDataBlockSize() {
try {
return sService.getDataBlockSize();
} catch (RemoteException e) {
onError("getting data block size");
- return 0;
+ return -1;
+ }
+ }
+
+ /**
+ * Retrieves the maximum size allowed for a data block.
+ *
+ * Returns -1 on error.
+ */
+ public long getMaximumDataBlockSize() {
+ try {
+ return sService.getMaximumDataBlockSize();
+ } catch (RemoteException e) {
+ onError("getting maximum data block size");
+ return -1;
+ }
+ }
+
+ /**
+ * Zeroes the previously written block in its entirety. Calling this method
+ * will erase all data written to the persistent data partition.
+ */
+ public void wipe() {
+ try {
+ sService.wipe();
+ } catch (RemoteException e) {
+ onError("wiping persistent partition");
}
}
diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java
index 3ab3b31..04159af 100644
--- a/core/java/android/text/style/TtsSpan.java
+++ b/core/java/android/text/style/TtsSpan.java
@@ -16,6 +16,9 @@
package android.text.style;
+import java.text.NumberFormat;
+import java.util.Locale;
+
import android.os.Parcel;
import android.os.PersistableBundle;
import android.text.ParcelableSpan;
@@ -228,7 +231,8 @@
/**
* Argument used to specify the integer part of a decimal or fraction. The
- * value can be a string of digits of any size optionally prefixed with a - or +.
+ * value can be a string of digits of any size optionally prefixed with
+ * a - or +.
* Can be used with {@link #TYPE_DECIMAL} and {@link #TYPE_FRACTION}.
*/
public static final String ARG_INTEGER_PART = "android.arg.integer_part";
@@ -308,9 +312,10 @@
/**
* Argument used to specify the month of a date. The value should be
* provided as an integer and can be any of {@link #MONTH_JANUARY},
- * {@link #MONTH_FEBRUARY}, {@link #MONTH_MARCH}, {@link #MONTH_APRIL}, {@link #MONTH_MAY},
- * {@link #MONTH_JUNE}, {@link #MONTH_JULY}, {@link #MONTH_AUGUST}, {@link #MONTH_SEPTEMBER},
- * {@link #MONTH_OCTOBER}, {@link #MONTH_NOVEMBER} and {@link #MONTH_DECEMBER}.
+ * {@link #MONTH_FEBRUARY}, {@link #MONTH_MARCH}, {@link #MONTH_APRIL},
+ * {@link #MONTH_MAY}, {@link #MONTH_JUNE}, {@link #MONTH_JULY},
+ * {@link #MONTH_AUGUST}, {@link #MONTH_SEPTEMBER}, {@link #MONTH_OCTOBER},
+ * {@link #MONTH_NOVEMBER} and {@link #MONTH_DECEMBER}.
* Can be used with {@link #TYPE_DATE}.
*/
public static final String ARG_MONTH = "android.arg.month";
@@ -344,7 +349,8 @@
/**
* Argument used to specify the main number part of a telephone number. Can
- * be a string of digits.
+ * be a string of digits where the different parts of the telephone number
+ * can be separated with a space, '-', '/' or '.'.
* Can be used with {@link #TYPE_TELEPHONE}.
*/
public static final String ARG_NUMBER_PART = "android.arg.number_part";
@@ -473,8 +479,8 @@
* this builder like {@link TtsSpan.TextBuilder} and
* {@link TtsSpan.CardinalBuilder} are likely more useful.
*
- * This class uses generics so methods from this class can return instances of
- * its child classes, resulting in a fluent API (CRTP pattern).
+ * This class uses generics so methods from this class can return instances
+ * of its child classes, resulting in a fluent API (CRTP pattern).
*/
public static abstract class Builder<C extends Builder<C>> {
// Holds the type of this class.
@@ -597,7 +603,7 @@
public static class TextBuilder extends SemioticClassBuilder<TextBuilder> {
/**
- * Creates a TtsSpan of type {@link TtsSpan#TYPE_TEXT}.
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_TEXT}.
*/
public TextBuilder() {
super(TtsSpan.TYPE_TEXT);
@@ -628,10 +634,12 @@
/**
* A builder for TtsSpans of type {@link TtsSpan #TYPE_CARDINAL}.
*/
- public static class CardinalBuilder extends SemioticClassBuilder<CardinalBuilder> {
+ public static class CardinalBuilder
+ extends SemioticClassBuilder<CardinalBuilder> {
/**
- * Creates a TtsSpan of type {@link TtsSpan#TYPE_CARDINAL}.
+ * Creates a builder for a TtsSpan of type
+ * {@link TtsSpan#TYPE_CARDINAL}.
*/
public CardinalBuilder() {
super(TtsSpan.TYPE_CARDINAL);
@@ -679,4 +687,570 @@
return setStringArgument(TtsSpan.ARG_NUMBER, number);
}
}
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan#TYPE_ORDINAL}.
+ */
+ public static class OrdinalBuilder
+ extends SemioticClassBuilder<OrdinalBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_ORDINAL}.
+ */
+ public OrdinalBuilder() {
+ super(TtsSpan.TYPE_ORDINAL);
+ }
+
+ /**
+ * Creates a TtsSpan of type {@link TtsSpan#TYPE_ORDINAL} and sets the
+ * {@link TtsSpan#ARG_NUMBER} argument.
+ * @param number The ordinal number to synthesize.
+ * @see #setNumber(long)
+ */
+ public OrdinalBuilder(long number) {
+ this();
+ setNumber(number);
+ }
+
+ /**
+ * Creates a TtsSpan of type {@link TtsSpan#TYPE_ORDINAL} and sets the
+ * {@link TtsSpan#ARG_NUMBER} argument.
+ * @param number The number to synthesize.
+ * @see #setNumber(String)
+ */
+ public OrdinalBuilder(String number) {
+ this();
+ setNumber(number);
+ }
+
+ /**
+ * Convenience method that converts the number to a String and sets it
+ * to the value for {@link TtsSpan#ARG_NUMBER}.
+ * @param number The ordinal number that will be synthesized.
+ * @return This instance.
+ */
+ public OrdinalBuilder setNumber(long number) {
+ return setNumber(String.valueOf(number));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_NUMBER} argument.
+ * @param number A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public OrdinalBuilder setNumber(String number) {
+ return setStringArgument(TtsSpan.ARG_NUMBER, number);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan#TYPE_DECIMAL}.
+ */
+ public static class DecimalBuilder
+ extends SemioticClassBuilder<DecimalBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_DECIMAL}.
+ */
+ public DecimalBuilder() {
+ super(TtsSpan.TYPE_DECIMAL);
+ }
+
+ /**
+ * Creates a TtsSpan of type {@link TtsSpan#TYPE_DECIMAL} and sets the
+ * {@link TtsSpan#ARG_INTEGER_PART} and
+ * {@link TtsSpan#ARG_FRACTIONAL_PART} arguments.
+ * @see {@link #setArgumentsFromDouble(double, int, int)
+ */
+ public DecimalBuilder(double number,
+ int minimumFractionDigits,
+ int maximumFractionDigits) {
+ this();
+ setArgumentsFromDouble(number,
+ minimumFractionDigits,
+ maximumFractionDigits);
+ }
+
+ /**
+ * Creates a TtsSpan of type {@link TtsSpan#TYPE_DECIMAL} and sets the
+ * {@link TtsSpan#ARG_INTEGER_PART} and
+ * {@link TtsSpan#ARG_FRACTIONAL_PART} arguments.
+ */
+ public DecimalBuilder(String integerPart, String fractionalPart) {
+ this();
+ setIntegerPart(integerPart);
+ setFractionalPart(fractionalPart);
+ }
+
+ /**
+ * Convenience method takes a double and a maximum number of fractional
+ * digits, it sets the {@link TtsSpan#ARG_INTEGER_PART} and
+ * {@link TtsSpan#ARG_FRACTIONAL_PART} arguments.
+ * @param number The number to be synthesized.
+ * @param minimumFractionDigits The minimum number of fraction digits
+ * that are pronounced.
+ * @param maximumFractionDigits The maximum number of fraction digits
+ * that are pronounced. If maximumFractionDigits <
+ * minimumFractionDigits then minimumFractionDigits will be assumed
+ * to be equal to maximumFractionDigits.
+ * @return This instance.
+ */
+ public DecimalBuilder setArgumentsFromDouble(
+ double number,
+ int minimumFractionDigits,
+ int maximumFractionDigits) {
+ // Format double.
+ NumberFormat formatter = NumberFormat.getInstance(Locale.US);
+ formatter.setMinimumFractionDigits(maximumFractionDigits);
+ formatter.setMaximumFractionDigits(maximumFractionDigits);
+ formatter.setGroupingUsed(false);
+ String str = formatter.format(number);
+
+ // Split at decimal point.
+ int i = str.indexOf('.');
+ if (i >= 0) {
+ setIntegerPart(str.substring(0, i));
+ setFractionalPart(str.substring(i + 1));
+ } else {
+ setIntegerPart(str);
+ }
+ return this;
+ }
+
+ /**
+ * Convenience method that converts the number to a String and sets it
+ * to the value for {@link TtsSpan#ARG_INTEGER_PART}.
+ * @param integerPart The integer part of the decimal.
+ * @return This instance.
+ */
+ public DecimalBuilder setIntegerPart(long integerPart) {
+ return setIntegerPart(String.valueOf(integerPart));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_INTEGER_PART} argument.
+ * @param integerPart A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public DecimalBuilder setIntegerPart(String integerPart) {
+ return setStringArgument(TtsSpan.ARG_INTEGER_PART, integerPart);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_FRACTIONAL_PART} argument.
+ * @param fractionalPart A non-empty string of digits.
+ * @return This instance.
+ */
+ public DecimalBuilder setFractionalPart(String fractionalPart) {
+ return setStringArgument(TtsSpan.ARG_FRACTIONAL_PART,
+ fractionalPart);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan#TYPE_FRACTION}.
+ */
+ public static class FractionBuilder
+ extends SemioticClassBuilder<FractionBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type
+ * {@link TtsSpan#TYPE_FRACTION}.
+ */
+ public FractionBuilder() {
+ super(TtsSpan.TYPE_FRACTION);
+ }
+
+ /**
+ * Creates a TtsSpan of type {@link TtsSpan#TYPE_FRACTION} and sets the
+ * {@link TtsSpan#ARG_INTEGER_PART}, {@link TtsSpan#ARG_NUMERATOR}, and
+ * {@link TtsSpan#ARG_DENOMINATOR} arguments.
+ */
+ public FractionBuilder(long integerPart,
+ long numerator,
+ long denominator) {
+ this();
+ setIntegerPart(integerPart);
+ setNumerator(numerator);
+ setDenominator(denominator);
+ }
+
+
+ /**
+ * Convenience method that converts the integer to a String and sets the
+ * argument {@link TtsSpan#ARG_NUMBER}.
+ * @param integerPart The integer part.
+ * @return This instance.
+ */
+ public FractionBuilder setIntegerPart(long integerPart) {
+ return setIntegerPart(String.valueOf(integerPart));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_INTEGER_PART} argument.
+ * @param integerPart A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public FractionBuilder setIntegerPart(String integerPart) {
+ return setStringArgument(TtsSpan.ARG_INTEGER_PART, integerPart);
+ }
+
+ /**
+ * Convenience method that converts the numerator to a String and sets
+ * the argument {@link TtsSpan#ARG_NUMERATOR}.
+ * @param numerator The numerator.
+ * @return This instance.
+ */
+ public FractionBuilder setNumerator(long numerator) {
+ return setNumerator(String.valueOf(numerator));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_NUMERATOR} argument.
+ * @param numerator A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public FractionBuilder setNumerator(String numerator) {
+ return setStringArgument(TtsSpan.ARG_NUMERATOR, numerator);
+ }
+
+ /**
+ * Convenience method that converts the denominator to a String and sets
+ * the argument {@link TtsSpan#ARG_DENOMINATOR}.
+ * @param denominator The denominator.
+ * @return This instance.
+ */
+ public FractionBuilder setDenominator(long denominator) {
+ return setDenominator(String.valueOf(denominator));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_DENOMINATOR} argument.
+ * @param denominator A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public FractionBuilder setDenominator(String denominator) {
+ return setStringArgument(TtsSpan.ARG_DENOMINATOR, denominator);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan #TYPE_MEASURE}.
+ */
+ public static class MeasureBuilder
+ extends SemioticClassBuilder<MeasureBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_MEASURE}.
+ */
+ public MeasureBuilder() {
+ super(TtsSpan.TYPE_MEASURE);
+ }
+
+ /**
+ * Convenience method that converts the number to a String and set it to
+ * the value for {@link TtsSpan#ARG_NUMBER}.
+ * @param number The amount of the measure.
+ * @return This instance.
+ */
+ public MeasureBuilder setNumber(long number) {
+ return setNumber(String.valueOf(number));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_NUMBER} argument.
+ * @param number A non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public MeasureBuilder setNumber(String number) {
+ return setStringArgument(TtsSpan.ARG_NUMBER, number);
+ }
+
+ /**
+ * Convenience method that converts the integer part to a String and set
+ * it to the value for {@link TtsSpan#ARG_INTEGER_PART}.
+ * @param integerPart The integer part of a decimal or fraction.
+ * @return This instance.
+ */
+ public MeasureBuilder setIntegerPart(long integerPart) {
+ return setNumber(String.valueOf(integerPart));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_INTEGER_PART} argument.
+ * @param integerPart The integer part of a decimal or fraction; a
+ * non-empty string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public MeasureBuilder setIntegerPart(String integerPart) {
+ return setStringArgument(TtsSpan.ARG_INTEGER_PART, integerPart);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_FRACTIONAL_PART} argument.
+ * @param fractionalPart The fractional part of a decimal; a non-empty
+ * string of digits with an optional
+ * leading + or -.
+ * @return This instance.
+ */
+ public MeasureBuilder setFractionalPart(String fractionalPart) {
+ return setStringArgument(TtsSpan.ARG_FRACTIONAL_PART,
+ fractionalPart);
+ }
+
+ /**
+ * Convenience method that converts the numerator to a String and set it
+ * to the value for {@link TtsSpan#ARG_NUMERATOR}.
+ * @param numerator The numerator of a fraction.
+ * @return This instance.
+ */
+ public MeasureBuilder setNumerator(long numerator) {
+ return setNumerator(String.valueOf(numerator));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_NUMERATOR} argument.
+ * @param numerator The numerator of a fraction; a non-empty string of
+ * digits with an optional leading + or -.
+ * @return This instance.
+ */
+ public MeasureBuilder setNumerator(String numerator) {
+ return setStringArgument(TtsSpan.ARG_NUMERATOR, numerator);
+ }
+
+ /**
+ * Convenience method that converts the denominator to a String and set
+ * it to the value for {@link TtsSpan#ARG_DENOMINATOR}.
+ * @param denominator The denominator of a fraction.
+ * @return This instance.
+ */
+ public MeasureBuilder setDenominator(long denominator) {
+ return setDenominator(String.valueOf(denominator));
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_DENOMINATOR} argument.
+ * @param denominator The denominator of a fraction; a non-empty string
+ * of digits with an optional leading + or -.
+ * @return This instance.
+ */
+ public MeasureBuilder setDenominator(String denominator) {
+ return setStringArgument(TtsSpan.ARG_DENOMINATOR, denominator);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_UNIT} argument.
+ * @param unit The unit of the measure.
+ * @return This instance.
+ * @see {@link TtsSpan.ARG_UNIT}
+ */
+ public MeasureBuilder setUnit(String unit) {
+ return setStringArgument(TtsSpan.ARG_UNIT, unit);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan #TYPE_TIME}.
+ */
+ public static class TimeBuilder
+ extends SemioticClassBuilder<TimeBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_TIME}.
+ */
+ public TimeBuilder() {
+ super(TtsSpan.TYPE_TIME);
+ }
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_TIME} and
+ * sets the {@link TtsSpan#ARG_HOURS} and {@link TtsSpan#ARG_MINUTES}
+ * arguments.
+ */
+ public TimeBuilder(int hours, int minutes) {
+ this();
+ setHours(hours);
+ setMinutes(minutes);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_HOURS} argument.
+ * @param hours The value to be set for hours. See
+ * {@link TtsSpan#ARG_HOURS}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_HOURS}
+ */
+ public TimeBuilder setHours(int hours) {
+ return setIntArgument(TtsSpan.ARG_HOURS, hours);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_MINUTES} argument.
+ * @param minutes The value to be set for minutes. See
+ * {@link TtsSpan#ARG_MINUTES}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_MINUTES}
+ */
+ public TimeBuilder setMinutes(int minutes) {
+ return setIntArgument(TtsSpan.ARG_MINUTES, minutes);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan #TYPE_DATE}.
+ */
+ public static class DateBuilder
+ extends SemioticClassBuilder<DateBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_DATE}.
+ */
+ public DateBuilder() {
+ super(TtsSpan.TYPE_DATE);
+ }
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_TIME} and
+ * possibly sets the {@link TtsSpan#ARG_WEEKDAY},
+ * {@link TtsSpan#ARG_DAY}, {@link TtsSpan#ARG_MONTH} and
+ * {@link TtsSpan#ARG_YEAR} arguments. Pass null to any argument to
+ * leave it unset.
+ */
+ public DateBuilder(Integer weekday,
+ Integer day,
+ Integer month,
+ Integer year) {
+ this();
+ if (weekday != null) {
+ setWeekday(weekday);
+ }
+ if (day != null) {
+ setDay(day);
+ }
+ if (month != null) {
+ setMonth(month);
+ }
+ if (year != null) {
+ setYear(year);
+ }
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_WEEKDAY} argument.
+ * @param weekday The value to be set for weekday. See
+ * {@link TtsSpan#ARG_WEEKDAY}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_WEEKDAY}
+ */
+ public DateBuilder setWeekday(int weekday) {
+ return setIntArgument(TtsSpan.ARG_WEEKDAY, weekday);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_DAY} argument.
+ * @param day The value to be set for day. See
+ * {@link TtsSpan#ARG_DAY}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_DAY}
+ */
+ public DateBuilder setDay(int day) {
+ return setIntArgument(TtsSpan.ARG_DAY, day);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_MONTH} argument.
+ * @param month The value to be set for month. See
+ * {@link TtsSpan#ARG_MONTH}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_MONTH}
+ */
+ public DateBuilder setMonth(int month) {
+ return setIntArgument(TtsSpan.ARG_MONTH, month);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_YEAR} argument.
+ * @param year The value to be set for year. See
+ * {@link TtsSpan#ARG_YEAR}.
+ * @return This instance.
+ * @see {@link TtsSpan#ARG_YEAR}
+ */
+ public DateBuilder setYear(int year) {
+ return setIntArgument(TtsSpan.ARG_YEAR, year);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan #TYPE_DIGITS}.
+ */
+ public static class DigitsBuilder
+ extends SemioticClassBuilder<DigitsBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type
+ * {@link TtsSpan#TYPE_VERBATIM}.
+ */
+ public DigitsBuilder() {
+ super(TtsSpan.TYPE_DIGITS);
+ }
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_DIGITS}
+ * and sets the {@link TtsSpan#ARG_DIGITS} argument.
+ */
+ public DigitsBuilder(String digits) {
+ this();
+ setDigits(digits);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_DIGITS} argument.
+ * @param digits A string of digits.
+ * @return This instance.
+ */
+ public DigitsBuilder setDigits(String digits) {
+ return setStringArgument(TtsSpan.ARG_DIGITS, digits);
+ }
+ }
+
+ /**
+ * A builder for TtsSpans of type {@link TtsSpan #TYPE_VERBATIM}.
+ */
+ public static class VerbatimBuilder
+ extends SemioticClassBuilder<VerbatimBuilder> {
+
+ /**
+ * Creates a builder for a TtsSpan of type
+ * {@link TtsSpan#TYPE_VERBATIM}.
+ */
+ public VerbatimBuilder() {
+ super(TtsSpan.TYPE_VERBATIM);
+ }
+
+ /**
+ * Creates a builder for a TtsSpan of type {@link TtsSpan#TYPE_VERBATIM}
+ * and sets the {@link TtsSpan#ARG_VERBATIM} argument.
+ */
+ public VerbatimBuilder(String verbatim) {
+ this();
+ setVerbatim(verbatim);
+ }
+
+ /**
+ * Sets the {@link TtsSpan#ARG_VERBATIM} argument.
+ * @param verbatim A string of characters that will be read verbatim,
+ * except whitespace.
+ * @return This instance.
+ */
+ public VerbatimBuilder setVerbatim(String verbatim) {
+ return setStringArgument(TtsSpan.ARG_VERBATIM, verbatim);
+ }
+ }
}
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index e0e19b9..89d879e 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -90,6 +90,18 @@
}
/**
+ * Returns the language component of a given locale string.
+ */
+ private static String parseLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
+ }
+
+ /**
* Get a spell checker session for the specified spell checker
* @param locale the locale for the spell checker. If {@code locale} is null and
* referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
@@ -134,9 +146,8 @@
}
if (locale != null) {
final String subtypeLocale = subtypeInUse.getLocale();
- final String inputLocale = locale.toString();
- if (subtypeLocale.length() < 2 || inputLocale.length() < 2
- || !subtypeLocale.substring(0, 2).equals(inputLocale.substring(0, 2))) {
+ final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+ if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
return null;
}
}
@@ -145,11 +156,12 @@
for (int i = 0; i < sci.getSubtypeCount(); ++i) {
final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
final String tempSubtypeLocale = subtype.getLocale();
+ final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
if (tempSubtypeLocale.equals(localeStr)) {
subtypeInUse = subtype;
break;
- } else if (localeStr.length() >= 2 && tempSubtypeLocale.length() >= 2
- && localeStr.startsWith(tempSubtypeLocale)) {
+ } else if (tempSubtypeLanguage.length() >= 2 &&
+ locale.getLanguage().equals(tempSubtypeLanguage)) {
subtypeInUse = subtype;
}
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index adf4803..9cf3e4f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1945,6 +1945,23 @@
}
/**
+ * Performs a zoom operation in this WebView.
+ *
+ * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the Webview's
+ * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
+ *
+ * @return false if no zoom changes, true otherwise.
+ */
+ public boolean zoomBy(float zoomFactor) {
+ checkThread();
+ if (zoomFactor < 0.01)
+ throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
+ if (zoomFactor > 100.0)
+ throw new IllegalArgumentException("zoomFactor must be less than 100.");
+ return mProvider.zoomBy(zoomFactor);
+ }
+
+ /**
* Performs zoom in in this WebView.
*
* @return true if zoom in succeeds, false if no zoom changes
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 13be453..2c7b3eb 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -63,6 +63,11 @@
private static final Object sProviderLock = new Object();
private static boolean sAddressSpaceReserved = false;
+ public static String getWebViewPackageName() {
+ // TODO: Make this dynamic based on resource configuration.
+ return "com.android.webview";
+ }
+
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index b6fd363..13cd2bd 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -238,6 +238,8 @@
public boolean canZoomOut();
+ public boolean zoomBy(float zoomFactor);
+
public boolean zoomIn();
public boolean zoomOut();
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/LegacyTimePickerDelegate.java
index 6dd70ba..70db211 100644
--- a/core/java/android/widget/LegacyTimePickerDelegate.java
+++ b/core/java/android/widget/LegacyTimePickerDelegate.java
@@ -429,11 +429,14 @@
@Override
public void setShowDoneButton(boolean showDoneButton) {
- mShowDoneButton = showDoneButton;
- updateDoneButton();
+ if (mDoneButton != null) {
+ mShowDoneButton = showDoneButton;
+ updateDoneButton();
+ }
}
- private boolean isShowDoneButton() {
+ @Override
+ public boolean isShowDoneButton() {
return mShowDoneButton;
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 58a6562..026a8ee 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -163,6 +163,13 @@
/**
* @hide
*/
+ public boolean isShowDoneButton() {
+ return mDelegate.isShowDoneButton();
+ }
+
+ /**
+ * @hide
+ */
public void setDismissCallback(TimePickerDismissCallback callback) {
mDelegate.setDismissCallback(callback);
}
@@ -234,6 +241,7 @@
void setEnabled(boolean enabled);
boolean isEnabled();
+ boolean isShowDoneButton();
void setShowDoneButton(boolean showDoneButton);
void setDismissCallback(TimePickerDismissCallback callback);
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java
index a9af2f9..45be637 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -564,7 +564,8 @@
return mRadialTimePickerView.getCurrentItemShowing();
}
- private boolean isShowDoneButton() {
+ @Override
+ public boolean isShowDoneButton() {
return mShowDoneButton;
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 7e58351..50e7bcf 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -271,6 +271,9 @@
mFullTargetPackage = null;
mSocket.close();
} catch (IOException e) {
+ if (DEBUG) {
+ Log.w(TAG, "Exception caught in finishBackup()", e);
+ }
return TRANSPORT_ERROR;
} finally {
mSocket = null;
@@ -576,7 +579,7 @@
return TRANSPORT_PACKAGE_REJECTED;
}
mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
- mFullRestoreBuffer = new byte[32*1024];
+ mFullRestoreBuffer = new byte[2*1024];
}
int nRead;
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 5b542ba..b8fa04c 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -94,6 +94,7 @@
JNIEnv* env = NULL;
int result = attach_thread(&env);
if (result != JNI_OK) {
+ ALOGE("Unable to attach thread with JNI.");
return;
}
@@ -215,7 +216,7 @@
return NULL;
}
- jclass string_class = env->FindClass("java/lang/String;");
+ jclass string_class = env->FindClass("java/lang/String");
if (string_class == NULL) {
ALOGE("Unable to find String class for supported activities.");
return NULL;
@@ -229,14 +230,8 @@
for (int i = 0; i < list_size; ++i) {
const char* string_ptr = const_cast<const char*>(list[i]);
- jsize string_length = strlen(string_ptr);
- jstring string = env->NewString((const jchar*) string_ptr, string_length);
+ jstring string = env->NewStringUTF(string_ptr);
env->SetObjectArrayElement(string_array, i, string);
-
- // log debugging information in case we need to try to trace issues with the strings
- if (string_length) {
- ALOGD("Invalid activity (index=%d) name size: %d", i, string_length);
- }
}
return string_array;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c9fd6c4..ba65d63 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -481,7 +481,9 @@
android:label="@string/permlab_writeCallLog"
android:description="@string/permdesc_writeCallLog" />
- <!-- Allows an application to read from the user's social stream. -->
+ <!-- Allows an application to read from the user's social stream.
+ @deprecated This functionality will be unsupported in the future; cursors returned
+ will be empty. Please do not use. -->
<permission android:name="android.permission.READ_SOCIAL_STREAM"
android:permissionGroup="android.permission-group.SOCIAL_INFO"
android:protectionLevel="dangerous"
@@ -489,7 +491,9 @@
android:description="@string/permdesc_readSocialStream" />
<!-- Allows an application to write (but not read) the user's
- social stream data. -->
+ social stream data.
+ @deprecated This functionality will be unsupported in the future; cursors returned
+ will be empty. Please do not use. -->
<permission android:name="android.permission.WRITE_SOCIAL_STREAM"
android:permissionGroup="android.permission-group.SOCIAL_INFO"
android:protectionLevel="dangerous"
@@ -2771,6 +2775,13 @@
android:description="@string/permdesc_accessDrmCertificates"
android:protectionLevel="signature|system" />
+ <!-- Api Allows an application to create media projection sessions.
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.CREATE_MEDIA_PROJECTION"
+ android:label="@string/permlab_createMediaProjection"
+ android:description="@string/permdesc_createMediaProjection"
+ android:protectionLevel="signature" />
+
<!-- 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/layout-sw720dp/status_bar_latest_event_ticker.xml b/core/res/res/layout-sw720dp/status_bar_latest_event_ticker.xml
deleted file mode 100644
index a09ad0c..0000000
--- a/core/res/res/layout-sw720dp/status_bar_latest_event_ticker.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/status_bar_height"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="match_parent"
- android:scaleType="center"
- />
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_weight="1"
- android:orientation="vertical"
- android:paddingStart="16dp"
- >
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
- <TextView android:id="@+id/text"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginTop="-4dp"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
- </LinearLayout>
- <TextView android:id="@+id/info"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginBottom="6dip"
- android:gravity="bottom"
- android:singleLine="true"
- />
-</LinearLayout>
diff --git a/core/res/res/layout-sw720dp/status_bar_latest_event_ticker_large_icon.xml b/core/res/res/layout-sw720dp/status_bar_latest_event_ticker_large_icon.xml
deleted file mode 100644
index 09ff1c8..0000000
--- a/core/res/res/layout-sw720dp/status_bar_latest_event_ticker_large_icon.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_weight="1"
- android:orientation="vertical"
- android:paddingStart="16dp"
- >
- <TextView android:id="@+id/title"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
- <TextView android:id="@+id/text"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginTop="-4dp"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- />
- </LinearLayout>
- <TextView android:id="@+id/info"
- android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginBottom="6dip"
- android:gravity="bottom"
- android:singleLine="true"
- />
- <ImageView android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:layout_marginBottom="13dip"
- android:scaleType="center"
- android:layout_marginEnd="4dip"
- android:layout_marginStart="16dip"
- />
-</LinearLayout>
-
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2492689..f133707 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3861,6 +3861,11 @@
<!-- Description of an application permission that lets it control keyguard. -->
<string name="permdesc_recovery">Allows an application to interact with the recovery system and system updates.</string>
+ <!-- Title of an application permission that lets it create media projection sessions. -->
+ <string name="permlab_createMediaProjection">Create media projection sessions</string>
+ <!-- Description of an application permission that lets it create media projection sessions. -->
+ <string name="permdesc_createMediaProjection">Allows an application to create media projection sessions. These sessions can provide applications the ability to capture display and audio contents. Should never be needed by normal apps.</string>
+
<!-- Shown in the tutorial for tap twice for zoom control. -->
<string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index 5531433..b0f5864 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -23,6 +23,7 @@
</style>
<style name="Widget.Leanback.TimePicker" parent="Widget.Material.TimePicker">
+ <item name="legacyMode">true</item>
<item name="legacyLayout">@layout/time_picker_legacy_leanback</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aaadc16..2ff6777 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1169,8 +1169,6 @@
<java-symbol type="layout" name="select_dialog" />
<java-symbol type="layout" name="simple_dropdown_hint" />
<java-symbol type="layout" name="status_bar_latest_event_content" />
- <java-symbol type="layout" name="status_bar_latest_event_ticker" />
- <java-symbol type="layout" name="status_bar_latest_event_ticker_large_icon" />
<java-symbol type="layout" name="text_edit_action_popup_text" />
<java-symbol type="layout" name="text_drag_thumbnail" />
<java-symbol type="layout" name="typing_filter" />
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 636216d..534e323 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -31,7 +31,6 @@
</style>
<style name="Theme.Leanback.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker">
- <item name="legacyMode">true</item>
<item name="colorBackground">@color/background_leanback_dark</item>
<item name="textColorPrimary">@color/primary_text_leanback_dark</item>
<item name="textColorSecondary">@color/secondary_text_leanback_dark</item>
@@ -41,7 +40,6 @@
</style>
<style name="Theme.Leanback.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker">
- <item name="legacyMode">true</item>
<item name="colorBackground">@color/background_leanback_light</item>
<item name="textColorPrimary">@color/primary_text_leanback_light</item>
<item name="textColorSecondary">@color/secondary_text_leanback_light</item>
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
old mode 100644
new mode 100755
index e468a754..5a96132
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -259,7 +259,7 @@
}
}
-void OpenGLRenderer::startTilingCurrentClip(bool opaque) {
+void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) {
if (!mSuppressTiling) {
const Snapshot* snapshot = currentSnapshot();
@@ -268,14 +268,27 @@
clip = &(snapshot->layer->clipRect);
}
- startTiling(*clip, getViewportHeight(), opaque);
+ startTiling(*clip, getViewportHeight(), opaque, expand);
}
}
-void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque) {
+void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) {
if (!mSuppressTiling) {
- mCaches.startTiling(clip.left, windowHeight - clip.bottom,
+ if(expand) {
+ // Expand the startTiling region by 1
+ int leftNotZero = (clip.left > 0) ? 1 : 0;
+ int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0;
+
+ mCaches.startTiling(
+ clip.left - leftNotZero,
+ windowHeight - clip.bottom - topNotZero,
+ clip.right - clip.left + leftNotZero + 1,
+ clip.bottom - clip.top + topNotZero + 1,
+ opaque);
+ } else {
+ mCaches.startTiling(clip.left, windowHeight - clip.bottom,
clip.right - clip.left, clip.bottom - clip.top, opaque);
+ }
}
}
@@ -821,7 +834,8 @@
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
layer->getTexture(), 0);
- startTilingCurrentClip(true);
+ // Expand the startTiling region by 1
+ startTilingCurrentClip(true, true);
// Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
mCaches.enableScissor();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
old mode 100644
new mode 100755
index e7328be..4e7844b
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -491,14 +491,14 @@
* This method needs to be invoked every time getTargetFbo() is
* bound again.
*/
- void startTilingCurrentClip(bool opaque = false);
+ void startTilingCurrentClip(bool opaque = false, bool expand = false);
/**
* Tells the GPU what part of the screen is about to be redrawn.
* This method needs to be invoked every time getTargetFbo() is
* bound again.
*/
- void startTiling(const Rect& clip, int windowHeight, bool opaque = false);
+ void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false);
/**
* Tells the GPU that we are done drawing the frame or that we
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
index 8707a10..c7dfc88 100644
--- a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -40,4 +40,17 @@
public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
return mActivityRecognitionEvents;
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ ActivityChangedEvent:");
+
+ for (ActivityRecognitionEvent event : mActivityRecognitionEvents) {
+ builder.append("\n ");
+ builder.append(event.toString());
+ }
+ builder.append("\n]");
+
+ return builder.toString();
+ }
}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
index 8c719ce4..a39cff2 100644
--- a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -41,4 +41,30 @@
public long getTimestampNs() {
return mTimestampNs;
}
+
+ @Override
+ public String toString() {
+ String eventString;
+ switch (mEventType) {
+ case ActivityRecognitionProvider.EVENT_TYPE_ENTER:
+ eventString = "Enter";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_EXIT:
+ eventString = "Exit";
+ break;
+ case ActivityRecognitionProvider.EVENT_TYPE_FLUSH_COMPLETE:
+ eventString = "FlushComplete";
+ break;
+ default:
+ eventString = "<Invalid>";
+ break;
+ }
+
+ return String.format(
+ "Activity='%s', EventType=%s(%s), TimestampNs=%s",
+ mActivity,
+ eventString,
+ mEventType,
+ mTimestampNs);
+ }
}
diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDevice.java
index 96d6196..e078354 100644
--- a/media/java/android/media/AudioDevice.java
+++ b/media/java/android/media/AudioDevice.java
@@ -19,30 +19,91 @@
import android.util.SparseIntArray;
/**
- * @hide
- * CANDIDATE FOR PUBLIC API
+ * Class to provide information about the audio devices.
*/
public class AudioDevice {
+ /**
+ * A device type associated with an unknown or uninitialized device.
+ */
public static final int DEVICE_TYPE_UNKNOWN = 0;
+ /**
+ * A device type describing the attached earphone speaker.
+ */
public static final int DEVICE_TYPE_BUILTIN_EARPIECE = 1;
+ /**
+ * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+ * in a device.
+ */
public static final int DEVICE_TYPE_BUILTIN_SPEAKER = 2;
+ /**
+ * A device type describing a headset, which is the combination of a headphones and microphone.
+ */
public static final int DEVICE_TYPE_WIRED_HEADSET = 3;
+ /**
+ * A device type describing a pair of wired headphones .
+ */
public static final int DEVICE_TYPE_WIRED_HEADPHONES = 4;
+ /**
+ * A device type describing an analog line-level connection.
+ */
public static final int DEVICE_TYPE_LINE_ANALOG = 5;
+ /**
+ * A device type describing a digital line connection (e.g. SPDIF).
+ */
public static final int DEVICE_TYPE_LINE_DIGITAL = 6;
+ /**
+ * A device type describing a Bluetooth device typically used for telephony .
+ */
public static final int DEVICE_TYPE_BLUETOOTH_SCO = 7;
+ /**
+ * A device type describing a Bluetooth device supporting the A2DP profile.
+ */
public static final int DEVICE_TYPE_BLUETOOTH_A2DP = 8;
+ /**
+ * A device type describing an HDMI connection .
+ */
public static final int DEVICE_TYPE_HDMI = 9;
+ /**
+ * A device type describing the Audio Return Channel of an HDMI connection.
+ */
public static final int DEVICE_TYPE_HDMI_ARC = 10;
+ /**
+ * A device type describing a USB audio device.
+ */
public static final int DEVICE_TYPE_USB_DEVICE = 11;
+ /**
+ * A device type describing a USB audio device in accessory mode.
+ */
public static final int DEVICE_TYPE_USB_ACCESSORY = 12;
+ /**
+ * A device type describing the audio device associated with a dock.
+ */
public static final int DEVICE_TYPE_DOCK = 13;
+ /**
+ * A device type associated with the transmission of audio signals over FM.
+ */
public static final int DEVICE_TYPE_FM = 14;
+ /**
+ * A device type describing the microphone(s) built in a device.
+ */
public static final int DEVICE_TYPE_BUILTIN_MIC = 15;
+ /**
+ * A device type for accessing the audio content transmitted over FM.
+ */
public static final int DEVICE_TYPE_FM_TUNER = 16;
+ /**
+ * A device type for accessing the audio content transmitted over the TV tuner system.
+ */
public static final int DEVICE_TYPE_TV_TUNER = 17;
+ /**
+ * A device type describing the transmission of audio signals over the telephony network.
+ */
public static final int DEVICE_TYPE_TELEPHONY = 18;
+ /**
+ * A device type describing the auxiliary line-level connectors.
+ */
+ public static final int DEVICE_TYPE_AUX_LINE = 19;
AudioDevicePortConfig mConfig;
@@ -50,18 +111,38 @@
mConfig = new AudioDevicePortConfig(config);
}
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * @return
+ */
public boolean isInputDevice() {
return (mConfig.port().role() == AudioPort.ROLE_SOURCE);
}
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * @return
+ */
public boolean isOutputDevice() {
return (mConfig.port().role() == AudioPort.ROLE_SINK);
}
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * @return
+ */
public int getDeviceType() {
return INT_TO_EXT_DEVICE_MAPPING.get(mConfig.port().type(), DEVICE_TYPE_UNKNOWN);
}
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * @return
+ */
public String getAddress() {
return mConfig.port().address();
}
@@ -102,6 +183,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HDMI_ARC, DEVICE_TYPE_HDMI_ARC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPDIF, DEVICE_TYPE_LINE_DIGITAL);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_FM, DEVICE_TYPE_FM);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_AUX_LINE, DEVICE_TYPE_AUX_LINE);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, DEVICE_TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, DEVICE_TYPE_BLUETOOTH_SCO);
@@ -143,6 +225,7 @@
EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE);
}
}
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 136761b..78eeccb 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -248,8 +248,6 @@
}
/**
- * @hide
- * CANDIDATE FOR PUBLIC API
* Checks if the combination of a channel mask and device type is supported by this virtualizer.
* Some virtualizer implementations may only support binaural processing (i.e. only support
* headphone output), some may support transaural processing (i.e. for speaker output) for the
@@ -276,8 +274,6 @@
}
/**
- * @hide
- * CANDIDATE FOR PUBLIC API
* Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
* mask and device type.
* If the virtualization configuration (mask and device) is supported (see
@@ -318,8 +314,6 @@
}
/**
- * @hide
- * CANDIDATE FOR PUBLIC API
* Forces the virtualizer effect to use the processing mode used for the given device type.
* The effect must be enabled for the forced mode to be applied.
* @param deviceType one of the device types defined in {@link AudioDevice}.
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
new file mode 100644
index 0000000..dd84ad32
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.media.projection.IMediaProjectionCallback;
+
+/** {@hide} */
+interface IMediaProjection {
+ void start(IMediaProjectionCallback callback);
+ void stop();
+ boolean canProjectAudio();
+ boolean canProjectVideo();
+ boolean canProjectSecureVideo();
+ int getVirtualDisplayFlags();
+ void addCallback(IMediaProjectionCallback callback);
+ void removeCallback(IMediaProjectionCallback callback);
+}
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
new file mode 100644
index 0000000..f3743d1
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+/** {@hide} */
+oneway interface IMediaProjectionCallback {
+ void onStop();
+}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
new file mode 100644
index 0000000..6ed803a
--- /dev/null
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.media.projection.IMediaProjection;
+import android.os.IBinder;
+
+/** {@hide} */
+interface IMediaProjectionManager {
+ boolean hasProjectionPermission(int uid, String packageName);
+ IMediaProjection createProjection(int uid, String packageName, int type,
+ boolean permanentGrant);
+ boolean isValidMediaProjection(IMediaProjection projection);
+}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
new file mode 100644
index 0000000..348a577
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.AudioRecord;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.Surface;
+
+import java.util.Map;
+
+/**
+ * A token granting applications the ability to capture screen contents and/or
+ * record system audio. The exact capabilities granted depend on the type of
+ * MediaProjection.
+ *
+ * <p>
+ * A screen capture session can be started through {@link
+ * MediaProjectionManager#getScreenCaptureIntent}. This grants the ability to
+ * capture screen contents, but not system audio.
+ * </p>
+ */
+public final class MediaProjection {
+ private static final String TAG = "MediaProjection";
+
+ private final IMediaProjection mImpl;
+ private final Context mContext;
+ private final Map<Callback, CallbackRecord> mCallbacks;
+
+ /** @hide */
+ public MediaProjection(Context context, IMediaProjection impl) {
+ mCallbacks = new ArrayMap<Callback, CallbackRecord>();
+ mContext = context;
+ mImpl = impl;
+ try {
+ mImpl.start(new MediaProjectionCallback());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to start media projection", e);
+ }
+ }
+
+ /** Register a listener to receive notifications about when the {@link
+ * MediaProjection} changes state.
+ *
+ * @param callback The callback to call.
+ * @param handler The handler on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's looper.
+ *
+ * @see #removeCallback
+ */
+ public void addCallback(Callback callback, Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback should not be null");
+ }
+ mCallbacks.put(callback, new CallbackRecord(callback, handler));
+ }
+
+ /** Unregister a MediaProjection listener.
+ *
+ * @param callback The callback to unregister.
+ *
+ * @see #addCallback
+ */
+ public void removeCallback(Callback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback should not be null");
+ }
+ mCallbacks.remove(callback);
+ }
+
+ /**
+ * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
+ * contents of the screen.
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be
+ * greater than 0.
+ * @param height The height of the virtual display in pixels. Must be
+ * greater than 0.
+ * @param dpi The density of the virtual display in dpi. Must be greater
+ * than 0.
+ * @param surface The surface to which the content of the virtual display
+ * should be rendered, or null if there is none initially.
+ * @param isSecure Whether the display should be considered a secure
+ * display. This typically requires special permissions not available to
+ * third party applications.
+ * @param callbacks Callbacks to call when the virtual display's state
+ * changes, or null if none.
+ * @param handler The {@link android.os.Handler} on which the callback should be
+ * invoked, or null if the callback should be invoked on the calling
+ * thread's main {@link android.os.Looper}.
+ *
+ * @see android.hardware.display.DisplayManager#createVirtualDisplay(
+ * String, int, int, int, int, Surface, VirtualDisplay.Callbacks, Handler)
+ */
+ public VirtualDisplay createVirtualDisplay(@NonNull String name,
+ int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+ @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+ DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+ int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
+ return dm.createVirtualDisplay(
+ this, name, width, height, dpi, surface, flags, callbacks, handler);
+ }
+
+ /**
+ * Creates an AudioRecord to capture audio played back by the system.
+ */
+ public AudioRecord createAudioRecord(
+ int sampleRateInHz, int channelConfig,
+ int audioFormat, int bufferSizeInBytes) {
+ return null;
+ }
+
+ /**
+ * Stops projection.
+ */
+ public void stop() {
+ try {
+ mImpl.stop();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to stop projection", e);
+ }
+ }
+
+ /**
+ * Get the underlying IMediaProjection.
+ * @hide
+ */
+ public IMediaProjection getProjection() {
+ return mImpl;
+ }
+
+ /**
+ * Callbacks for the projection session.
+ */
+ public static abstract class Callback {
+ /**
+ * Called when the MediaProjection session is no longer valid.
+ *
+ * Once a MediaProjection has been stopped, it's up to the application to release any
+ * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s).
+ */
+ public void onStop() { }
+ }
+
+ private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
+ @Override
+ public void onStop() {
+ final int N = mCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mCallbacks.get(i).onStop();
+ }
+ }
+ }
+
+ private final static class CallbackRecord {
+ private Callback mCallback;
+ private Handler mHandler;
+
+ public CallbackRecord(Callback callback, Handler handler) {
+ mCallback = callback;
+ mHandler = handler;
+ }
+
+ public void onStop() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onStop();
+ }
+ });
+ }
+ }
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
new file mode 100644
index 0000000..aac8cf9
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.projection.IMediaProjection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Manages the retrieval of certain types of {@link MediaProjection} tokens.
+ *
+ * <p>
+ * Get an instance of this class by calling {@link
+ * android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument {@link
+ * android.content.Context#MEDIA_PROJECTION_SERVICE}.
+ * </p>
+ */
+public final class MediaProjectionManager {
+ /** @hide */
+ public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
+ /** @hide */
+ public static final String EXTRA_MEDIA_PROJECTION =
+ "android.media.projection.extra.EXTRA_MEDIA_PROJECTION";
+
+ /** @hide */
+ public static final int TYPE_SCREEN_CAPTURE = 0;
+ /** @hide */
+ public static final int TYPE_MIRRORING = 1;
+ /** @hide */
+ public static final int TYPE_PRESENTATION = 2;
+
+ private Context mContext;
+
+ /** @hide */
+ public MediaProjectionManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Returns an Intent that <b>must</b> passed to startActivityForResult()
+ * in order to start screen capture. The activity will prompt
+ * the user whether to allow screen capture. The result of this
+ * activity should be passed to getMediaProjection.
+ */
+ public Intent getScreenCaptureIntent() {
+ Intent i = new Intent();
+ i.setClassName("com.android.systemui",
+ "com.android.systemui.media.MediaProjectionPermissionActivity");
+ return i;
+ }
+
+ /**
+ * Retrieve the MediaProjection obtained from a succesful screen
+ * capture request. Will be null if the result from the
+ * startActivityForResult() is anything other than RESULT_OK.
+ *
+ * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int,
+ * int, android.content.Intent)}
+ * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int,
+ * int, android.content.Intent)}
+ */
+ public MediaProjection getMediaProjection(int resultCode, @NonNull Intent resultData) {
+ if (resultCode != Activity.RESULT_OK || resultData == null) {
+ return null;
+ }
+ IBinder projection = resultData.getIBinderExtra(EXTRA_MEDIA_PROJECTION);
+ if (projection == null) {
+ return null;
+ }
+ return new MediaProjection(mContext, IMediaProjection.Stub.asInterface(projection));
+ }
+}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4841360..086cd23 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -154,7 +154,7 @@
if (TextUtils.isEmpty(tag)) {
throw new IllegalArgumentException("tag cannot be null or empty");
}
- mCbStub = new CallbackStub();
+ mCbStub = new CallbackStub(this);
MediaSessionManager manager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
try {
@@ -769,7 +769,7 @@
public static class CallbackStub extends ISessionCallback.Stub {
private WeakReference<MediaSession> mMediaSession;
- public void setMediaSession(MediaSession session) {
+ public CallbackStub(MediaSession session) {
mMediaSession = new WeakReference<MediaSession>(session);
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 423e317..403b1ba 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -36,4 +36,5 @@
void onTrackInfoChanged(in List<TvTrackInfo> tracks, int seq);
void onVideoAvailable(int seq);
void onVideoUnavailable(int reason, int seq);
+ void onContentBlocked(in String rating, int seq);
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 6a0c592..16c4199 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -34,6 +34,7 @@
*/
interface ITvInputManager {
List<TvInputInfo> getTvInputList(int userId);
+ TvInputInfo getTvInputInfo(in String inputId, int userId);
void registerCallback(in ITvInputManagerCallback callback, int userId);
void unregisterCallback(in ITvInputManagerCallback callback, int userId);
@@ -42,6 +43,8 @@
void releaseSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+ void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+ int userId);
void setVolume(in IBinder sessionToken, float volume, int userId);
void tune(in IBinder sessionToken, in Uri channelUri, int userId);
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
@@ -53,6 +56,8 @@
void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
void removeOverlayView(in IBinder sessionToken, int userId);
+ void unblockContent(in IBinder sessionToken, in String unblockedRating, int userId);
+
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
/*
diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
index 5c8a0a3..50a7636 100644
--- a/media/java/android/media/tv/ITvInputManagerCallback.aidl
+++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl
@@ -22,4 +22,6 @@
*/
oneway interface ITvInputManagerCallback {
void onInputStateChanged(in String inputId, int state);
+ void onInputAdded(in String inputId);
+ void onInputRemoved(in String inputId);
}
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index 992e424..158223a 100644
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -18,6 +18,7 @@
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSessionCallback;
+import android.media.tv.TvInputHardwareInfo;
import android.view.InputChannel;
/**
@@ -27,5 +28,10 @@
oneway interface ITvInputService {
void registerCallback(ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
- void createSession(in InputChannel channel, ITvInputSessionCallback callback);
+ void createSession(in InputChannel channel, ITvInputSessionCallback callback,
+ in String inputId);
+
+ // For hardware TvInputService
+ void notifyHardwareAdded(in TvInputHardwareInfo info);
+ void notifyHardwareRemoved(int deviceId);
}
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index 1fdb8c5..287da71 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -16,13 +16,14 @@
package android.media.tv;
-import android.content.ComponentName;
+import android.media.tv.TvInputInfo;
/**
- * Helper interface for ITvInputService to allow the TV input to notify the client when its status
- * has been changed.
+ * Helper interface for ITvInputService to allow the service to notify the
+ * TvInputManagerService.
* @hide
*/
oneway interface ITvInputServiceCallback {
- void onInputStateChanged(int state);
+ void addTvInput(in TvInputInfo inputInfo);
+ void removeTvInput(in String inputId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 875fa34..a97daf2 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -29,6 +29,7 @@
void release();
void setSurface(in Surface surface);
+ void dispatchSurfaceChanged(int format, int width, int height);
// TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
// is to introduce some new concepts that will solve a number of problems in audio policy today.
void setVolume(float volume);
@@ -40,4 +41,6 @@
void createOverlayView(in IBinder windowToken, in Rect frame);
void relayoutOverlayView(in Rect frame);
void removeOverlayView();
+
+ void unblockContent(in String unblockedRating);
}
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index e0036e1..f529595 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -33,4 +33,5 @@
void onTrackInfoChanged(in List<TvTrackInfo> tracks);
void onVideoAvailable();
void onVideoUnavailable(int reason);
+ void onContentBlocked(in String rating);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 0c08590..eddc6c9 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -42,14 +42,16 @@
private static final int DO_RELEASE = 1;
private static final int DO_SET_SURFACE = 2;
- private static final int DO_SET_VOLUME = 3;
- private static final int DO_TUNE = 4;
- private static final int DO_SET_CAPTION_ENABLED = 5;
- private static final int DO_SELECT_TRACK = 6;
- private static final int DO_UNSELECT_TRACK = 7;
- private static final int DO_CREATE_OVERLAY_VIEW = 8;
- private static final int DO_RELAYOUT_OVERLAY_VIEW = 9;
- private static final int DO_REMOVE_OVERLAY_VIEW = 10;
+ private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+ private static final int DO_SET_VOLUME = 4;
+ private static final int DO_TUNE = 5;
+ private static final int DO_SET_CAPTION_ENABLED = 6;
+ private static final int DO_SELECT_TRACK = 7;
+ private static final int DO_UNSELECT_TRACK = 8;
+ private static final int DO_CREATE_OVERLAY_VIEW = 9;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 10;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 11;
+ private static final int DO_UNBLOCK_CONTENT = 12;
private final HandlerCaller mCaller;
@@ -91,6 +93,12 @@
mTvInputSessionImpl.setSurface((Surface) msg.obj);
return;
}
+ case DO_DISPATCH_SURFACE_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.dispatchSurfaceChanged(args.argi1, args.argi2, args.argi3);
+ args.recycle();
+ return;
+ }
case DO_SET_VOLUME: {
mTvInputSessionImpl.setVolume((Float) msg.obj);
return;
@@ -125,6 +133,10 @@
mTvInputSessionImpl.removeOverlayView(true);
return;
}
+ case DO_UNBLOCK_CONTENT: {
+ mTvInputSessionImpl.unblockContent((String) msg.obj);
+ return;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
@@ -143,6 +155,12 @@
}
@Override
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED,
+ format, width, height, 0));
+ }
+
+ @Override
public final void setVolume(float volume) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
}
@@ -183,6 +201,11 @@
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
}
+ @Override
+ public void unblockContent(String unblockedRating) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UNBLOCK_CONTENT, unblockedRating));
+ }
+
private final class TvInputEventReceiver extends InputEventReceiver {
public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 905b0bd..986a7d9 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -16,18 +16,20 @@
package android.media.tv;
+import android.annotation.SystemApi;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A class representing a TV content rating.
*/
-public class TvContentRating {
+public final class TvContentRating {
private static final String TAG = "TvContentRating";
private static final int RATING_PREFIX_LENGTH = 10;
@@ -124,10 +126,10 @@
// A mapping from two-letter country code (ISO 3166-1 alpha-2) to its rating-to-sub-ratings map.
// This is used for validating the builder parameters.
private static final Map<String, Map<String, String[]>> sRatings
- = new HashMap<String, Map<String, String[]>>();
+ = new ArrayMap<String, Map<String, String[]>>();
static {
- Map<String, String[]> usRatings = new HashMap<String, String[]>();
+ Map<String, String[]> usRatings = new ArrayMap<String, String[]>();
usRatings.put(RATING_US_TV_Y, null);
usRatings.put(RATING_US_TV_Y7, new String[] { SUBRATING_US_FV });
usRatings.put(RATING_US_TV_G, null);
@@ -139,7 +141,7 @@
SUBRATING_US_L, SUBRATING_US_S, SUBRATING_US_V });
sRatings.put(PREFIX_RATING_US, usRatings);
- Map<String, String[]> krRatings = new HashMap<String, String[]>();
+ Map<String, String[]> krRatings = new ArrayMap<String, String[]>();
krRatings.put(RATING_KR_ALL, null);
krRatings.put(RATING_KR_7, null);
krRatings.put(RATING_KR_12, null);
@@ -157,8 +159,7 @@
* @param rating The rating constant defined in this class.
*/
public TvContentRating(String rating) {
- mRating = rating;
- mSubRatings = null;
+ this(rating, null);
}
/**
@@ -168,11 +169,11 @@
* @param subRatings The String array of sub-rating constants defined in this class.
*/
public TvContentRating(String rating, String[] subRatings) {
- mRating = rating;
- mSubRatings = subRatings;
- if (TextUtils.isEmpty(mRating)) {
+ if (TextUtils.isEmpty(rating)) {
throw new IllegalArgumentException("rating cannot be null");
}
+ mRating = rating;
+ mSubRatings = subRatings;
String prefix = "";
if (mRating.length() > RATING_PREFIX_LENGTH) {
prefix = mRating.substring(0, RATING_PREFIX_LENGTH);
@@ -188,6 +189,10 @@
} else {
List<String> validSubRatingList = Arrays.asList(subRatings);
for (String sr : mSubRatings) {
+ if (TextUtils.isEmpty(sr)) {
+ throw new IllegalArgumentException(
+ "subRatings cannot contain empty elements");
+ }
if (!validSubRatingList.contains(sr)) {
Log.w(TAG, "Invalid subrating: " + sr);
break;
@@ -201,6 +206,52 @@
}
/**
+ * Returns the main rating constant.
+ *
+ * @return the rating string that starts with "RATING_" prefix as defined in this class.
+ */
+ public String getMainRating() {
+ return mRating;
+ }
+
+ /**
+ * Returns the list of sub-rating constants.
+ *
+ * @return the unmodifiable {@code List} of sub-rating strings that start with "SUBRATING_"
+ * prefix as defined in this class.
+ */
+ public List<String> getSubRatings() {
+ if (mSubRatings == null) {
+ return null;
+ }
+ return Collections.unmodifiableList(Arrays.asList(mSubRatings));
+ }
+
+
+ /**
+ * Returns a String that unambiguously describes both the rating and sub-rating information
+ * contained in the TvContentRating. You can later recover the TvContentRating from this string
+ * through {@link #unflattenFromString}.
+ *
+ * @return a new String holding rating/sub-rating information, which can later be stored in the
+ * database and settings.
+ * @see #unflattenFromString
+ */
+ public String flattenToString() {
+ // TODO: Consider removing all obvious/redundant sub-strings including "RATING" and
+ // "SUBRATING" and find out a storage-efficient string format such as:
+ // <country>-<primary>/<sub1>/<sub2>/<sub3>
+ StringBuilder builder = new StringBuilder(mRating);
+ if (mSubRatings != null) {
+ for (String subRating : mSubRatings) {
+ builder.append(DELIMITER);
+ builder.append(subRating);
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
* Recovers a TvContentRating from a String that was previously created with
* {@link #flattenToString}.
*
@@ -226,20 +277,35 @@
}
/**
- * @return a String that unambiguously describes both the rating and sub-rating information
- * contained in the TvContentRating. You can later recover the TvContentRating from this
- * string through {@link #unflattenFromString}.
- * @see #unflattenFromString
+ * Returns true if this rating has the same main rating as the specified rating and when this
+ * rating's sub-ratings contain the other's.
+ * <p>
+ * For example, a TvContentRating object that represents TV-PG with S(Sexual content) and
+ * V(Violence) contains TV-PG, TV-PG/S, TV-PG/V and itself.
+ * </p>
+ *
+ * @param rating The {@link TvContentRating} to check.
+ * @return {@code true} if this object contains {@code rating}, {@code false} otherwise.
+ * @hide
*/
- public String flattenToString() {
- StringBuffer ratingStr = new StringBuffer();
- ratingStr.append(mRating);
- if (mSubRatings != null) {
- for (String subRating : mSubRatings) {
- ratingStr.append(DELIMITER);
- ratingStr.append(subRating);
- }
+ @SystemApi
+ public final boolean contains(TvContentRating rating) {
+ if (rating == null) {
+ throw new IllegalArgumentException("rating cannot be null");
}
- return ratingStr.toString();
+ if (!rating.getMainRating().equals(mRating)) {
+ return false;
+ }
+ List<String> subRatings = getSubRatings();
+ List<String> subRatingsOther = rating.getSubRatings();
+ if (subRatings == null && subRatingsOther == null) {
+ return true;
+ } else if (subRatings == null && subRatingsOther != null) {
+ return false;
+ } else if (subRatings != null && subRatingsOther == null) {
+ return true;
+ } else {
+ return subRatings.containsAll(subRatingsOther);
+ }
}
}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 8ecf808..78e0404 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,9 +16,9 @@
package android.media.tv;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
-import android.media.tv.TvContract.Programs;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.ArraySet;
@@ -53,6 +53,7 @@
private static final String PATH_CHANNEL = "channel";
private static final String PATH_PROGRAM = "program";
private static final String PATH_INPUT = "input";
+ private static final String PATH_PASSTHROUGH = "passthrough";
/**
* An optional query, update or delete URI parameter that allows the caller to specify start
@@ -88,6 +89,16 @@
public static final String PARAM_CANONICAL_GENRE = "canonical_genre";
/**
+ * Builds an ID that uniquely identifies a TV input service.
+ *
+ * @param name The {@link ComponentName} of the TV input service to build ID for.
+ * @return the ID for the given TV input service.
+ */
+ public static final String buildInputId(ComponentName name) {
+ return name.flattenToShortString();
+ }
+
+ /**
* Builds a URI that points to a specific channel.
*
* @param channelId The ID of the channel to point to.
@@ -97,6 +108,18 @@
}
/**
+ * Build a special channel URI intended to be used with pass-through type inputs. (e.g. HDMI)
+ *
+ * @param inputId The ID of the TV input to build a channels URI for.
+ * @see TvInputInfo#isPassthroughInputType()
+ */
+ public static final Uri buildChannelUriForPassthroughTvInput(String inputId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath(PATH_INPUT).appendPath(inputId).appendPath(PATH_CHANNEL)
+ .appendPath(PATH_PASSTHROUGH).build();
+ }
+
+ /**
* Builds a URI that points to a channel logo. See {@link Channels.Logo}.
*
* @param channelId The ID of the channel whose logo is pointed to.
@@ -118,12 +141,12 @@
}
/**
- * Builds a URI that points to all browsable channels from a given TV input.
+ * Builds a URI that points to all channels from a given TV input.
*
* @param inputId The ID of the TV input to build a channels URI for.
*/
public static final Uri buildChannelsUriForInput(String inputId) {
- return buildChannelsUriForInput(inputId, true);
+ return buildChannelsUriForInput(inputId, false);
}
/**
@@ -133,6 +156,7 @@
* @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
* to {@code false} the URI points to all channels regardless of whether they are
* browsable or not.
+ * @hide
*/
public static final Uri buildChannelsUriForInput(String inputId, boolean browsableOnly) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
@@ -327,68 +351,65 @@
/** The channel type for SECAM. */
public static final int TYPE_SECAM = 0x3;
- /** The special channel type used for pass-through inputs such as HDMI. */
- public static final int TYPE_PASSTHROUGH = 0x00010000;
-
/** The channel type for DVB-T (terrestrial). */
- public static final int TYPE_DVB_T = 0x00020000;
+ public static final int TYPE_DVB_T = 0x00010000;
/** The channel type for DVB-T2 (terrestrial). */
- public static final int TYPE_DVB_T2 = 0x00020001;
+ public static final int TYPE_DVB_T2 = 0x00010001;
/** The channel type for DVB-S (satellite). */
- public static final int TYPE_DVB_S = 0x00020100;
+ public static final int TYPE_DVB_S = 0x00010100;
/** The channel type for DVB-S2 (satellite). */
- public static final int TYPE_DVB_S2 = 0x00020101;
+ public static final int TYPE_DVB_S2 = 0x00010101;
/** The channel type for DVB-C (cable). */
- public static final int TYPE_DVB_C = 0x00020200;
+ public static final int TYPE_DVB_C = 0x00010200;
/** The channel type for DVB-C2 (cable). */
- public static final int TYPE_DVB_C2 = 0x00020201;
+ public static final int TYPE_DVB_C2 = 0x00010201;
/** The channel type for DVB-H (handheld). */
- public static final int TYPE_DVB_H = 0x00020300;
+ public static final int TYPE_DVB_H = 0x00010300;
/** The channel type for DVB-SH (satellite). */
- public static final int TYPE_DVB_SH = 0x00020400;
+ public static final int TYPE_DVB_SH = 0x00010400;
/** The channel type for ATSC (terrestrial). */
- public static final int TYPE_ATSC_T = 0x00030000;
+ public static final int TYPE_ATSC_T = 0x00020000;
/** The channel type for ATSC (cable). */
- public static final int TYPE_ATSC_C = 0x00030200;
+ public static final int TYPE_ATSC_C = 0x00020200;
/** The channel type for ATSC-M/H (mobile/handheld). */
- public static final int TYPE_ATSC_M_H = 0x00030300;
+ public static final int TYPE_ATSC_M_H = 0x00020300;
/** The channel type for ISDB-T (terrestrial). */
- public static final int TYPE_ISDB_T = 0x00040000;
+ public static final int TYPE_ISDB_T = 0x00030000;
/** The channel type for ISDB-Tb (Brazil). */
- public static final int TYPE_ISDB_TB = 0x00040100;
+ public static final int TYPE_ISDB_TB = 0x00030100;
/** The channel type for ISDB-S (satellite). */
- public static final int TYPE_ISDB_S = 0x00040200;
+ public static final int TYPE_ISDB_S = 0x00030200;
/** The channel type for ISDB-C (cable). */
- public static final int TYPE_ISDB_C = 0x00040300;
+ public static final int TYPE_ISDB_C = 0x00030300;
/** The channel type for 1seg (handheld). */
- public static final int TYPE_1SEG = 0x00040400;
+ public static final int TYPE_1SEG = 0x00030400;
/** The channel type for DTMB (terrestrial). */
- public static final int TYPE_DTMB = 0x00050000;
+ public static final int TYPE_DTMB = 0x00040000;
/** The channel type for CMMB (handheld). */
- public static final int TYPE_CMMB = 0x00050100;
+ public static final int TYPE_CMMB = 0x00040100;
/** The channel type for T-DMB (terrestrial). */
- public static final int TYPE_T_DMB = 0x00060000;
+ public static final int TYPE_T_DMB = 0x00050000;
/** The channel type for S-DMB (satellite). */
- public static final int TYPE_S_DMB = 0x00060100;
+ public static final int TYPE_S_DMB = 0x00050100;
/** A generic service type. */
public static final int SERVICE_TYPE_OTHER = 0x0;
@@ -475,7 +496,9 @@
}
/**
- * The ID of the TV input that provides this TV channel.
+ * The ID of the TV input service that provides this TV channel.
+ * <p>
+ * Use {@link #buildInputId} to build the ID.
* <p>
* This is a required field.
* </p><p>
@@ -488,14 +511,13 @@
* The predefined type of this TV channel.
* <p>
* This is primarily used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the
- * current channel conforms to, with an exception being {@link #TYPE_PASSTHROUGH}, which is
- * a special channel type used only by pass-through inputs such as HDMI. The value should
- * match to one of the followings: {@link #TYPE_OTHER}, {@link #TYPE_PASSTHROUGH},
- * {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S}, {@link #TYPE_DVB_S2},
- * {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H}, {@link #TYPE_DVB_SH},
- * {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C}, {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T},
- * {@link #TYPE_ISDB_TB}, {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C} {@link #TYPE_1SEG},
- * {@link #TYPE_DTMB}, {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB}
+ * current channel conforms to. The value should match to one of the followings:
+ * {@link #TYPE_OTHER}, {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S},
+ * {@link #TYPE_DVB_S2}, {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H},
+ * {@link #TYPE_DVB_SH}, {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C},
+ * {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T}, {@link #TYPE_ISDB_TB},
+ * {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C}, {@link #TYPE_1SEG}, {@link #TYPE_DTMB},
+ * {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB}
* </p><p>
* This is a required field.
* </p><p>
@@ -644,6 +666,7 @@
* </p><p>
* Type: INTEGER (boolean)
* </p>
+ * @hide
*/
public static final String COLUMN_BROWSABLE = "browsable";
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index e5f9889..d7d3cf2 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.SystemApi;
import android.media.AudioManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,7 @@
*
* @hide
*/
+@SystemApi
public final class TvInputHardwareInfo implements Parcelable {
static final String TAG = "TvInputHardwareInfo";
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 37f166b..e7d8b4e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -27,6 +27,8 @@
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.media.tv.TvInputHardwareInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -55,17 +57,45 @@
// Should be in sync with hardware/libhardware/include/hardware/tv_input.h
/**
- * TV input type: the TV input service is HDMI. (e.g. HDMI 1)
+ * TV input type: a generic hardware TV input type.
*/
- public static final int TYPE_HDMI = 1;
+ public static final int TYPE_OTHER_HARDWARE = 1;
/**
* TV input type: the TV input service is a tuner. (e.g. terrestrial tuner)
*/
public static final int TYPE_TUNER = 2;
/**
- * TV input type: the TV input service is stateless pass-through. (e.g. RGB, composite, etc.)
+ * TV input type: the TV input service is HDMI. (e.g. HDMI 1)
*/
- public static final int TYPE_PASSTHROUGH = 3;
+ public static final int TYPE_HDMI = 3;
+ /**
+ * TV input type: the TV input service represents a display port.
+ */
+ public static final int TYPE_DISPLAY_PORT = 4;
+ /**
+ * TV input type: the TV input service represents a SCART port.
+ */
+ public static final int TYPE_SCART = 5;
+ /**
+ * TV input type: the TV input service represents a DVI port.
+ */
+ public static final int TYPE_DVI = 6;
+ /**
+ * TV input type: the TV input service represents a VGA port.
+ */
+ public static final int TYPE_VGA = 7;
+ /**
+ * TV input type: the TV input service represents a component port.
+ */
+ public static final int TYPE_COMPONENT = 8;
+ /**
+ * TV input type: the TV input service represents a composite port.
+ */
+ public static final int TYPE_COMPOSITE = 9;
+ /**
+ * TV input type: the TV input service represents a SVIDEO port.
+ */
+ public static final int TYPE_SVIDEO = 10;
/**
* The ID of the TV input to provide to the setup activity and settings activity.
@@ -88,9 +118,46 @@
* instantiating it from the given Context and ResolveInfo.
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
- * @hide */
+ * @hide
+ */
public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForComponentName(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name)));
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class,
+ * instantiating it from the given Context, ResolveInfo, and HdmiCecDeviceInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param cecInfo The HdmiCecDeviceInfo for a HDMI CEC logical device.
+ * @hide
+ */
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ HdmiCecDeviceInfo cecInfo) throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForHdmiCec(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ cecInfo));
+ }
+
+ /**
+ * Create a new instance of the TvInputInfo class,
+ * instantiating it from the given Context, ResolveInfo, and TvInputHardwareInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @param hardwareInfo The TvInputHardwareInfo for a TV input hardware device.
+ * @hide
+ */
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ TvInputHardwareInfo hardwareInfo) throws XmlPullParserException, IOException {
+ return createTvInputInfo(context, service, generateInputIdForHardware(
+ new ComponentName(service.serviceInfo.packageName, service.serviceInfo.name),
+ hardwareInfo));
+ }
+
+ private static TvInputInfo createTvInputInfo(Context context, ResolveInfo service,
+ String id) throws XmlPullParserException, IOException {
ServiceInfo si = service.serviceInfo;
PackageManager pm = context.getPackageManager();
XmlResourceParser parser = null;
@@ -115,7 +182,7 @@
"Meta-data does not start with tv-input-service tag in " + si.name);
}
- TvInputInfo input = new TvInputInfo(context, service, null);
+ TvInputInfo input = new TvInputInfo(context, service, id, null);
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.TvInputService);
input.mSetupActivity = sa.getString(
@@ -153,12 +220,13 @@
* Constructor.
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
- * @hide
+ * @param id ID of this TV input. Should be generated via generateInputId*().
+ * @param parentId ID of this TV input's parent input. {@code null} if none exists.
*/
- private TvInputInfo(Context context, ResolveInfo service, String parentId) {
+ private TvInputInfo(Context context, ResolveInfo service, String id, String parentId) {
mService = service;
ServiceInfo si = service.serviceInfo;
- mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name));
+ mId = id;
mParentId = parentId;
}
@@ -237,26 +305,41 @@
}
/**
+ * Returns {@code true} if this TV input is pass-though which does not have any real channels
+ * in TvProvider. {@code false} otherwise.
+ *
+ * @see TvContract#buildChannelUriForPassthroughTvInput(String)
+ */
+ public boolean isPassthroughInputType() {
+ if (mType == TYPE_HDMI || mType == TYPE_DISPLAY_PORT || mType == TYPE_SCART
+ || mType == TYPE_DVI || mType == TYPE_VGA || mType == TYPE_COMPONENT
+ || mType == TYPE_COMPOSITE || mType == TYPE_SVIDEO) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Loads the user-displayed label for this TV input service.
*
- * @param pm Supplies a PackageManager used to load the TV input's resources.
+ * @param context Supplies a {@link Context} used to load the label.
* @return a CharSequence containing the TV input's label. If the TV input does not have
* a label, its name is returned.
*/
- public CharSequence loadLabel(PackageManager pm) {
- return mService.loadLabel(pm);
+ public CharSequence loadLabel(Context context) {
+ return mService.loadLabel(context.getPackageManager());
}
/**
* Loads the user-displayed icon for this TV input service.
*
- * @param pm Supplies a PackageManager used to load the TV input's resources.
+ * @param context Supplies a {@link Context} used to load the icon.
* @return a Drawable containing the TV input's icon. If the TV input does not have
* an icon, application icon is returned. If it's unavailable too, system default is
* returned.
*/
- public Drawable loadIcon(PackageManager pm) {
- return mService.serviceInfo.loadIcon(pm);
+ public Drawable loadIcon(Context context) {
+ return mService.serviceInfo.loadIcon(context.getPackageManager());
}
@Override
@@ -311,13 +394,37 @@
*
* @param name the component name for generating an input id.
* @return the generated input id for the given {@code name}.
- * @hide
*/
- public static final String generateInputIdForComponentName(ComponentName name) {
+ private static final String generateInputIdForComponentName(ComponentName name) {
return name.flattenToShortString();
}
/**
+ * Used to generate an input id from a ComponentName and HdmiCecDeviceInfo.
+ *
+ * @param name the component name for generating an input id.
+ * @param cecInfo HdmiCecDeviceInfo describing this TV input.
+ * @return the generated input id for the given {@code name} and {@code cecInfo}.
+ */
+ private static final String generateInputIdForHdmiCec(
+ ComponentName name, HdmiCecDeviceInfo cecInfo) {
+ return name.flattenToShortString() + String.format("|CEC%08X%08X",
+ cecInfo.getPhysicalAddress(), cecInfo.getLogicalAddress());
+ }
+
+ /**
+ * Used to generate an input id from a ComponentName and TvInputHardwareInfo
+ *
+ * @param name the component name for generating an input id.
+ * @param hardwareInfo TvInputHardwareInfo describing this TV input.
+ * @return the generated input id for the given {@code name} and {@code hardwareInfo}.
+ */
+ private static final String generateInputIdForHardware(
+ ComponentName name, TvInputHardwareInfo hardwareInfo) {
+ return name.flattenToShortString() + String.format("|HW%d", hardwareInfo.getDeviceId());
+ }
+
+ /**
* Used to make this class parcelable.
*
* @hide
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 79a83b0..910b725 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -36,7 +36,6 @@
import android.view.View;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -49,10 +48,13 @@
public final class TvInputManager {
private static final String TAG = "TvInputManager";
+ static final int VIDEO_UNAVAILABLE_REASON_START = 0;
+ static final int VIDEO_UNAVAILABLE_REASON_END = 3;
+
/**
* A generic reason. Video is not available due to an unspecified error.
*/
- public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0;
+ public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START;
/**
* Video is not available because the TV input is tuning to another channel.
*/
@@ -65,13 +67,13 @@
* Video is not available because the TV input stopped the playback temporarily to buffer more
* data.
*/
- public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
+ public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END;
/**
* The TV input is connected.
* <p>
* State for {@link #getInputState} and {@link
- * TvInputManager.TvInputListener#onInputStateChanged}.
+ * TvInputManager.TvInputCallback#onInputStateChanged}.
* </p>
*/
public static final int INPUT_STATE_CONNECTED = 0;
@@ -80,7 +82,7 @@
* fully ready.
* <p>
* State for {@link #getInputState} and {@link
- * TvInputManager.TvInputListener#onInputStateChanged}.
+ * TvInputManager.TvInputCallback#onInputStateChanged}.
* </p>
*/
public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
@@ -88,7 +90,7 @@
* The TV input is disconnected.
* <p>
* State for {@link #getInputState} and {@link
- * TvInputManager.TvInputListener#onInputStateChanged}.
+ * TvInputManager.TvInputCallback#onInputStateChanged}.
* </p>
*/
public static final int INPUT_STATE_DISCONNECTED = 2;
@@ -98,8 +100,8 @@
private final Object mLock = new Object();
// @GuardedBy(mLock)
- private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
- new LinkedList<TvInputListenerRecord>();
+ private final List<TvInputCallbackRecord> mTvInputCallbackRecordsList =
+ new LinkedList<TvInputCallbackRecord>();
// A mapping from TV input ID to the state of corresponding input.
// @GuardedBy(mLock)
@@ -185,6 +187,15 @@
}
/**
+ * This is called when the current program content is blocked by parental controls.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param rating The content ration of the blocked program.
+ */
+ public void onContentBlocked(Session session, TvContentRating rating) {
+ }
+
+ /**
* This is called when a custom event has been sent from this session.
*
* @param session A {@link TvInputManager.Session} associated with this callback
@@ -263,6 +274,15 @@
});
}
+ public void postContentBlocked(final TvContentRating rating) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onContentBlocked(mSession, rating);
+ }
+ });
+ }
+
public void postSessionEvent(final String eventType, final Bundle eventArgs) {
mHandler.post(new Runnable() {
@Override
@@ -276,12 +296,12 @@
/**
* Interface used to monitor status of the TV input.
*/
- public abstract static class TvInputListener {
+ public abstract static class TvInputCallback {
/**
* This is called when the state of a given TV input is changed.
*
- * @param inputId the id of the TV input.
- * @param state state of the TV input. The value is one of the following:
+ * @param inputId The id of the TV input.
+ * @param state State of the TV input. The value is one of the following:
* <ul>
* <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
* <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
@@ -290,26 +310,60 @@
*/
public void onInputStateChanged(String inputId, int state) {
}
+
+ /**
+ * This is called when a TV input is added.
+ *
+ * @param inputId The id of the TV input.
+ */
+ public void onInputAdded(String inputId) {
+ }
+
+ /**
+ * This is called when a TV input is removed.
+ *
+ * @param inputId The id of the TV input.
+ */
+ public void onInputRemoved(String inputId) {
+ }
}
- private static final class TvInputListenerRecord {
- private final TvInputListener mListener;
+ private static final class TvInputCallbackRecord {
+ private final TvInputCallback mCallback;
private final Handler mHandler;
- public TvInputListenerRecord(TvInputListener listener, Handler handler) {
- mListener = listener;
+ public TvInputCallbackRecord(TvInputCallback callback, Handler handler) {
+ mCallback = callback;
mHandler = handler;
}
- public TvInputListener getListener() {
- return mListener;
+ public TvInputCallback getCallback() {
+ return mCallback;
}
- public void postStateChanged(final String inputId, final int state) {
+ public void postInputStateChanged(final String inputId, final int state) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mListener.onInputStateChanged(inputId, state);
+ mCallback.onInputStateChanged(inputId, state);
+ }
+ });
+ }
+
+ public void postInputAdded(final String inputId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onInputAdded(inputId);
+ }
+ });
+ }
+
+ public void postInputRemoved(final String inputId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onInputRemoved(inputId);
}
});
}
@@ -403,6 +457,18 @@
}
@Override
+ public void onContentBlocked(String rating, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postContentBlocked(TvContentRating.unflattenFromString(rating));
+ }
+ }
+
+ @Override
public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -419,8 +485,28 @@
public void onInputStateChanged(String inputId, int state) {
synchronized (mLock) {
mStateMap.put(inputId, state);
- for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
- record.postStateChanged(inputId, state);
+ for (TvInputCallbackRecord record : mTvInputCallbackRecordsList) {
+ record.postInputStateChanged(inputId, state);
+ }
+ }
+ }
+
+ @Override
+ public void onInputAdded(String inputId) {
+ synchronized (mLock) {
+ mStateMap.put(inputId, INPUT_STATE_CONNECTED);
+ for (TvInputCallbackRecord record : mTvInputCallbackRecordsList) {
+ record.postInputAdded(inputId);
+ }
+ }
+ }
+
+ @Override
+ public void onInputRemoved(String inputId) {
+ synchronized (mLock) {
+ mStateMap.remove(inputId);
+ for (TvInputCallbackRecord record : mTvInputCallbackRecordsList) {
+ record.postInputRemoved(inputId);
}
}
}
@@ -446,6 +532,20 @@
}
/**
+ * Returns the {@link TvInputInfo} for a given TV input.
+ *
+ * @param inputId The ID of the TV input.
+ * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
+ */
+ public TvInputInfo getTvInputInfo(String inputId) {
+ try {
+ return mService.getTvInputInfo(inputId, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Returns the state of a given TV input. It retuns one of the following:
* <ul>
* <li>{@link #INPUT_STATE_CONNECTED}
@@ -453,7 +553,7 @@
* <li>{@link #INPUT_STATE_DISCONNECTED}
* </ul>
*
- * @param inputId the id of the TV input.
+ * @param inputId The id of the TV input.
* @throws IllegalArgumentException if the argument is {@code null} or if there is no
* {@link TvInputInfo} corresponding to {@code inputId}.
*/
@@ -471,39 +571,39 @@
}
/**
- * Registers a {@link TvInputListener}.
+ * Registers a {@link TvInputCallback}.
*
- * @param listener a listener used to monitor status of the TV inputs.
- * @param handler a {@link Handler} that the status change will be delivered to.
+ * @param callback A callback used to monitor status of the TV inputs.
+ * @param handler A {@link Handler} that the status change will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
- public void registerListener(TvInputListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
+ public void registerCallback(TvInputCallback callback, Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
synchronized (mLock) {
- mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
+ mTvInputCallbackRecordsList.add(new TvInputCallbackRecord(callback, handler));
}
}
/**
- * Unregisters the existing {@link TvInputListener}.
+ * Unregisters the existing {@link TvInputCallback}.
*
- * @param listener the existing listener to remove.
+ * @param callback The existing callback to remove.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
- public void unregisterListener(final TvInputListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
+ public void unregisterCallback(final TvInputCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
}
synchronized (mLock) {
- for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+ for (Iterator<TvInputCallbackRecord> it = mTvInputCallbackRecordsList.iterator();
it.hasNext(); ) {
- TvInputListenerRecord record = it.next();
- if (record.getListener() == listener) {
+ TvInputCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
it.remove();
break;
}
@@ -518,9 +618,9 @@
* the given TV input.
* </p>
*
- * @param inputId the id of the TV input.
- * @param callback a callback used to receive the created session.
- * @param handler a {@link Handler} that the session creation will be delivered to.
+ * @param inputId The id of the TV input.
+ * @param callback A callback used to receive the created session.
+ * @param handler A {@link Handler} that the session creation will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
* @hide
*/
@@ -623,6 +723,27 @@
}
/**
+ * Notifies of any structural changes (format or size) of the {@link Surface}
+ * passed by {@link #setSurface}.
+ *
+ * @param format The new PixelFormat of the {@link Surface}.
+ * @param width The new width of the {@link Surface}.
+ * @param height The new height of the {@link Surface}.
+ * @hide
+ */
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Sets the relative stream volume of this session to handle a change of audio focus.
*
* @param volume A volume value between 0.0f to 1.0f.
@@ -685,7 +806,7 @@
/**
* Select a track.
*
- * @param track the track to be selected.
+ * @param track The track to be selected.
* @see #getTracks()
*/
public void selectTrack(TvTrackInfo track) {
@@ -706,7 +827,7 @@
/**
* Unselect a track.
*
- * @param track the track to be selected.
+ * @param track The track to be selected.
* @see #getTracks()
*/
public void unselectTrack(TvTrackInfo track) {
@@ -811,12 +932,27 @@
}
/**
+ * Unblock content blocked by parental controls.
+ */
+ void unblockContent(TvContentRating unblockedRating) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.unblockContent(mToken, unblockedRating.flattenToString(), mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Dispatches an input event to this session.
*
- * @param event {@link InputEvent} to dispatch.
+ * @param event An {@link InputEvent} to dispatch.
* @param token A token used to identify the input event later in the callback.
* @param callback A callback used to receive the dispatch result.
- * @param handler {@link Handler} that the dispatch result will be delivered to.
+ * @param handler A {@link Handler} that the dispatch result will be delivered to.
* @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
* {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
* {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
@@ -860,7 +996,7 @@
/**
* Called when the dispatched input event is finished.
*
- * @param token a token passed to {@link #dispatchInputEvent}.
+ * @param token A token passed to {@link #dispatchInputEvent}.
* @param handled {@code true} if the dispatched input event was handled properly.
* {@code false} otherwise.
*/
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 3206320..4f1f988 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -17,8 +17,8 @@
package android.media.tv;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.app.Service;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
@@ -104,7 +104,8 @@
}
@Override
- public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
+ public void createSession(InputChannel channel, ITvInputSessionCallback cb,
+ String inputId) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
@@ -114,8 +115,21 @@
SomeArgs args = SomeArgs.obtain();
args.arg1 = channel;
args.arg2 = cb;
+ args.arg3 = inputId;
mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
+
+ @Override
+ public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
+ mHandler.obtainMessage(ServiceHandler.DO_ADD_TV_INPUT_FROM_HARDWARE,
+ hardwareInfo).sendToTarget();
+ }
+
+ @Override
+ public void notifyHardwareRemoved(int deviceId) {
+ mHandler.obtainMessage(ServiceHandler.DO_REMOVE_TV_INPUT_FROM_HARDWARE,
+ deviceId, 0).sendToTarget();
+ }
};
}
@@ -134,8 +148,33 @@
* <p>
* May return {@code null} if this TV input service fails to create a session for some reason.
* </p>
+ * @param inputId The ID of the TV input associated with the session.
*/
- public abstract Session onCreateSession();
+ public abstract Session onCreateSession(String inputId);
+
+ /**
+ * Returns a new TvInputInfo object if this service is responsible for {@code hardwareInfo};
+ * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
+ *
+ * @param hardwareInfo TvInputHardwareInfo object just added.
+ *
+ * @hide
+ */
+ @SystemApi
+ public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
+ return null;
+ }
+
+ /**
+ * Returns the input ID for {@code deviceId} if it is handled by this service;
+ * otherwise, return {@code null}. Override to modify default behavior of ignoring all input.
+ *
+ * @hide
+ */
+ @SystemApi
+ public String onHardwareRemoved(int deviceId) {
+ return null;
+ }
/**
* Base class for derived classes to implement to provide a TV input session.
@@ -267,6 +306,42 @@
}
/**
+ * Informs the application that the current program content is blocked by parent controls.
+ * <p>
+ * Each TV input service is required to query the system whether the user is allowed to
+ * watch the current program before showing it to the user if the parental control is turned
+ * on, which can be checked by calling {@link TvParentalControlManager#isEnabled}. Whether
+ * the TV input service should block the content or not is determined by invoking
+ * {@link TvParentalControlManager#isRatingBlocked} with the content rating for the current
+ * program. Then the TvParentalControlManager makes a judgment based on the user blocked
+ * ratings stored in the secure settings and returns the result. If the rating in question
+ * turns out to be blocked, the TV input service must immediately block the content and call
+ * this method with the content rating of the current program to prompt the PIN verification
+ * screen.
+ * </p><p>
+ * Each TV input service also needs to continuously listen to any changes made to the
+ * parental control settings by registering a
+ * {@link TvParentalControlManager.ParentalControlCallback} to the manager and immediately
+ * reevaluate the current program with the new parental control settings.
+ * </p>
+ *
+ * @param rating The content rating for the current TV program.
+ */
+ public void dispatchContentBlocked(final TvContentRating rating) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchContentBlocked");
+ mSessionCallback.onContentBlocked(rating.flattenToString());
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in dispatchContentBlocked");
+ }
+ }
+ });
+ }
+
+ /**
* Informs the application that video is not available, so the TV input cannot continue
* playing the TV stream.
*
@@ -279,10 +354,8 @@
* </ul>
*/
public void dispatchVideoUnavailable(final int reason) {
- if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNE
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
+ if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
+ || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
throw new IllegalArgumentException("Unknown reason: " + reason);
}
mHandler.post(new Runnable() {
@@ -313,6 +386,18 @@
public abstract boolean onSetSurface(Surface surface);
/**
+ * Called after any structural changes (format or size) have been made to the
+ * {@link Surface} passed by {@link #onSetSurface}. This method is always called
+ * at least once, after {@link #onSetSurface} with non-null {@link Surface} is called.
+ *
+ * @param format The new PixelFormat of the {@link Surface}.
+ * @param width The new width of the {@link Surface}.
+ * @param height The new height of the {@link Surface}.
+ */
+ public void onSurfaceChanged(int format, int width, int height) {
+ }
+
+ /**
* Sets the relative stream volume of the current TV input session to handle the change of
* audio focus by setting.
*
@@ -342,6 +427,21 @@
public abstract void onSetCaptionEnabled(boolean enabled);
/**
+ * Called when the user allowed to unblock the content.
+ * <p>
+ * The implementation should unblock the content.
+ * TV input service has responsibility to decide when/how the unblock expires
+ * while it can keep previously unblocked ratings in order not to ask a user
+ * to unblock whenever a content rating is changed.
+ * Therefore an unblocked rating can be valid for a channel, a program,
+ * or certain amount of time depending on the implementation.
+ * </p>
+ *
+ * @param unblockedRating An unblocked content rating
+ */
+ public abstract void onContentUnblocked(TvContentRating unblockedRating);
+
+ /**
* Selects a given track.
* <p>
* If it is called multiple times on the same type of track (ie. Video, Audio, Text), the
@@ -526,6 +626,17 @@
}
/**
+ * Calls {@link #onSurfaceChanged}.
+ */
+ void dispatchSurfaceChanged(int format, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ }
+ onSurfaceChanged(format, width, height);
+ }
+
+ /**
* Calls {@link #onSetStreamVolume}.
*/
void setVolume(float volume) {
@@ -564,6 +675,14 @@
}
/**
+ * Calls {@link #onContentUnblocked}.
+ */
+ void unblockContent(String unblockedRating) {
+ onContentUnblocked(TvContentRating.unflattenFromString(unblockedRating));
+ // TODO: Handle failure.
+ }
+
+ /**
* Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
* to the overlay window.
*
@@ -715,6 +834,32 @@
@SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_ADD_TV_INPUT_FROM_HARDWARE = 2;
+ private static final int DO_REMOVE_TV_INPUT_FROM_HARDWARE = 3;
+
+ private void broadcastAddTvInput(TvInputInfo inputInfo) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ mCallbacks.getBroadcastItem(i).addTvInput(inputInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while broadcasting: " + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ private void broadcastRemoveTvInput(String inputId) {
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while broadcasting: " + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
@Override
public final void handleMessage(Message msg) {
@@ -723,8 +868,9 @@
SomeArgs args = (SomeArgs) msg.obj;
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+ String inputId = (String) args.arg3;
try {
- Session sessionImpl = onCreateSession();
+ Session sessionImpl = onCreateSession(inputId);
if (sessionImpl == null) {
// Failed to create a session.
cb.onSessionCreated(null);
@@ -740,6 +886,22 @@
args.recycle();
return;
}
+ case DO_ADD_TV_INPUT_FROM_HARDWARE: {
+ TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
+ TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
+ if (inputInfo != null) {
+ broadcastAddTvInput(inputInfo);
+ }
+ return;
+ }
+ case DO_REMOVE_TV_INPUT_FROM_HARDWARE: {
+ int deviceId = msg.arg1;
+ String inputId = onHardwareRemoved(deviceId);
+ if (inputId != null) {
+ broadcastRemoveTvInput(inputId);
+ }
+ return;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
diff --git a/media/java/android/media/tv/TvParentalControlManager.java b/media/java/android/media/tv/TvParentalControlManager.java
new file mode 100644
index 0000000..3741991
--- /dev/null
+++ b/media/java/android/media/tv/TvParentalControlManager.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Contains methods for accessing and monitoring the user's parental control settings.
+ * <p>
+ * To obtain a handle to the TV parental control manager, do the following:
+ * <p>
+ * <code>
+ * <pre>TvParentalControlManager tvParentalControlManager =
+ * (TvParentalControlManager) context.getSystemService(Context.TV_PARENTAL_CONTROL_SERVICE);
+ * </pre>
+ * </code>
+ */
+public final class TvParentalControlManager {
+ /** Default parental control enabled value. */
+ private static final int DEFAULT_ENABLED = 0;
+
+ private final Handler mHandler = new Handler();
+
+ private final ContentResolver mContentResolver;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final List<ParentalControlCallbackRecord> mParentalControlCallbackRecordList =
+ new LinkedList<ParentalControlCallbackRecord>();
+
+ @GuardedBy("mLock")
+ private final List<TvContentRating> mBlockedRatings = new ArrayList<TvContentRating>();
+
+ @GuardedBy("mLock")
+ private String mBlockedRatingsString;
+
+ /**
+ * Creates a new parental control manager for the specified context.
+ *
+ * @hide
+ */
+ public TvParentalControlManager(Context context) {
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Returns the user's parental control enabled state.
+ *
+ * @return {@code true} if the user enabled the parental control, {@code false} otherwise.
+ */
+ public final boolean isEnabled() {
+ return Settings.Secure.getInt(mContentResolver, Settings.Secure.TV_PARENTAL_CONTROL_ENABLED,
+ DEFAULT_ENABLED) == 1;
+ }
+
+ /**
+ * Checks whether a given TV content rating is blocked by the user.
+ *
+ * @param rating The TV content rating to check.
+ * @return {@code true} if blocked, {@code false} if not blocked or parental control is
+ * disabled.
+ */
+ public final boolean isRatingBlocked(TvContentRating rating) {
+ if (!isEnabled()) {
+ // Parental control is disabled. Enjoy watching good stuff.
+ return false;
+ }
+
+ // Update the blocked ratings only when they change.
+ final String blockedRatingsString = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS);
+ synchronized (mLock) {
+ if (!TextUtils.equals(blockedRatingsString, mBlockedRatingsString)) {
+ mBlockedRatingsString = blockedRatingsString;
+ updateBlockedRatingsLocked();
+ }
+ for (TvContentRating blockedRating : mBlockedRatings) {
+ if (rating.contains(blockedRating)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void updateBlockedRatingsLocked() {
+ mBlockedRatings.clear();
+ if (TextUtils.isEmpty(mBlockedRatingsString)) {
+ return;
+ }
+ for (String blockedRatingString : mBlockedRatingsString.split("\\s*,\\s*")) {
+ mBlockedRatings.add(TvContentRating.unflattenFromString(blockedRatingString));
+ }
+ }
+
+ /**
+ * Adds a callback for monitoring the changes in the user's parental control settings.
+ *
+ * @param callback The callback to add.
+ * @param handler a {@link Handler} that the settings change will be delivered to.
+ */
+ public void addParentalControlCallback(ParentalControlCallback callback,
+ Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mLock) {
+ if (mParentalControlCallbackRecordList.isEmpty()) {
+ registerObserver(Settings.Secure.TV_PARENTAL_CONTROL_ENABLED);
+ registerObserver(Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS);
+ }
+ mParentalControlCallbackRecordList.add(
+ new ParentalControlCallbackRecord(callback, handler));
+ }
+ }
+
+ private void registerObserver(String key) {
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(key), false,
+ mContentObserver);
+ }
+
+ /**
+ * Removes a callback previously added using {@link #addParentalControlCallback}.
+ *
+ * @param callback The callback to remove.
+ */
+ public void removeParentalControlCallback(ParentalControlCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ synchronized (mLock) {
+ for (Iterator<ParentalControlCallbackRecord> it =
+ mParentalControlCallbackRecordList.iterator(); it.hasNext();) {
+ ParentalControlCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ private void notifyEnabledChanged() {
+ final boolean enabled = isEnabled();
+ synchronized (mLock) {
+ for (ParentalControlCallbackRecord record : mParentalControlCallbackRecordList) {
+ record.postEnabledChanged(enabled);
+ }
+ }
+ }
+
+ private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String uriPath = uri.getPath();
+ final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
+ if (Settings.Secure.TV_PARENTAL_CONTROL_ENABLED.equals(name)) {
+ notifyEnabledChanged();
+ } else if (Settings.Secure.TV_PARENTAL_CONTROL_BLOCKED_RATINGS.equals(name)) {
+ // We only need a single callback when multiple ratings change in rapid
+ // succession.
+ mHandler.removeCallbacks(mBlockedRatingsChangedRunnable);
+ mHandler.post(mBlockedRatingsChangedRunnable);
+ }
+ }
+ };
+
+ /**
+ * Runnable posted when user blocked ratings change. This is used to prevent unnecessary change
+ * notifications when multiple ratings change in rapid succession.
+ */
+ private final Runnable mBlockedRatingsChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ for (ParentalControlCallbackRecord record : mParentalControlCallbackRecordList) {
+ record.postBlockedRatingsChanged();
+ }
+ }
+ }
+ };
+
+ /**
+ * Callback for changes in parental control settings, including enabled state.
+ */
+ public static abstract class ParentalControlCallback {
+ /**
+ * Called when the parental control enabled state changes.
+ *
+ * @param enabled the user's parental control enabled state
+ */
+ public void onEnabledChanged(boolean enabled) {}
+
+ /**
+ * Called when the user blocked ratings change.
+ * <p>
+ * When this is invoked, one should immediately call
+ * {@link TvParentalControlManager#isRatingBlocked} to reevaluate the current content since
+ * the user might have changed her mind and blocked the rating for the content.
+ *
+ * @see TvParentalControlManager#isRatingBlocked
+ */
+ public void onBlockedRatingsChanged() {}
+ }
+
+ private static final class ParentalControlCallbackRecord {
+ private final ParentalControlCallback mCallback;
+ private final Handler mHandler;
+
+ public ParentalControlCallbackRecord(ParentalControlCallback callback, Handler handler) {
+ mCallback = callback;
+ mHandler = handler;
+ }
+
+ public ParentalControlCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postEnabledChanged(final boolean enabled) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onEnabledChanged(enabled);
+ }
+ });
+ }
+
+ public void postBlockedRatingsChanged() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onBlockedRatingsChanged();
+ }
+ });
+ }
+ }
+}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 4ac1ba4..9fbb3b2 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -62,7 +62,7 @@
private static final int VIDEO_SIZE_VALUE_UNKNOWN = 0;
private final Handler mHandler = new Handler();
- private TvInputManager.Session mSession;
+ private Session mSession;
private final SurfaceView mSurfaceView;
private Surface mSurface;
private boolean mOverlayViewCreated;
@@ -75,17 +75,21 @@
private float mStreamVolume;
private int mVideoWidth = VIDEO_SIZE_VALUE_UNKNOWN;
private int mVideoHeight = VIDEO_SIZE_VALUE_UNKNOWN;
+ private boolean mSurfaceChanged;
+ private int mSurfaceFormat;
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+ ", height=" + height + ")");
- if (holder.getSurface() == mSurface) {
- return;
- }
- mSurface = holder.getSurface();
- setSessionSurface(mSurface);
+ mSurfaceFormat = format;
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+ mSurfaceChanged = true;
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
@Override
@@ -97,6 +101,7 @@
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurface = null;
+ mSurfaceChanged = false;
setSessionSurface(null);
}
};
@@ -215,6 +220,22 @@
}
/**
+ * Unblock TV content.
+ * <p>
+ * This notifies TV input that blocked content is now OK to play.
+ * </p>
+ *
+ * @param unblockedRating a TvContentRating to unblock.
+ * @see TvInputService.Session#dispatchContentBlocked(TvContentRating)
+ * @hide
+ */
+ public void unblockContent(TvContentRating unblockedRating) {
+ if (mSession != null) {
+ mSession.unblockContent(unblockedRating);
+ }
+ }
+
+ /**
* Enables or disables the caption in this TvView.
* <p>
* Note that this method does not take any effect unless the current TvView is tuned.
@@ -423,6 +444,13 @@
mSession.setSurface(surface);
}
+ private void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.dispatchSurfaceChanged(format, width, height);
+ }
+
private void createSessionOverlayView() {
if (mSession == null || !isAttachedToWindow()
|| mOverlayViewCreated) {
@@ -548,6 +576,15 @@
}
/**
+ * This is called when the current program content is blocked by parental controls.
+ *
+ * @param inputId The ID of the TV input bound to this view.
+ * @param rating The content rating of the blocked program.
+ */
+ public void onContentBlocked(String inputId, TvContentRating rating) {
+ }
+
+ /**
* This is invoked when a custom event from the bound TV input is sent to this view.
*
* @param eventType The type of the event.
@@ -600,6 +637,9 @@
// setSessionSurface will be called in surfaceCreated.
if (mSurface != null) {
setSessionSurface(mSurface);
+ if (mSurfaceChanged) {
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+ }
}
createSessionOverlayView();
mSession.tune(mChannelUri);
@@ -615,9 +655,10 @@
@Override
public void onSessionReleased(Session session) {
- if (this == mSessionCallback) {
- mSessionCallback = null;
+ if (this != mSessionCallback) {
+ return;
}
+ mSessionCallback = null;
mSession = null;
if (mListener != null) {
mListener.onError(mInputId, ERROR_TV_INPUT_DISCONNECTED);
@@ -626,6 +667,9 @@
@Override
public void onChannelRetuned(Session session, Uri channelUri) {
+ if (this != mSessionCallback) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")");
}
@@ -648,7 +692,11 @@
}
}
+ @Override
public void onVideoAvailable(Session session) {
+ if (this != mSessionCallback) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onVideoAvailable()");
}
@@ -657,7 +705,11 @@
}
}
+ @Override
public void onVideoUnavailable(Session session, int reason) {
+ if (this != mSessionCallback) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "onVideoUnavailable(" + reason + ")");
}
@@ -667,11 +719,22 @@
}
@Override
- public void onSessionEvent(TvInputManager.Session session, String eventType,
- Bundle eventArgs) {
+ public void onContentBlocked(Session session, TvContentRating rating) {
+ if (DEBUG) {
+ Log.d(TAG, "onContentBlocked()");
+ }
+ if (mListener != null) {
+ mListener.onContentBlocked(mInputId, rating);
+ }
+ }
+
+ public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
if (this != mSessionCallback) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "onSessionEvent(" + eventType + ")");
+ }
if (mListener != null) {
mListener.onEvent(mInputId, eventType, eventArgs);
}
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 2c1db02..09525b2 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -97,6 +97,16 @@
}
@Override
+ public void onBackPressed() {
+ WebView myWebView = (WebView) findViewById(R.id.webview);
+ if (myWebView.canGoBack()) {
+ myWebView.goBack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_use_network) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index ce47b3e..4a23ec4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -311,7 +311,7 @@
}
}
- private final class PreloadController implements RecyclerView.OnScrollListener {
+ private final class PreloadController extends RecyclerView.OnScrollListener {
private final RecyclerView mRecyclerView;
private int mOldScrollState;
@@ -322,7 +322,7 @@
}
@Override
- public void onScrollStateChanged(int state) {
+ public void onScrollStateChanged(RecyclerView recyclerView, int state) {
switch (mOldScrollState) {
case RecyclerView.SCROLL_STATE_SETTLING: {
if (state == RecyclerView.SCROLL_STATE_IDLE
@@ -341,11 +341,6 @@
mOldScrollState = state;
}
- @Override
- public void onScrolled(int dx, int dy) {
- /* do nothing */
- }
-
public void startPreloadContent() {
PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5752646..a038899 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -109,6 +109,9 @@
<uses-permission android:name="android.permission.CAMERA" />
+ <!-- Screen Capturing -->
+ <uses-permission android:name="android.permission.CREATE_MEDIA_PROJECTION" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
@@ -245,6 +248,15 @@
android:taskAffinity="com.android.systemui.net"
android:excludeFromRecents="true" />
+ <!-- started from MediaProjectionManager -->
+ <activity
+ android:name=".media.MediaProjectionPermissionActivity"
+ android:exported="true"
+ android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:launchMode="singleTop"
+ android:excludeFromRecents="true" />
+
<!-- platform logo easter egg activity -->
<activity
android:name=".DessertCase"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 6d101d7..c9e1618 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -9,4 +9,4 @@
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.recents.*
\ No newline at end of file
+-keep class com.android.systemui.recents.*
diff --git a/packages/SystemUI/res/drawable/zen_alarm_hard_background.xml b/packages/SystemUI/res/drawable/zen_alarm_hard_background.xml
new file mode 100644
index 0000000..3620212
--- /dev/null
+++ b/packages/SystemUI/res/drawable/zen_alarm_hard_background.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/system_warning_color" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="0dp"
+ android:bottomLeftRadius="@dimen/notification_material_rounded_rect_radius"
+ android:bottomRightRadius="@dimen/notification_material_rounded_rect_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/zen_alarm_soft_background.xml b/packages/SystemUI/res/drawable/zen_alarm_soft_background.xml
new file mode 100644
index 0000000..a99c79c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/zen_alarm_soft_background.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/zen_alarm_soft_warning_background" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="0dp"
+ android:bottomLeftRadius="@dimen/notification_material_rounded_rect_radius"
+ android:bottomRightRadius="@dimen/notification_material_rounded_rect_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index 691a80e..d17390e 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -32,8 +32,8 @@
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.UserName"
/>
<com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/picture"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="@dimen/max_avatar_size"
+ android:layout_height="@dimen/max_avatar_size"
android:contentDescription="@null"
sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
sysui:activeFrameColor="@color/current_user_border_color" />
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 29d92e5..526aa5e 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -29,8 +29,8 @@
<com.android.systemui.statusbar.phone.UserAvatarView
android:id="@+id/user_picture"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="@dimen/max_avatar_size"
+ android:layout_height="@dimen/max_avatar_size"
android:layout_marginBottom="12dp"
systemui:frameWidth="2dp"
systemui:activeFrameColor="@color/current_user_border_color"/>
diff --git a/packages/SystemUI/res/layout/remember_permission_checkbox.xml b/packages/SystemUI/res/layout/remember_permission_checkbox.xml
new file mode 100644
index 0000000..a21acb3
--- /dev/null
+++ b/packages/SystemUI/res/layout/remember_permission_checkbox.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<!-- Check box that is displayed in the activity resolver UI for the user
+ to make their selection the preferred activity. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="8dp">
+
+ <CheckBox
+ android:id="@+id/remember"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:clickable="true"
+ android:text="@string/media_projection_remember_text" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/segmented_button.xml b/packages/SystemUI/res/layout/segmented_button.xml
index ef78220..538e434 100644
--- a/packages/SystemUI/res/layout/segmented_button.xml
+++ b/packages/SystemUI/res/layout/segmented_button.xml
@@ -23,4 +23,4 @@
android:background="@drawable/segmented_button"
android:textAppearance="@style/TextAppearance.QS.SegmentedButton"
android:minHeight="0dp"
- android:padding="12dp" />
+ android:padding="10dp" />
diff --git a/packages/SystemUI/res/layout/volume_panel.xml b/packages/SystemUI/res/layout/volume_panel.xml
index b377a06..6416308 100644
--- a/packages/SystemUI/res/layout/volume_panel.xml
+++ b/packages/SystemUI/res/layout/volume_panel.xml
@@ -18,6 +18,7 @@
android:id="@+id/visible_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="8dp"
android:orientation="vertical" >
<FrameLayout
diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml
index d91e2ab..b1dd237 100644
--- a/packages/SystemUI/res/layout/volume_panel_item.xml
+++ b/packages/SystemUI/res/layout/volume_panel_item.xml
@@ -26,10 +26,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
- android:paddingBottom="12dip"
+ android:paddingBottom="10dip"
android:paddingLeft="16dip"
android:paddingRight="16dip"
- android:paddingTop="12dip" />
+ android:paddingTop="10dip" />
<SeekBar
android:id="@+id/seekbar"
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 8b1c2b7..b0e43fc 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/qs_panel_padding"
android:layout_marginRight="@dimen/qs_panel_padding"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="12dp"
android:background="@drawable/segmented_buttons"
android:clipChildren="false" />
@@ -81,8 +81,10 @@
<TextView
android:id="@+id/zen_alarm_warning"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/qs_panel_padding"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" />
+ android:layout_height="48dp"
+ android:layout_marginTop="@dimen/qs_panel_padding"
+ android:paddingLeft="@dimen/qs_panel_padding"
+ android:paddingRight="@dimen/qs_panel_padding"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
</com.android.systemui.volume.ZenModePanel>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a1064fd..f23486e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -43,6 +43,8 @@
<color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
<color name="qs_subhead">#66FFFFFF</color><!-- 40% white -->
<color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
+ <color name="zen_alarm_soft_warning_text">#99FFFFFF</color><!-- 60% white -->
+ <color name="zen_alarm_soft_warning_background">#15FFFFFF</color><!-- 8% white -->
<color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
<color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
<color name="data_usage_graph_warning">#FFFFFFFF</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e86aa0a..bb18120 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -361,4 +361,8 @@
<!-- Battery level padding end when in expanded QS (but not on Keyguard) -->
<dimen name="battery_level_padding_end">4dp</dimen>
+
+ <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
+ quick settings header -->
+ <dimen name="max_avatar_size">48dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f8c9d4c..46057af 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -703,5 +703,15 @@
<!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
<string name="notification_hidden_text">Contents hidden</string>
+
<string name="guest_exit_guest">Exit guest</string>
+
+ <!-- Media projection permission dialog warning text. [CHAR LIMIT=NONE] -->
+ <string name="media_projection_dialog_text"><xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g> will start capturing everything that\'s displayed on your screen.</string>
+
+ <!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
+ <string name="media_projection_remember_text">Don\'t show again</string>
+
+ <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
+ <string name="media_projection_action_text">Start now</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 5cc987a..419ecc7 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -267,8 +267,8 @@
<!-- Window animations used for volume panel. -->
<style name="VolumePanelAnimation">
- <item name="android:windowEnterAnimation">@*android:anim/dock_top_enter</item>
- <item name="android:windowExitAnimation">@*android:anim/dock_top_exit</item>
+ <item name="android:windowEnterAnimation">@*android:anim/popup_enter_material</item>
+ <item name="android:windowExitAnimation">@*android:anim/popup_exit_material</item>
</style>
<style name="TextAppearance.StatusBar.Material.EventContent.Parenthetical"
diff --git a/packages/SystemUI/src/com/android/systemui/BitmapHelper.java b/packages/SystemUI/src/com/android/systemui/BitmapHelper.java
new file mode 100644
index 0000000..008b422
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/BitmapHelper.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+
+public class BitmapHelper {
+ /**
+ * Generate a new bitmap (width x height pixels, ARGB_8888) with the input bitmap scaled
+ * to fit and clipped to an inscribed circle.
+ * @param input Bitmap to resize and clip
+ * @param width Width of output bitmap (and diameter of circle)
+ * @param height Height of output bitmap
+ * @return A shiny new bitmap for you to use
+ */
+ public static Bitmap createCircularClip(Bitmap input, int width, int height) {
+ final int inWidth = input.getWidth();
+ final int inHeight = input.getHeight();
+ final Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(output);
+ final Paint paint = new Paint();
+ paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+ paint.setAntiAlias(true);
+ final RectF srcRect = new RectF(0, 0, inWidth, inHeight);
+ final RectF dstRect = new RectF(0, 0, width, height);
+ final Matrix m = new Matrix();
+ m.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.CENTER);
+ canvas.setMatrix(m);
+ canvas.drawCircle(inWidth / 2, inHeight / 2, inWidth / 2, paint);
+ return output;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
new file mode 100644
index 0000000..b441eaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -0,0 +1,138 @@
+/*
+ * 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.media;
+
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.media.projection.MediaProjectionManager;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.systemui.R;
+
+public class MediaProjectionPermissionActivity extends AlertActivity
+ implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
+ private static final String TAG = "MediaProjectionPermissionActivity";
+
+ private boolean mPermanentGrant;
+ private String mPackageName;
+ private int mUid;
+ private IMediaProjectionManager mService;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Intent intent = getIntent();
+ mPackageName = getCallingPackage();
+ IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+ mService = IMediaProjectionManager.Stub.asInterface(b);
+
+ if (mPackageName == null) {
+ finish();
+ return;
+ }
+
+ PackageManager packageManager = getPackageManager();
+ ApplicationInfo aInfo;
+ try {
+ aInfo = packageManager.getApplicationInfo(mPackageName, 0);
+ mUid = aInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "unable to look up package name", e);
+ finish();
+ return;
+ }
+
+ try {
+ if (mService.hasProjectionPermission(mUid, mPackageName)) {
+ setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName,
+ false /*permanentGrant*/));
+ finish();
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error checking projection permissions", e);
+ finish();
+ return;
+ }
+
+ String appName = aInfo.loadLabel(packageManager).toString();
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mIcon = aInfo.loadIcon(packageManager);
+ ap.mMessage = getString(R.string.media_projection_dialog_text, appName);
+ ap.mPositiveButtonText = getString(R.string.media_projection_action_text);
+ ap.mNegativeButtonText = getString(android.R.string.cancel);
+ ap.mPositiveButtonListener = this;
+ ap.mNegativeButtonListener = this;
+
+ // add "always use" checkbox
+ LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ ap.mView = inflater.inflate(R.layout.remember_permission_checkbox, null);
+ CheckBox rememberPermissionCheckbox =
+ (CheckBox)ap.mView.findViewById(R.id.remember);
+ rememberPermissionCheckbox.setOnCheckedChangeListener(this);
+
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ setResult(RESULT_OK, getMediaProjectionIntent(
+ mUid, mPackageName, mPermanentGrant));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error granting projection permission", e);
+ setResult(RESULT_CANCELED);
+ } finally {
+ finish();
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mPermanentGrant = isChecked;
+ }
+
+ private Intent getMediaProjectionIntent(int uid, String packageName, boolean permanentGrant)
+ throws RemoteException {
+ IMediaProjection projection = mService.createProjection(uid, packageName,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, permanentGrant);
+ Intent intent = new Intent();
+ intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
+ return intent;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index bd4ea90..068cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -378,6 +378,8 @@
}
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
+ // Re-enable touch events from this task view
+ tv.setTouchEnabled(true);
// Remove the task view from the stack
mSv.onTaskViewDismissed(tv);
}
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 72c12d6..e975045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1240,22 +1240,25 @@
if (!notificationIsForCurrentProfiles(ent.notification)) continue;
+ final boolean hideSensitive = shouldHideSensitiveContents(ent.notification.getUserId());
final int vis = ent.notification.getNotification().visibility;
- if (vis != Notification.VISIBILITY_SECRET) {
- // when isLockscreenPublicMode() we show the public form of VISIBILITY_PRIVATE notifications
- boolean showingPublic = isLockscreenPublicMode()
- && vis == Notification.VISIBILITY_PRIVATE
- && !userAllowsPrivateNotificationsInPublic(ent.notification.getUserId());
- ent.row.setShowingPublic(showingPublic);
- if (ent.autoRedacted && ent.legacy) {
- if (showingPublic) {
- ent.row.setShowingLegacyBackground(false);
- } else {
- ent.row.setShowingLegacyBackground(true);
- }
- }
- toShow.add(ent.row);
+
+ // when isLockscreenPublicMode() we suppress VISIBILITY_SECRET notifications
+ if (vis == Notification.VISIBILITY_SECRET && hideSensitive) {
+ continue;
}
+
+ // when isLockscreenPublicMode() we show the public form of VISIBILITY_PRIVATE notifications
+ boolean showingPublic = vis == Notification.VISIBILITY_PRIVATE && hideSensitive;
+ ent.row.setShowingPublic(showingPublic);
+ if (ent.autoRedacted && ent.legacy) {
+ if (showingPublic) {
+ ent.row.setShowingLegacyBackground(false);
+ } else {
+ ent.row.setShowingLegacyBackground(true);
+ }
+ }
+ toShow.add(ent.row);
}
ArrayList<View> toRemove = new ArrayList<View>();
@@ -1333,6 +1336,15 @@
updateNotificationIcons();
}
+ /**
+ * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive"
+ * notification data. If so, private notifications should show their (possibly
+ * auto-generated) publicVersion, and secret notifications should be totally invisible.
+ */
+ private boolean shouldHideSensitiveContents(int userid) {
+ return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid);
+ }
+
private void updateNotificationIcons() {
final LinearLayout.LayoutParams params
= new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight);
@@ -1353,10 +1365,8 @@
if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE)
|| showNotificationEvenIfUnprovisioned(ent.notification))) continue;
if (!notificationIsForCurrentProfiles(ent.notification)) continue;
- if (isLockscreenPublicMode()
- && ent.notification.getNotification().visibility
- == Notification.VISIBILITY_SECRET
- && !userAllowsPrivateNotificationsInPublic(ent.notification.getUserId())) {
+ if (ent.notification.getNotification().visibility == Notification.VISIBILITY_SECRET
+ && shouldHideSensitiveContents(ent.notification.getUserId())) {
// in "public" mode (atop a secure keyguard), secret notifs are totally hidden
continue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 574d536..3012b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -285,6 +285,9 @@
mDateExpanded.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
mSettingsButton.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
mQsDetailHeader.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
+ if (mStatusIcons != null) {
+ mStatusIcons.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
+ }
if (mSignalCluster != null) {
mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
}
@@ -437,15 +440,16 @@
public void attachSystemIcons(LinearLayout systemIcons) {
mSystemIconsContainer.addView(systemIcons);
mStatusIcons = systemIcons.findViewById(R.id.statusIcons);
- mStatusIcons.addOnLayoutChangeListener(mStatusIconsChanged);
mSignalCluster = systemIcons.findViewById(R.id.signal_cluster);
+ mSignalCluster.addOnLayoutChangeListener(mSignalClusterChanged);
}
public void onSystemIconsDetached() {
if (mStatusIcons != null) {
- mStatusIcons.removeOnLayoutChangeListener(mStatusIconsChanged);
+ mStatusIcons.setVisibility(View.VISIBLE);
}
if (mSignalCluster != null) {
+ mSignalCluster.removeOnLayoutChangeListener(mSignalClusterChanged);
mSignalCluster.setVisibility(View.VISIBLE);
}
mStatusIcons = null;
@@ -528,7 +532,7 @@
// here.
}
- private final OnLayoutChangeListener mStatusIconsChanged = new OnLayoutChangeListener() {
+ private final OnLayoutChangeListener mSignalClusterChanged = new OnLayoutChangeListener() {
private final Rect mClipBounds = new Rect();
@Override
@@ -536,12 +540,9 @@
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Hide the statusIcons in the header by clipping them. Can't touch visibility since
// they are mirrored to the real status bar.
- final Rect r = mSystemIconsContainer.getClipBounds();
- if (r == null || r.left != right) {
- mClipBounds.set(right, 0, mSystemIconsContainer.getWidth(),
- mSystemIconsContainer.getHeight());
- mSystemIconsContainer.setClipBounds(mClipBounds);
- }
+ mClipBounds.set(left, 0, mSystemIconsContainer.getWidth(),
+ mSystemIconsContainer.getHeight());
+ mSystemIconsContainer.setClipBounds(mClipBounds);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index c90750c..a3f3819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.BitmapHelper;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.StatusBarHeaderView;
import com.android.systemui.statusbar.phone.UserAvatarView;
@@ -176,11 +177,16 @@
} catch (RemoteException e) {
Log.e(TAG, "Couln't get current user.", e);
}
+ final int avatarSize
+ = mContext.getResources().getDimensionPixelSize(R.dimen.max_avatar_size);
for (int i = 0; i < N; i++) {
UserInfo user = users.get(i);
if (user.supportsSwitchTo()) {
boolean isCurrent = user.id == currentUser;
- result.add(new UserData(user, mUserManager.getUserIcon(user.id), isCurrent));
+ final Bitmap picture = BitmapHelper.createCircularClip(
+ mUserManager.getUserIcon(user.id),
+ avatarSize, avatarSize);
+ result.add(new UserData(user, picture, isCurrent));
}
}
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index 3ce6905..8cbe272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -28,10 +28,12 @@
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -46,6 +48,7 @@
import android.util.Pair;
import com.android.internal.view.RotationPolicy;
+import com.android.systemui.BitmapHelper;
import com.android.systemui.R;
import java.util.ArrayList;
@@ -124,17 +127,6 @@
queryForUserInformation();
}
- private Bitmap circularClip(Bitmap input) {
- Bitmap output = Bitmap.createBitmap(input.getWidth(),
- input.getHeight(), Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- final Paint paint = new Paint();
- paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
- paint.setAntiAlias(true);
- canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2, paint);
- return output;
- }
-
private void queryForUserInformation() {
Context currentUserContext;
UserInfo userInfo;
@@ -151,6 +143,8 @@
}
final int userId = userInfo.id;
final String userName = userInfo.name;
+ final int avatarSize
+ = mContext.getResources().getDimensionPixelSize(R.dimen.max_avatar_size);
final Context context = currentUserContext;
mUserInfoTask = new AsyncTask<Void, Void, Pair<String, Drawable>>() {
@@ -164,7 +158,8 @@
Drawable avatar = null;
Bitmap rawAvatar = um.getUserIcon(userId);
if (rawAvatar != null) {
- avatar = new BitmapDrawable(mContext.getResources(), circularClip(rawAvatar));
+ avatar = new BitmapDrawable(mContext.getResources(),
+ BitmapHelper.createCircularClip(rawAvatar, avatarSize, avatarSize));
} else {
avatar = mContext.getResources().getDrawable(R.drawable.ic_account_circle);
mUseDefaultAvatar = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 7cc8ed5..2134042 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import com.android.systemui.BitmapHelper;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.tiles.UserDetailView;
@@ -104,6 +105,8 @@
ArrayList<UserRecord> records = new ArrayList<>(infos.size());
int currentId = ActivityManager.getCurrentUser();
UserRecord guestRecord = null;
+ int avatarSize = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.max_avatar_size);
for (UserInfo info : infos) {
boolean isCurrent = currentId == info.id;
@@ -115,6 +118,10 @@
if (picture == null) {
picture = mUserManager.getUserIcon(info.id);
}
+ if (picture != null) {
+ picture = BitmapHelper.createCircularClip(
+ picture, avatarSize, avatarSize);
+ }
records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index e38c2ac..6d6fd60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -132,6 +132,9 @@
final Intent intent = ZenModePanel.ZEN_SETTINGS;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+
+ // dismiss shade if showing
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
});
mDialogPanel.postDismiss(mDismissDelay);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 33cf3b6..7473c41 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -80,8 +81,8 @@
private final H mHandler = new H();
private final Favorites mFavorites;
private final Interpolator mFastOutSlowInInterpolator;
- private final int mTextColor;
- private final int mAccentColor;
+ private final int mHardWarningColor;
+ private final int mSoftWarningColor;
private char mLogTag = '?';
private String mTag;
@@ -94,6 +95,7 @@
private LinearLayout mZenConditions;
private TextView mAlarmWarning;
+ private int mBottomPadding;
private Callback mCallback;
private ZenModeController mController;
private boolean mRequestingConditions;
@@ -112,8 +114,9 @@
mInflater = LayoutInflater.from(mContext.getApplicationContext());
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
- mTextColor = mContext.getResources().getColor(R.color.qs_text);
- mAccentColor = mContext.getResources().getColor(R.color.system_accent_color);
+ final Resources res = mContext.getResources();
+ mHardWarningColor = res.getColor(R.color.qs_text);
+ mSoftWarningColor = res.getColor(R.color.zen_alarm_soft_warning_text);
updateTag();
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
@@ -126,6 +129,8 @@
protected void onFinishInflate() {
super.onFinishInflate();
+ mBottomPadding = getPaddingBottom();
+
mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
mZenButtons.addButton(R.string.interruption_level_priority,
@@ -285,6 +290,7 @@
final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
final boolean foreverSelected = mExitConditionId == null;
final boolean hasNextAlarm = mNextAlarm != 0;
+ final boolean showAlarmWarning = zenNone && mExpanded && hasNextAlarm;
mZenSubhead.setVisibility(!zenOff && (mExpanded || !foreverSelected) ? VISIBLE : GONE);
mZenSubheadExpanded.setVisibility(mExpanded ? VISIBLE : GONE);
@@ -292,8 +298,8 @@
mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE);
mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE);
mAlarmWarning.setVisibility(zenNone && mExpanded && hasNextAlarm ? VISIBLE : GONE);
-
- if (zenNone && mExpanded && hasNextAlarm) {
+ setPadding(0, 0, 0, showAlarmWarning ? 0 : mBottomPadding);
+ if (showAlarmWarning) {
final long exitTime = ZenModeConfig.tryParseCountdownConditionId(mExitConditionId);
final long now = System.currentTimeMillis();
final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay;
@@ -304,12 +310,14 @@
final boolean isWarning = exitTime > 0 && mNextAlarm > now && mNextAlarm < exitTime;
if (isWarning) {
mAlarmWarning.setText(mContext.getString(R.string.zen_alarm_warning, alarm));
- mAlarmWarning.setTextColor(mAccentColor);
+ mAlarmWarning.setTextColor(mHardWarningColor);
+ mAlarmWarning.setBackgroundResource(R.drawable.zen_alarm_hard_background);
} else {
mAlarmWarning.setText(mContext.getString(alarmToday
? R.string.zen_alarm_information_time
: R.string.zen_alarm_information_day_time, alarm));
- mAlarmWarning.setTextColor(mTextColor);
+ mAlarmWarning.setTextColor(mSoftWarningColor);
+ mAlarmWarning.setBackgroundResource(R.drawable.zen_alarm_soft_background);
}
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index da0bc30..1e61236 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -71,6 +71,7 @@
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.Gravity;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
@@ -3182,9 +3183,17 @@
if (a.getBoolean(com.android.internal.R.styleable.Window_windowContentTransitions, false)) {
requestFeature(FEATURE_CONTENT_TRANSITIONS);
}
- if (a.hasValue(com.android.internal.R.styleable.Window_windowOutsetBottom)) {
- if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
- a.getValue(com.android.internal.R.styleable.Window_windowOutsetBottom, mOutsetBottom);
+
+ final WindowManager windowService = (WindowManager) getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+ if (windowService != null) {
+ final Display display = windowService.getDefaultDisplay();
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY &&
+ a.hasValue(com.android.internal.R.styleable.Window_windowOutsetBottom)) {
+ if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Window_windowOutsetBottom,
+ mOutsetBottom);
+ }
}
final Context context = getContext();
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index f54f798..17a5263 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3539,6 +3539,11 @@
// In all cases we need to give the transport its finish callback
int finishResult = transport.finishBackup();
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Done trying to send backup data: result="
+ + result + " finishResult=" + finishResult);
+ }
+
// If we were otherwise in a good state, now interpret the final
// result based on what finishBackup() returned. If we're in a
// failure case already, preserve that result and ignore whatever
@@ -3561,7 +3566,7 @@
// do nothing, clean up, and continue looping
} else if (result != BackupTransport.TRANSPORT_OK) {
if (DEBUG) {
- Slog.i(TAG, "Transport failed; aborting backup");
+ Slog.i(TAG, "Transport failed; aborting backup: " + result);
return;
}
}
@@ -4083,6 +4088,9 @@
// okay, if the remote end failed at any point, deal with
// it by ignoring the rest of the restore on it
if (!agentSuccess) {
+ if (DEBUG) {
+ Slog.i(TAG, "Agent failure; ending restore");
+ }
mBackupHandler.removeMessages(MSG_TIMEOUT);
tearDownPipes();
tearDownAgent(mTargetApp);
@@ -4124,6 +4132,11 @@
// If we got here we're either running smoothly or we've finished
if (info == null) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "No [more] data for this package; tearing down");
+ }
+ tearDownPipes();
+ tearDownAgent(mTargetApp);
setRunning(false);
}
return (info != null);
@@ -7037,6 +7050,7 @@
// handling will deal properly with that.
Slog.e(TAG, "Error " + result + " streaming restore for "
+ mCurrentPackage.packageName);
+ status = result;
}
}
if (MORE_DEBUG) Slog.v(TAG, "Done copying to engine, falling through");
@@ -7063,6 +7077,10 @@
// Don't proceed until the engine has torn down the agent etc
eThread.waitForResult();
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "engine thread finished; proceeding");
+ }
+
// Now we're really done with this one too
IoUtils.closeQuietly(mEnginePipes[0]);
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 1eded6f..f71a18a 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.server;
import android.Manifest;
@@ -8,8 +24,9 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.service.persistentdata.IPersistentDataBlockService;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.R;
+import libcore.io.IoUtils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -23,7 +40,8 @@
/**
* Service for reading and writing blocks to a persistent partition.
- * This data will live across factory resets.
+ * This data will live across factory resets not initiated via the Settings UI.
+ * When a device is factory reset through Settings this data is wiped.
*
* Allows writing one block at a time. Namely, each time
* {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
@@ -40,19 +58,29 @@
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final int HEADER_SIZE = 8;
- private static final int BLOCK_ID = 0x1990;
+ // Magic number to mark block device as adhering to the format consumed by this service
+ private static final int PARTITION_TYPE_MARKER = 0x1990;
+ // Limit to 100k as blocks larger than this might cause strain on Binder.
+ // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
+ private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
private final Context mContext;
private final String mDataBlockFile;
- private long mBlockDeviceSize;
-
private final int mAllowedUid;
+ private final Object mLock = new Object();
+ /*
+ * Separate lock for OEM unlock related operations as they can happen in parallel with regular
+ * block operations.
+ */
+ private final Object mOemLock = new Object();
+
+ private long mBlockDeviceSize;
public PersistentDataBlockService(Context context) {
super(context);
mContext = context;
mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
- mBlockDeviceSize = 0; // Load lazily
+ mBlockDeviceSize = -1; // Load lazily
String allowedPackage = context.getResources()
.getString(R.string.config_persistentDataPackageName);
PackageManager pm = mContext.getPackageManager();
@@ -62,7 +90,7 @@
Binder.getCallingUserHandle().getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
// not expected
- Log.e(TAG, "not able to find package " + allowedPackage, e);
+ Slog.e(TAG, "not able to find package " + allowedPackage, e);
}
mAllowedUid = allowedUid;
@@ -85,10 +113,10 @@
}
}
- private int getTotalDataSize(DataInputStream inputStream) throws IOException {
+ private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
int totalDataSize;
int blockId = inputStream.readInt();
- if (blockId == BLOCK_ID) {
+ if (blockId == PARTITION_TYPE_MARKER) {
totalDataSize = inputStream.readInt();
} else {
totalDataSize = 0;
@@ -96,17 +124,18 @@
return totalDataSize;
}
- private long maybeReadBlockDeviceSize() {
- synchronized (this) {
- if (mBlockDeviceSize == 0) {
- mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
+ private long getBlockDeviceSize() {
+ synchronized (mLock) {
+ if (mBlockDeviceSize == -1) {
+ mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
}
}
return mBlockDeviceSize;
}
- private native long getBlockDeviceSize(String path);
+ private native long nativeGetBlockDeviceSize(String path);
+ private native int nativeWipe(String path);
private final IBinder mService = new IPersistentDataBlockService.Stub() {
@Override
@@ -114,62 +143,93 @@
enforceUid(Binder.getCallingUid());
// Need to ensure we don't write over the last byte
- if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
- return -1;
+ long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ if (data.length > maxBlockSize) {
+ // partition is ~500k so shouldn't be a problem to downcast
+ return (int) -maxBlockSize;
}
DataOutputStream outputStream;
try {
outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available?", e);
+ Slog.e(TAG, "partition not available?", e);
return -1;
}
ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
- headerAndData.putInt(BLOCK_ID);
+ headerAndData.putInt(PARTITION_TYPE_MARKER);
headerAndData.putInt(data.length);
headerAndData.put(data);
try {
- outputStream.write(headerAndData.array());
- return data.length;
+ synchronized (mLock) {
+ outputStream.write(headerAndData.array());
+ return data.length;
+ }
} catch (IOException e) {
- Log.e(TAG, "failed writing to the persistent data block", e);
+ Slog.e(TAG, "failed writing to the persistent data block", e);
return -1;
} finally {
try {
outputStream.close();
} catch (IOException e) {
- Log.e(TAG, "failed closing output stream", e);
+ Slog.e(TAG, "failed closing output stream", e);
}
}
}
@Override
- public int read(byte[] data) {
+ public byte[] read() {
enforceUid(Binder.getCallingUid());
DataInputStream inputStream;
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available?", e);
- return -1;
+ Slog.e(TAG, "partition not available?", e);
+ return null;
}
try {
- int totalDataSize = getTotalDataSize(inputStream);
- return inputStream.read(data, 0,
- (data.length > totalDataSize) ? totalDataSize : data.length);
+ synchronized (mLock) {
+ int totalDataSize = getTotalDataSizeLocked(inputStream);
+
+ if (totalDataSize == 0) {
+ return new byte[0];
+ }
+
+ byte[] data = new byte[totalDataSize];
+ int read = inputStream.read(data, 0, totalDataSize);
+ if (read < totalDataSize) {
+ // something went wrong, not returning potentially corrupt data
+ Slog.e(TAG, "failed to read entire data block. bytes read: " +
+ read + "/" + totalDataSize);
+ return null;
+ }
+ return data;
+ }
} catch (IOException e) {
- Log.e(TAG, "failed to read data", e);
- return -1;
+ Slog.e(TAG, "failed to read data", e);
+ return null;
} finally {
try {
inputStream.close();
} catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
+ Slog.e(TAG, "failed to close OutputStream");
+ }
+ }
+ }
+
+ @Override
+ public void wipe() {
+ enforceOemUnlockPermission();
+
+ synchronized (mLock) {
+ int ret = nativeWipe(mDataBlockFile);
+
+ if (ret < 0) {
+ Slog.e(TAG, "failed to wipe persistent partition");
}
}
}
@@ -181,28 +241,26 @@
try {
outputStream = new FileOutputStream(new File(mDataBlockFile));
} catch (FileNotFoundException e) {
- Log.e(TAG, "parition not available", e);
+ Slog.e(TAG, "parition not available", e);
return;
}
try {
FileChannel channel = outputStream.getChannel();
- channel.position(maybeReadBlockDeviceSize() - 1);
+ channel.position(getBlockDeviceSize() - 1);
ByteBuffer data = ByteBuffer.allocate(1);
data.put(enabled ? (byte) 1 : (byte) 0);
data.flip();
- channel.write(data);
- } catch (IOException e) {
- Log.e(TAG, "unable to access persistent partition", e);
- } finally {
- try {
- outputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
+ synchronized (mOemLock) {
+ channel.write(data);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ } finally {
+ IoUtils.closeQuietly(outputStream);
}
}
@@ -213,22 +271,20 @@
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available");
+ Slog.e(TAG, "partition not available");
return false;
}
try {
- inputStream.skip(maybeReadBlockDeviceSize() - 1);
- return inputStream.readByte() != 0;
+ inputStream.skip(getBlockDeviceSize() - 1);
+ synchronized (mOemLock) {
+ return inputStream.readByte() != 0;
+ }
} catch (IOException e) {
- Log.e(TAG, "unable to access persistent partition", e);
+ Slog.e(TAG, "unable to access persistent partition", e);
return false;
} finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
- }
+ IoUtils.closeQuietly(inputStream);
}
}
@@ -240,22 +296,27 @@
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available");
+ Slog.e(TAG, "partition not available");
return 0;
}
try {
- return getTotalDataSize(inputStream);
+ synchronized (mLock) {
+ return getTotalDataSizeLocked(inputStream);
+ }
} catch (IOException e) {
- Log.e(TAG, "error reading data block size");
+ Slog.e(TAG, "error reading data block size");
return 0;
} finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
- }
+ IoUtils.closeQuietly(inputStream);
}
}
+
+ @Override
+ public long getMaximumDataBlockSize() {
+ long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
+ }
+
};
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4cfd042..9d1f8c6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3757,9 +3757,10 @@
VirtualActivityDisplay(int width, int height, int density) {
DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, VIRTUAL_DISPLAY_BASE_NAME,
- width, height, density, null, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
- DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, null,
+ VIRTUAL_DISPLAY_BASE_NAME, width, height, density, null,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, null, null);
init(mVirtualDisplay.getDisplay());
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 6697b60..2737646 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,8 +29,11 @@
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
import android.hardware.display.IDisplayManager;
import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallbacks;
import android.hardware.display.WifiDisplayStatus;
import android.hardware.input.InputManagerInternal;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -39,6 +42,7 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -127,6 +131,7 @@
private final DisplayAdapterListener mDisplayAdapterListener;
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
+ private IMediaProjectionManager mProjectionService;
// The synchronization root for the display manager.
// This lock guards most of the display manager's state.
@@ -486,7 +491,8 @@
}
}
- private int createVirtualDisplayInternal(IBinder appToken, int callingUid, String packageName,
+ private int createVirtualDisplayInternal(IVirtualDisplayCallbacks callbacks,
+ IMediaProjection projection, int callingUid, String packageName,
String name, int width, int height, int densityDpi, Surface surface, int flags) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
@@ -496,8 +502,8 @@
}
DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
- appToken, callingUid, packageName, name, width, height, densityDpi,
- surface, flags);
+ callbacks, projection, callingUid, packageName,
+ name, width, height, densityDpi, surface, flags);
if (device == null) {
return -1;
}
@@ -511,7 +517,7 @@
// Something weird happened and the logical display was not created.
Slog.w(TAG, "Rejecting request to create virtual display "
+ "because the logical display was not created.");
- mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+ mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callbacks.asBinder());
handleDisplayDeviceRemovedLocked(device);
}
return -1;
@@ -878,6 +884,14 @@
mTempCallbacks.clear();
}
+ private IMediaProjectionManager getProjectionService() {
+ if (mProjectionService == null) {
+ IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
+ mProjectionService = IMediaProjectionManager.Stub.asInterface(b);
+ }
+ return mProjectionService;
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("DISPLAY MANAGER (dumpsys display)");
@@ -1215,13 +1229,14 @@
}
@Override // Binder call
- public int createVirtualDisplay(IBinder appToken, String packageName,
- String name, int width, int height, int densityDpi, Surface surface, int flags) {
+ public int createVirtualDisplay(IVirtualDisplayCallbacks callbacks,
+ IMediaProjection projection, String packageName, String name,
+ int width, int height, int densityDpi, Surface surface, int flags) {
final int callingUid = Binder.getCallingUid();
if (!validatePackageName(callingUid, packageName)) {
throw new SecurityException("packageName must match the calling uid");
}
- if (appToken == null) {
+ if (callbacks == null) {
throw new IllegalArgumentException("appToken must not be null");
}
if (TextUtils.isEmpty(name)) {
@@ -1231,51 +1246,78 @@
throw new IllegalArgumentException("width, height, and densityDpi must be "
+ "greater than 0");
}
+
+ if (projection != null) {
+ try {
+ if (!getProjectionService().isValidMediaProjection(projection)) {
+ throw new SecurityException("Invalid media projection");
+ }
+ } catch (RemoteException e) {
+ throw new SecurityException("unable to validate media projection");
+ }
+ flags &= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+ try {
+ flags |= projection.getVirtualDisplayFlags();
+ } catch (RemoteException e) {
+ throw new RuntimeException("unable to retrieve media projection flags");
+ }
+ }
+
+ if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) != 0) {
+ if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0 ||
+ (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
+ throw new IllegalArgumentException("screen sharing virtual displays must not "
+ + "be public or presentation displays");
+ }
+ if (!canProjectVideo(projection)) {
+ throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ + "MediaProjection token in order to create a screen sharing virtual "
+ + "display.");
+ }
+ }
+
+
if (callingUid != Process.SYSTEM_UID &&
(flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
- if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
- != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingPermission(
- android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
- != PackageManager.PERMISSION_GRANTED) {
+ if (!canProjectVideo(projection)) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
- + "CAPTURE_SECURE_VIDEO_OUTPUT permission to create a "
- + "public virtual display.");
+ + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ + "MediaProjection token to create a public virtual display.");
}
}
if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
- != PackageManager.PERMISSION_GRANTED) {
+ if (!canProjectSecureVideo(projection)) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
- + "to create a secure virtual display.");
+ + "or an appropriate MediaProjection token to create a "
+ + "secure virtual display.");
}
}
final long token = Binder.clearCallingIdentity();
try {
- return createVirtualDisplayInternal(appToken, callingUid, packageName,
- name, width, height, densityDpi, surface, flags);
+ return createVirtualDisplayInternal(callbacks, projection, callingUid,
+ packageName, name, width, height, densityDpi, surface, flags);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
- public void setVirtualDisplaySurface(IBinder appToken, Surface surface) {
+ public void setVirtualDisplaySurface(IVirtualDisplayCallbacks callbacks, Surface surface) {
final long token = Binder.clearCallingIdentity();
try {
- setVirtualDisplaySurfaceInternal(appToken, surface);
+ setVirtualDisplaySurfaceInternal(callbacks.asBinder(), surface);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
- public void releaseVirtualDisplay(IBinder appToken) {
+ public void releaseVirtualDisplay(IVirtualDisplayCallbacks callbacks) {
final long token = Binder.clearCallingIdentity();
try {
- releaseVirtualDisplayInternal(appToken);
+ releaseVirtualDisplayInternal(callbacks.asBinder());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1312,6 +1354,39 @@
}
return false;
}
+
+ private boolean canProjectVideo(IMediaProjection projection) {
+ if (projection != null) {
+ try {
+ if (projection.canProjectVideo()) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ }
+ }
+ if (mContext.checkCallingPermission(
+ android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+ != PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ return canProjectSecureVideo(projection);
+ }
+
+ private boolean canProjectSecureVideo(IMediaProjection projection) {
+ if (projection != null) {
+ try {
+ if (projection.canProjectSecureVideo()){
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to query projection service for permissions", e);
+ }
+ }
+ return mContext.checkCallingPermission(
+ android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
+ != PackageManager.PERMISSION_GRANTED;
+ }
}
private final class LocalService extends DisplayManagerInternal {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index ed619d9..284780d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -337,4 +337,4 @@
pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
}
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index ec14cf1..1032081 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -18,9 +18,13 @@
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.hardware.display.IVirtualDisplayCallbacks;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
@@ -28,6 +32,8 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import java.io.PrintWriter;
+
/**
* A display adapter that provides virtual displays on behalf of applications.
* <p>
@@ -40,30 +46,38 @@
private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
new ArrayMap<IBinder, VirtualDisplayDevice>();
+ private Handler mHandler;
// Called with SyncRoot lock held.
public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
super(syncRoot, context, handler, listener, TAG);
+ mHandler = handler;
}
- public DisplayDevice createVirtualDisplayLocked(IBinder appToken,
- int ownerUid, String ownerPackageName,
+ public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallbacks callbacks,
+ IMediaProjection projection, int ownerUid, String ownerPackageName,
String name, int width, int height, int densityDpi, Surface surface, int flags) {
boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
+ IBinder appToken = callbacks.asBinder();
IBinder displayToken = SurfaceControl.createDisplay(name, secure);
VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
- ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags);
+ ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
+ new Callbacks(callbacks, mHandler));
+
+ mVirtualDisplayDevices.put(appToken, device);
try {
+ if (projection != null) {
+ projection.addCallback(new MediaProjectionCallback(appToken));
+ }
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
+ mVirtualDisplayDevices.remove(appToken);
device.destroyLocked();
return null;
}
- mVirtualDisplayDevices.put(appToken, device);
-
// Return the display device without actually sending the event indicating
// that it was added. The caller will handle it.
return device;
@@ -98,23 +112,35 @@
}
}
- private final class VirtualDisplayDevice extends DisplayDevice
- implements DeathRecipient {
+ private void handleMediaProjectionStoppedLocked(IBinder appToken) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+ if (device != null) {
+ Slog.i(TAG, "Virtual display device released because media projection stopped: "
+ + device.mName);
+ device.stopLocked();
+ }
+ }
+
+ private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
private final IBinder mAppToken;
private final int mOwnerUid;
final String mOwnerPackageName;
- private final String mName;
+ final String mName;
private final int mWidth;
private final int mHeight;
private final int mDensityDpi;
private final int mFlags;
+ private final Callbacks mCallbacks;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
+ private int mState;
+ private boolean mStopped;
- public VirtualDisplayDevice(IBinder displayToken,
- IBinder appToken, int ownerUid, String ownerPackageName,
- String name, int width, int height, int densityDpi, Surface surface, int flags) {
+ public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
+ int ownerUid, String ownerPackageName,
+ String name, int width, int height, int densityDpi, Surface surface, int flags,
+ Callbacks callbacks) {
super(VirtualDisplayAdapter.this, displayToken);
mAppToken = appToken;
mOwnerUid = ownerUid;
@@ -125,6 +151,8 @@
mDensityDpi = densityDpi;
mSurface = surface;
mFlags = flags;
+ mCallbacks = callbacks;
+ mState = Display.STATE_UNKNOWN;
}
@Override
@@ -142,6 +170,19 @@
mSurface = null;
}
SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+ mCallbacks.dispatchDisplayStopped();
+ }
+
+ @Override
+ public void requestDisplayStateLocked(int state) {
+ if (state != mState) {
+ mState = state;
+ if (state == Display.STATE_OFF) {
+ mCallbacks.dispatchDisplayPaused();
+ } else {
+ mCallbacks.dispatchDisplayResumed();
+ }
+ }
}
@Override
@@ -150,7 +191,7 @@
}
public void setSurfaceLocked(Surface surface) {
- if (mSurface != surface) {
+ if (!mStopped && mSurface != surface) {
if ((mSurface != null) != (surface != null)) {
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
}
@@ -160,6 +201,20 @@
}
}
+ public void stopLocked() {
+ setSurfaceLocked(null);
+ mStopped = true;
+ }
+
+ @Override
+ public void dumpLocked(PrintWriter pw) {
+ super.dumpLocked(pw);
+ pw.println("mFlags=" + mFlags);
+ pw.println("mState=" + Display.stateToString(mState));
+ pw.println("mStopped=" + mStopped);
+ }
+
+
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
@@ -174,9 +229,11 @@
mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
mInfo.flags = 0;
if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
- | DisplayDeviceInfo.FLAG_NEVER_BLANK
- | DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
+ if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
+ | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+ }
} else if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
}
@@ -195,4 +252,62 @@
return mInfo;
}
}
+
+ private static class Callbacks extends Handler {
+ private static final int MSG_ON_DISPLAY_PAUSED = 0;
+ private static final int MSG_ON_DISPLAY_RESUMED = 1;
+ private static final int MSG_ON_DISPLAY_STOPPED = 2;
+
+ private final IVirtualDisplayCallbacks mCallbacks;
+
+ public Callbacks(IVirtualDisplayCallbacks callbacks, Handler handler) {
+ super(handler.getLooper());
+ mCallbacks = callbacks;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ try {
+ switch (msg.what) {
+ case MSG_ON_DISPLAY_PAUSED:
+ mCallbacks.onDisplayPaused();
+ break;
+ case MSG_ON_DISPLAY_RESUMED:
+ mCallbacks.onDisplayResumed();
+ break;
+ case MSG_ON_DISPLAY_STOPPED:
+ mCallbacks.onDisplayStopped();
+ break;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
+ }
+ }
+
+ public void dispatchDisplayPaused() {
+ sendEmptyMessage(MSG_ON_DISPLAY_PAUSED);
+ }
+
+ public void dispatchDisplayResumed() {
+ sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
+ }
+
+ public void dispatchDisplayStopped() {
+ sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
+ }
+ }
+
+ private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
+ private IBinder mAppToken;
+ public MediaProjectionCallback(IBinder appToken) {
+ mAppToken = appToken;
+ }
+
+ @Override
+ public void onStop() {
+ synchronized (getSyncRoot()) {
+ handleMediaProjectionStoppedLocked(mAppToken);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
index 7e7f2e4..607805b 100644
--- a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -31,7 +31,8 @@
* @hide
*/
public class ActivityRecognitionProxy {
- private final String TAG = "ActivityRecognitionProxy";
+ private static final String TAG = "ActivityRecognitionProxy";
+
private final ServiceWatcher mServiceWatcher;
private final ActivityRecognitionHardware mActivityRecognitionHardware;
@@ -85,6 +86,7 @@
// try to bind the provider
if (!activityRecognitionProxy.mServiceWatcher.start()) {
+ Log.e(TAG, "ServiceWatcher could not start.");
return null;
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
new file mode 100644
index 0000000..7b28699
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -0,0 +1,329 @@
+/*
+ * 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.server.media.projection;
+
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.MediaProjectionManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages MediaProjection sessions.
+ *
+ * The {@link MediaProjectionManagerService} manages the creation and lifetime of MediaProjections,
+ * as well as the capabilities they grant. Any service using MediaProjection tokens as permission
+ * grants <b>must</b> validate the token before use by calling {@link
+ * IMediaProjectionService#isValidMediaProjection}.
+ */
+public final class MediaProjectionManagerService extends SystemService
+ implements Watchdog.Monitor {
+ private static final String TAG = "MediaProjectionManagerService";
+
+ private final Object mLock = new Object(); // Protects the list of media projections
+ private final Map<IBinder, MediaProjection> mProjectionGrants;
+
+ private final Context mContext;
+ private final AppOpsManager mAppOps;
+
+ public MediaProjectionManagerService(Context context) {
+ super(context);
+ mContext = context;
+ mProjectionGrants = new ArrayMap<IBinder, MediaProjection>();
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ Watchdog.getInstance().addMonitor(this);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(),
+ false /*allowIsolated*/);
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) { /* check for deadlock */ }
+ }
+
+ private void dump(final PrintWriter pw) {
+ pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)");
+ synchronized (mLock) {
+ Collection<MediaProjection> projections = mProjectionGrants.values();
+ pw.println("Media Projections: size=" + projections.size());
+ for (MediaProjection mp : projections) {
+ mp.dump(pw, " ");
+ }
+ }
+ }
+
+ private final class BinderService extends IMediaProjectionManager.Stub {
+
+ @Override // Binder call
+ public boolean hasProjectionPermission(int uid, String packageName) {
+ long token = Binder.clearCallingIdentity();
+ boolean hasPermission = false;
+ try {
+ hasPermission |= checkPermission(packageName,
+ android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+ || mAppOps.checkOpNoThrow(
+ AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
+ == AppOpsManager.MODE_ALLOWED;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return hasPermission;
+ }
+
+ @Override // Binder call
+ public IMediaProjection createProjection(int uid, String packageName, int type,
+ boolean isPermanentGrant) {
+ if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant "
+ + "projection permission");
+ }
+ long callingToken = Binder.clearCallingIdentity();
+ MediaProjection projection;
+ try {
+ projection = new MediaProjection(type, uid, packageName);
+ if (isPermanentGrant) {
+ mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+ projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ return projection;
+ }
+
+ @Override // Binder call
+ public boolean isValidMediaProjection(IMediaProjection projection) {
+ return mProjectionGrants.containsKey(projection.asBinder());
+ }
+
+ @Override // Binder call
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mContext == null
+ || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump MediaProjectionManager from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ dump(pw);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean checkPermission(String packageName, String permission) {
+ return mContext.getPackageManager().checkPermission(permission, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+
+ private final class MediaProjection extends IMediaProjection.Stub implements DeathRecipient {
+ public int uid;
+ public String packageName;
+
+ private IBinder mToken;
+ private int mType;
+ private CallbackDelegate mCallbackDelegate;
+
+ public MediaProjection(int type, int uid, String packageName) {
+ mType = type;
+ this.uid = uid;
+ this.packageName = packageName;
+ mCallbackDelegate = new CallbackDelegate();
+ }
+
+ @Override // Binder call
+ public boolean canProjectVideo() {
+ return mType == MediaProjectionManager.TYPE_MIRRORING ||
+ mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE;
+ }
+
+ @Override // Binder call
+ public boolean canProjectSecureVideo() {
+ return false;
+ }
+
+ @Override // Binder call
+ public boolean canProjectAudio() {
+ return mType == MediaProjectionManager.TYPE_MIRRORING ||
+ mType == MediaProjectionManager.TYPE_PRESENTATION;
+ }
+
+ @Override // Binder call
+ public int getVirtualDisplayFlags() {
+ switch (mType) {
+ case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
+ return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE;
+ case MediaProjectionManager.TYPE_MIRRORING:
+ return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+ case MediaProjectionManager.TYPE_PRESENTATION:
+ return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ }
+ throw new RuntimeException("Unknown MediaProjection type");
+ }
+
+ @Override // Binder call
+ public void start(IMediaProjectionCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ synchronized (mLock) {
+ if (mProjectionGrants.containsKey(asBinder())) {
+ throw new IllegalStateException(
+ "Cannot start already started MediaProjection");
+ }
+ addCallback(callback);
+ try {
+ mToken = callback.asBinder();
+ mToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG,
+ "MediaProjectionCallbacks must be valid, aborting MediaProjection", e);
+ return;
+ }
+ mProjectionGrants.put(asBinder(), this);
+ }
+ }
+
+ @Override // Binder call
+ public void stop() {
+ synchronized (mLock) {
+ if (!mProjectionGrants.containsKey(asBinder())) {
+ Slog.w(TAG, "Attempted to stop inactive MediaProjection "
+ + "(uid=" + Binder.getCallingUid() + ", "
+ + "pid=" + Binder.getCallingPid() + ")");
+ return;
+ }
+ mToken.unlinkToDeath(this, 0);
+ mCallbackDelegate.dispatchStop();
+ mProjectionGrants.remove(asBinder());
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ stop();
+ }
+
+ @Override
+ public void addCallback(IMediaProjectionCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ mCallbackDelegate.add(callback);
+ }
+
+ @Override
+ public void removeCallback(IMediaProjectionCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ mCallbackDelegate.remove(callback);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
+ }
+ }
+
+ private static class CallbackDelegate {
+ private static final int MSG_ON_STOP = 0;
+ private List<IMediaProjectionCallback> mCallbacks;
+ private Handler mHandler;
+ private Object mLock = new Object();
+
+ public CallbackDelegate() {
+ mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+ mCallbacks = new ArrayList<IMediaProjectionCallback>();
+ }
+
+ public void add(IMediaProjectionCallback callback) {
+ synchronized (mLock) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void remove(IMediaProjectionCallback callback) {
+ synchronized (mLock) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ public void dispatchStop() {
+ synchronized (mLock) {
+ for (final IMediaProjectionCallback callback : mCallbacks) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ callback.onStop();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify media projection has stopped", e);
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+
+ private static String typeToString(int type) {
+ switch (type) {
+ case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
+ return "TYPE_SCREEN_CAPTURE";
+ case MediaProjectionManager.TYPE_MIRRORING:
+ return "TYPE_MIRRORING";
+ case MediaProjectionManager.TYPE_PRESENTATION:
+ return "TYPE_PRESENTATION";
+ }
+ return Integer.toString(type);
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aa9e151..0e18776 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -220,6 +220,7 @@
private static final int REASON_NOMAN_CANCEL_ALL = 9;
private static final int REASON_LISTENER_CANCEL = 10;
private static final int REASON_LISTENER_CANCEL_ALL = 11;
+ private static final int REASON_GROUP_SUMMARY_CANCELED = 12;
private static class Archive {
final int mBufferSize;
@@ -2071,9 +2072,9 @@
mHandler.post(new Runnable() {
@Override
public void run() {
+ String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId,
- mustHaveFlags, mustNotHaveFlags, reason,
- listener == null ? null : listener.component.toShortString());
+ mustHaveFlags, mustNotHaveFlags, reason, listenerName);
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id, userId);
@@ -2097,6 +2098,7 @@
mNotificationsByKey.remove(r.sbn.getKey());
cancelNotificationLocked(r, sendDelete, reason);
+ cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName);
updateLightsLocked();
}
}
@@ -2135,13 +2137,14 @@
boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
int mustNotHaveFlags, boolean doit, int userId, int reason,
ManagedServiceInfo listener) {
+ String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listener == null ? null : listener.component.toShortString());
+ listenerName);
synchronized (mNotificationList) {
final int N = mNotificationList.size();
- boolean canceledSomething = false;
+ ArrayList<NotificationRecord> canceledNotifications = null;
for (int i = N-1; i >= 0; --i) {
NotificationRecord r = mNotificationList.get(i);
if (!notificationMatchesUserId(r, userId)) {
@@ -2160,7 +2163,10 @@
if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
continue;
}
- canceledSomething = true;
+ if (canceledNotifications == null) {
+ canceledNotifications = new ArrayList<>();
+ }
+ canceledNotifications.add(r);
if (!doit) {
return true;
}
@@ -2168,19 +2174,27 @@
mNotificationsByKey.remove(r.sbn.getKey());
cancelNotificationLocked(r, false, reason);
}
- if (canceledSomething) {
+ if (doit && canceledNotifications != null) {
+ final int M = canceledNotifications.size();
+ for (int i = 0; i < M; i++) {
+ cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
+ listenerName);
+ }
+ }
+ if (canceledNotifications != null) {
updateLightsLocked();
}
- return canceledSomething;
+ return canceledNotifications != null;
}
}
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
ManagedServiceInfo listener, boolean includeCurrentProfiles) {
+ String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
- null, userId, 0, 0, reason,
- listener == null ? null : listener.component.toShortString());
+ null, userId, 0, 0, reason, listenerName);
+ ArrayList<NotificationRecord> canceledNotifications = null;
final int N = mNotificationList.size();
for (int i=N-1; i>=0; i--) {
NotificationRecord r = mNotificationList.get(i);
@@ -2199,11 +2213,54 @@
mNotificationList.remove(i);
mNotificationsByKey.remove(r.sbn.getKey());
cancelNotificationLocked(r, true, reason);
+ // Make a note so we can cancel children later.
+ if (canceledNotifications == null) {
+ canceledNotifications = new ArrayList<>();
+ }
+ canceledNotifications.add(r);
}
}
+ int M = canceledNotifications != null ? canceledNotifications.size() : 0;
+ for (int i = 0; i < M; i++) {
+ cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
+ listenerName);
+ }
updateLightsLocked();
}
+ // Warning: The caller is responsible for invoking updateLightsLocked().
+ private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
+ String listenerName) {
+ Notification n = r.getNotification();
+ if (n.getGroup() == null || (n.flags & Notification.FLAG_GROUP_SUMMARY) == 0) {
+ return;
+ }
+
+ String pkg = r.sbn.getPackageName();
+ int userId = r.getUserId();
+
+ if (pkg == null) {
+ if (DBG) Log.e(TAG, "No package for group summary: " + r.getKey());
+ return;
+ }
+
+ final int N = mNotificationList.size();
+ for (int i = N - 1; i >= 0; i--) {
+ NotificationRecord childR = mNotificationList.get(i);
+ Notification childN = childR.getNotification();
+ StatusBarNotification childSbn = childR.sbn;
+ if (childR.getUserId() == userId && pkg.equals(childSbn.getPackageName()) &&
+ n.getGroup().equals(childN.getGroup())) {
+ EventLogTags.writeNotificationCancel(callingUid, callingPid,
+ pkg, childSbn.getId(), childSbn.getTag(), userId, 0, 0,
+ REASON_GROUP_SUMMARY_CANCELED, listenerName);
+ mNotificationList.remove(i);
+ mNotificationsByKey.remove(childR.getKey());
+ cancelNotificationLocked(childR, false, REASON_GROUP_SUMMARY_CANCELED);
+ }
+ }
+ }
+
// lock on mNotificationList
void updateLightsLocked()
{
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 8f237db..252f2f4 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -20,8 +20,10 @@
import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
import android.content.Context;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiHotplugEvent;
+import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.media.AudioPatch;
@@ -33,7 +35,6 @@
import android.media.tv.TvInputInfo;
import android.media.tv.TvStreamConfig;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -66,27 +67,24 @@
private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
private final Context mContext;
- private final TvInputManagerService.Client mClient;
+ private final Listener mListener;
private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
private final AudioManager mAudioManager;
private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
// TODO: Should handle INACTIVE case.
private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+ private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
- // Calls to mClient should happen here.
- private final HandlerThread mHandlerThread = new HandlerThread(TAG);
- private final Handler mHandler;
+ // Calls to mListener should happen here.
+ private final Handler mHandler = new ListenerHandler();
private final Object mLock = new Object();
- public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
+ public TvInputHardwareManager(Context context, Listener listener) {
mContext = context;
- mClient = client;
+ mListener = listener;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mHal.init();
-
- mHandlerThread.start();
- mHandler = new ClientHandler(mHandlerThread.getLooper());
}
public void onBootPhase(int phase) {
@@ -105,7 +103,8 @@
connection.updateConfigsLocked(configs);
mConnections.put(info.getDeviceId(), connection);
buildInfoListLocked();
- // TODO: notify if necessary
+ mHandler.obtainMessage(
+ ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
}
}
@@ -127,7 +126,8 @@
connection.resetLocked(null, null, null, null, null);
mConnections.remove(deviceId);
buildInfoListLocked();
- // TODO: notify if necessary
+ mHandler.obtainMessage(
+ ListenerHandler.HARDWARE_DEVICE_REMOVED, deviceId, 0).sendToTarget();
}
}
@@ -191,7 +191,7 @@
for (int i = 0; i < mHdmiStateMap.size(); ++i) {
String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
if (inputId != null && inputId.equals(info.getId())) {
- mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
inputId).sendToTarget();
}
@@ -273,7 +273,7 @@
if (inputId == null) {
return;
}
- mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}
}
@@ -502,20 +502,48 @@
}
}
- private class ClientHandler extends Handler {
- private static final int DO_SET_AVAILABLE = 1;
+ interface Listener {
+ public void onStateChanged(String inputId, int state);
+ public void onHardwareDeviceAdded(TvInputHardwareInfo info);
+ public void onHardwareDeviceRemoved(int deviceId);
+ public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
+ public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
+ }
- ClientHandler(Looper looper) {
- super(looper);
- }
+ private class ListenerHandler extends Handler {
+ private static final int STATE_CHANGED = 1;
+ private static final int HARDWARE_DEVICE_ADDED = 2;
+ private static final int HARDWARE_DEVICE_REMOVED = 3;
+ private static final int CEC_DEVICE_ADDED = 4;
+ private static final int CEC_DEVICE_REMOVED = 5;
@Override
public final void handleMessage(Message msg) {
switch (msg.what) {
- case DO_SET_AVAILABLE: {
+ case STATE_CHANGED: {
String inputId = (String) msg.obj;
int state = msg.arg1;
- mClient.setState(inputId, state);
+ mListener.onStateChanged(inputId, state);
+ break;
+ }
+ case HARDWARE_DEVICE_ADDED: {
+ TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
+ mListener.onHardwareDeviceAdded(info);
+ break;
+ }
+ case HARDWARE_DEVICE_REMOVED: {
+ int deviceId = msg.arg1;
+ mListener.onHardwareDeviceRemoved(deviceId);
+ break;
+ }
+ case CEC_DEVICE_ADDED: {
+ HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
+ mListener.onHdmiCecDeviceAdded(info);
+ break;
+ }
+ case CEC_DEVICE_REMOVED: {
+ HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
+ mListener.onHdmiCecDeviceRemoved(info);
break;
}
default: {
@@ -525,4 +553,14 @@
}
}
}
+
+ private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
+ @Override
+ public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
+ mHandler.obtainMessage(
+ activated ? ListenerHandler.CEC_DEVICE_ADDED
+ : ListenerHandler.CEC_DEVICE_REMOVED,
+ 0, 0, deviceInfo).sendToTarget();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 20fdefa..69c693a 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -37,6 +37,7 @@
import android.content.pm.ServiceInfo;
import android.database.Cursor;
import android.graphics.Rect;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
@@ -80,6 +81,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -113,7 +115,7 @@
mContentResolver = context.getContentResolver();
mLogHandler = new LogHandler(IoThread.get().getLooper());
- mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
+ mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +131,7 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
registerBroadcastReceivers();
+ } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mLock) {
buildTvInputListLocked(mCurrentUserId);
}
@@ -201,12 +204,15 @@
}, UserHandle.ALL, intentFilter, null, null);
}
+ private static boolean hasHardwarePermission(PackageManager pm, ComponentName name) {
+ return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
+ name.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+ }
+
private void buildTvInputListLocked(int userId) {
UserState userState = getUserStateLocked(userId);
- Map<String, TvInputState> oldInputMap = userState.inputMap;
- userState.inputMap = new HashMap<String, TvInputState>();
-
+ Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
userState.packageSet.clear();
if (DEBUG) Slog.d(TAG, "buildTvInputList");
@@ -214,6 +220,7 @@
List<ResolveInfo> services = pm.queryIntentServices(
new Intent(TvInputService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ List<TvInputInfo> infoList = new ArrayList<TvInputInfo>();
for (ResolveInfo ri : services) {
ServiceInfo si = ri.serviceInfo;
if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
@@ -222,23 +229,56 @@
continue;
}
try {
- TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
- if (DEBUG) Slog.d(TAG, "add " + info.getId());
- TvInputState state = oldInputMap.get(info.getId());
- if (state == null) {
- state = new TvInputState();
+ infoList.clear();
+ ComponentName service = new ComponentName(si.packageName, si.name);
+ if (hasHardwarePermission(pm, service)) {
+ ServiceState serviceState = userState.serviceStateMap.get(service);
+ if (serviceState == null) {
+ // We see this hardware TV input service for the first time; we need to
+ // prepare the ServiceState object so that we can connect to the service and
+ // let it add TvInputInfo objects to mInputList if there's any.
+ serviceState = new ServiceState(service, userId);
+ userState.serviceStateMap.put(service, serviceState);
+ } else {
+ infoList.addAll(serviceState.mInputList);
+ }
+ } else {
+ infoList.add(TvInputInfo.createTvInputInfo(mContext, ri));
}
- userState.inputMap.put(info.getId(), state);
- state.mInfo = info;
- userState.packageSet.add(si.packageName);
+
+ for (TvInputInfo info : infoList) {
+ if (DEBUG) Slog.d(TAG, "add " + info.getId());
+ TvInputState state = userState.inputMap.get(info.getId());
+ if (state == null) {
+ state = new TvInputState();
+ }
+ state.mInfo = info;
+ inputMap.put(info.getId(), state);
+ }
// Reconnect the service if existing input is updated.
- updateServiceConnectionLocked(info.getId(), userId);
+ updateServiceConnectionLocked(service, userId);
+
+ userState.packageSet.add(si.packageName);
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Can't load TV input " + si.name, e);
}
}
- oldInputMap.clear();
+
+ for (String inputId : inputMap.keySet()) {
+ if (!userState.inputMap.containsKey(inputId)) {
+ notifyInputAddedLocked(userState, inputId);
+ }
+ }
+
+ for (String inputId : userState.inputMap.keySet()) {
+ if (!inputMap.containsKey(inputId)) {
+ notifyInputRemovedLocked(userState, inputId);
+ }
+ }
+
+ userState.inputMap.clear();
+ userState.inputMap = inputMap;
}
private void switchUser(int userId) {
@@ -305,11 +345,11 @@
return userState;
}
- private ServiceState getServiceStateLocked(String inputId, int userId) {
+ private ServiceState getServiceStateLocked(ComponentName name, int userId) {
UserState userState = getUserStateLocked(userId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
if (serviceState == null) {
- throw new IllegalStateException("Service state not found for " + inputId + " (userId="
+ throw new IllegalStateException("Service state not found for " + name + " (userId="
+ userId + ")");
}
return serviceState;
@@ -344,9 +384,16 @@
false, methodName, null);
}
- private void updateServiceConnectionLocked(String inputId, int userId) {
+ private static boolean shouldMaintainConnection(ServiceState serviceState) {
+ return !serviceState.mClientTokens.isEmpty()
+ || !serviceState.mSessionTokens.isEmpty()
+ || serviceState.mIsHardware;
+ // TODO: Find a way to maintain connection only when necessary.
+ }
+
+ private void updateServiceConnectionLocked(ComponentName service, int userId) {
UserState userState = getUserStateLocked(userId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(service);
if (serviceState == null) {
return;
}
@@ -357,9 +404,8 @@
}
serviceState.mReconnecting = false;
}
- boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
- && serviceState.mSessionTokens.isEmpty();
- if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
+ boolean maintainConnection = shouldMaintainConnection(serviceState);
+ if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) {
// This means that the service is not yet connected but its state indicates that we
// have pending requests. Then, connect the service.
if (serviceState.mBound) {
@@ -368,25 +414,23 @@
return;
}
if (DEBUG) {
- Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
- + ")");
+ Slog.d(TAG, "bindServiceAsUser(service=" + service + ", userId=" + userId + ")");
}
- Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
- userState.inputMap.get(inputId).mInfo.getComponent());
+ Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(service);
// Binding service may fail if the service is updating.
// In that case, the connection will be revived in buildTvInputListLocked called by
// onSomePackagesChanged.
serviceState.mBound = mContext.bindServiceAsUser(
i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
- } else if (serviceState.mService != null && isStateEmpty) {
+ } else if (serviceState.mService != null && !maintainConnection) {
// This means that the service is already connected but its state indicates that we have
// nothing to do with it. Then, disconnect the service.
if (DEBUG) {
- Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
+ Slog.d(TAG, "unbindService(service=" + service + ")");
}
mContext.unbindService(serviceState.mConnection);
- userState.serviceStateMap.remove(inputId);
+ userState.serviceStateMap.remove(service);
}
}
@@ -407,7 +451,7 @@
final UserState userState = getUserStateLocked(userId);
final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (DEBUG) {
- Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
+ Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")");
}
final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
@@ -417,14 +461,14 @@
@Override
public void onSessionCreated(ITvInputSession session) {
if (DEBUG) {
- Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
+ Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
}
synchronized (mLock) {
sessionState.mSession = session;
if (session == null) {
removeSessionStateLocked(sessionToken, userId);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
- null, null, sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mInfo.getId(), null, null, sessionState.mSeq);
} else {
try {
session.asBinder().linkToDeath(sessionState, 0);
@@ -439,8 +483,9 @@
}
clientState.mSessionTokens.add(sessionState.mSessionToken);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
- sessionToken, channels[0], sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mInfo.getId(), sessionToken, channels[0],
+ sessionState.mSeq);
}
channels[0].dispose();
}
@@ -520,6 +565,23 @@
}
@Override
+ public void onContentBlocked(String rating) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onContentBlocked()");
+ }
+ if (sessionState.mSession == null || sessionState.mClient == null) {
+ return;
+ }
+ try {
+ sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onContentBlocked");
+ }
+ }
+ }
+
+ @Override
public void onSessionEvent(String eventType, Bundle eventArgs) {
synchronized (mLock) {
if (DEBUG) {
@@ -540,12 +602,12 @@
// Create a session. When failed, send a null token immediately.
try {
- service.createSession(channels[1], callback);
+ service.createSession(channels[1], callback, sessionState.mInfo.getId());
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
removeSessionStateLocked(sessionToken, userId);
- sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
- sessionState.mSeq);
+ sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
+ null, sessionState.mSeq);
}
channels[1].dispose();
}
@@ -595,11 +657,14 @@
}
}
- ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
- if (serviceState != null) {
- serviceState.mSessionTokens.remove(sessionToken);
+ TvInputInfo info = sessionState.mInfo;
+ if (info != null) {
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
}
- updateServiceConnectionLocked(sessionState.mInputId, userId);
+ updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
}
private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
@@ -613,7 +678,11 @@
}
}
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ TvInputInfo info = userState.inputMap.get(inputId).mInfo;
+ if (info == null) {
+ return;
+ }
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState == null) {
return;
}
@@ -633,14 +702,40 @@
Slog.e(TAG, "error in unregisterCallback", e);
} finally {
serviceState.mCallback = null;
- updateServiceConnectionLocked(inputId, userId);
+ updateServiceConnectionLocked(info.getComponent(), userId);
}
}
- private void notifyStateChangedLocked(UserState userState, String inputId,
+ private void notifyInputAddedLocked(UserState userState, String inputId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyInputAdded: inputId = " + inputId);
+ }
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ try {
+ callback.onInputAdded(inputId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report added input to callback.");
+ }
+ }
+ }
+
+ private void notifyInputRemovedLocked(UserState userState, String inputId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyInputRemovedLocked: inputId = " + inputId);
+ }
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ try {
+ callback.onInputRemoved(inputId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report removed input to callback.");
+ }
+ }
+ }
+
+ private void notifyInputStateChangedLocked(UserState userState, String inputId,
int state, ITvInputManagerCallback targetCallback) {
if (DEBUG) {
- Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+ Slog.d(TAG, "notifyInputStateChangedLocked: inputId = " + inputId
+ "; state = " + state);
}
if (targetCallback == null) {
@@ -666,14 +761,13 @@
ServiceState serviceState = userState.serviceStateMap.get(inputId);
int oldState = inputState.mState;
inputState.mState = state;
- boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
- && serviceState.mSessionTokens.isEmpty();
- if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+ if (serviceState != null && serviceState.mService == null
+ && shouldMaintainConnection(serviceState)) {
// We don't notify state change while reconnecting. It should remain disconnected.
return;
}
if (oldState != state) {
- notifyStateChangedLocked(userState, inputId, state, null);
+ notifyInputStateChangedLocked(userState, inputId, state, null);
}
}
@@ -698,6 +792,22 @@
}
@Override
+ public TvInputInfo getTvInputInfo(String inputId, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getTvInputInfo");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ TvInputState state = userState.inputMap.get(inputId);
+ return state == null ? null : state.mInfo;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void registerCallback(final ITvInputManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "registerCallback");
@@ -707,7 +817,7 @@
UserState userState = getUserStateLocked(resolvedUserId);
userState.callbackSet.add(callback);
for (TvInputState state : userState.inputMap.values()) {
- notifyStateChangedLocked(userState, state.mInfo.getId(),
+ notifyInputStateChangedLocked(userState, state.mInfo.getId(),
state.mState, callback);
}
}
@@ -741,11 +851,11 @@
try {
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ TvInputInfo info = userState.inputMap.get(inputId).mInfo;
+ ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
if (serviceState == null) {
- serviceState = new ServiceState(
- userState.inputMap.get(inputId).mInfo, resolvedUserId);
- userState.serviceStateMap.put(inputId, serviceState);
+ serviceState = new ServiceState(info.getComponent(), resolvedUserId);
+ userState.serviceStateMap.put(info.getComponent(), serviceState);
}
// Send a null token immediately while reconnecting.
if (serviceState.mReconnecting == true) {
@@ -755,7 +865,7 @@
// Create a new session token and a session state.
IBinder sessionToken = new Binder();
- SessionState sessionState = new SessionState(sessionToken, inputId, client,
+ SessionState sessionState = new SessionState(sessionToken, info, client,
seq, callingUid, resolvedUserId);
// Add them to the global session state map of the current user.
@@ -768,7 +878,7 @@
createSessionInternalLocked(serviceState.mService, sessionToken,
resolvedUserId);
} else {
- updateServiceConnectionLocked(inputId, resolvedUserId);
+ updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
}
}
} finally {
@@ -816,6 +926,27 @@
}
@Override
+ public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+ int height, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "dispatchSurfaceChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .dispatchSurfaceChanged(format, width, height);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in dispatchSurfaceChanged", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void setVolume(IBinder sessionToken, float volume, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -861,8 +992,7 @@
}
// Create a log entry and fill it later.
- String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
- .getServiceInfo().packageName;
+ String packageName = sessionState.mInfo.getServiceInfo().packageName;
ContentValues values = new ContentValues();
values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
@@ -888,6 +1018,26 @@
}
@Override
+ public void unblockContent(IBinder sessionToken, String unblockedRating, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "unblockContent");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .unblockContent(unblockedRating);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in unblockContent", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1025,6 +1175,7 @@
@Override
public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ // TODO: Revisit to sort out deviceId ownership.
if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return;
@@ -1107,8 +1258,8 @@
pw.println("inputMap: inputId -> TvInputState");
pw.increaseIndent();
- for (TvInputState state : userState.inputMap.values()) {
- pw.println(state.toString());
+ for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
+ pw.println(entry.getKey() + ": " + entry.getValue());
}
pw.decreaseIndent();
@@ -1149,9 +1300,9 @@
}
pw.decreaseIndent();
- pw.println("serviceStateMap: inputId -> ServiceState");
+ pw.println("serviceStateMap: ComponentName -> ServiceState");
pw.increaseIndent();
- for (Map.Entry<String, ServiceState> entry :
+ for (Map.Entry<ComponentName, ServiceState> entry :
userState.serviceStateMap.entrySet()) {
ServiceState service = entry.getValue();
pw.println(entry.getKey() + ": " + service);
@@ -1189,7 +1340,7 @@
pw.println(entry.getKey() + ": " + session);
pw.increaseIndent();
- pw.println("mInputId: " + session.mInputId);
+ pw.println("mInfo: " + session.mInfo);
pw.println("mClient: " + session.mClient);
pw.println("mSeq: " + session.mSeq);
pw.println("mCallingUid: " + session.mCallingUid);
@@ -1239,8 +1390,8 @@
new HashMap<IBinder, ClientState>();
// A mapping from the name of a TV input service to its state.
- private final Map<String, ServiceState> serviceStateMap =
- new HashMap<String, ServiceState>();
+ private final Map<ComponentName, ServiceState> serviceStateMap =
+ new HashMap<ComponentName, ServiceState>();
// A mapping from the token of a TV input session to its state.
private final Map<IBinder, SessionState> sessionStateMap =
@@ -1293,21 +1444,24 @@
private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
private final ServiceConnection mConnection;
- private final TvInputInfo mTvInputInfo;
+ private final ComponentName mName;
+ private final boolean mIsHardware;
+ private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
private ITvInputService mService;
private ServiceCallback mCallback;
private boolean mBound;
private boolean mReconnecting;
- private ServiceState(TvInputInfo inputInfo, int userId) {
- mTvInputInfo = inputInfo;
- mConnection = new InputServiceConnection(inputInfo, userId);
+ private ServiceState(ComponentName name, int userId) {
+ mName = name;
+ mConnection = new InputServiceConnection(name, userId);
+ mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mName);
}
}
private final class SessionState implements IBinder.DeathRecipient {
- private final String mInputId;
+ private final TvInputInfo mInfo;
private final ITvInputClient mClient;
private final int mSeq;
private final int mCallingUid;
@@ -1316,10 +1470,10 @@
private ITvInputSession mSession;
private Uri mLogUri;
- private SessionState(IBinder sessionToken, String inputId, ITvInputClient client, int seq,
+ private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
int callingUid, int userId) {
mSessionToken = sessionToken;
- mInputId = inputId;
+ mInfo = info;
mClient = client;
mSeq = seq;
mCallingUid = callingUid;
@@ -1343,28 +1497,29 @@
}
private final class InputServiceConnection implements ServiceConnection {
- private final TvInputInfo mTvInputInfo;
+ private final ComponentName mName;
private final int mUserId;
- private InputServiceConnection(TvInputInfo inputInfo, int userId) {
+ private InputServiceConnection(ComponentName name, int userId) {
+ mName = name;
mUserId = userId;
- mTvInputInfo = inputInfo;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- String inputId = mTvInputInfo.getId();
if (DEBUG) {
- Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
+ Slog.d(TAG, "onServiceConnected(name=" + name + ")");
}
synchronized (mLock) {
+ List<TvInputHardwareInfo> hardwareInfoList =
+ mTvInputHardwareManager.getHardwareList();
UserState userState = getUserStateLocked(mUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(mName);
serviceState.mService = ITvInputService.Stub.asInterface(service);
// Register a callback, if we need to.
- if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
- serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
+ if (serviceState.mIsHardware && serviceState.mCallback == null) {
+ serviceState.mCallback = new ServiceCallback(mName, mUserId);
try {
serviceState.mService.registerCallback(serviceState.mCallback);
} catch (RemoteException e) {
@@ -1377,10 +1532,24 @@
createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
}
- TvInputState inputState = userState.inputMap.get(inputId);
- if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
- notifyStateChangedLocked(userState, mTvInputInfo.getId(),
- inputState.mState, null);
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.mInfo.getComponent().equals(name)
+ && inputState.mState != INPUT_STATE_DISCONNECTED) {
+ notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
+ inputState.mState, null);
+ }
+ }
+
+ if (serviceState.mIsHardware) {
+ for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
+ try {
+ serviceState.mService.notifyHardwareAdded(hardwareInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+
+ // TODO: Grab CEC devices and notify them to the service.
}
}
}
@@ -1388,15 +1557,15 @@
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) {
- Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
+ Slog.d(TAG, "onServiceDisconnected(name=" + name + ")");
}
- if (!mTvInputInfo.getComponent().equals(name)) {
+ if (!mName.equals(name)) {
throw new IllegalArgumentException("Mismatched ComponentName: "
- + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
+ + mName + " (expected), " + name + " (actual).");
}
synchronized (mLock) {
UserState userState = getUserStateLocked(mUserId);
- ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
+ ServiceState serviceState = userState.serviceStateMap.get(mName);
if (serviceState != null) {
serviceState.mReconnecting = true;
serviceState.mBound = false;
@@ -1409,35 +1578,72 @@
if (sessionState.mSession == null) {
removeSessionStateLocked(sessionToken, sessionState.mUserId);
sendSessionTokenToClientLocked(sessionState.mClient,
- sessionState.mInputId, null, null, sessionState.mSeq);
+ sessionState.mInfo.getId(), null, null, sessionState.mSeq);
}
}
- notifyStateChangedLocked(userState, mTvInputInfo.getId(),
- INPUT_STATE_DISCONNECTED, null);
- updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
+ for (TvInputState inputState : userState.inputMap.values()) {
+ if (inputState.mInfo.getComponent().equals(name)) {
+ notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
+ INPUT_STATE_DISCONNECTED, null);
+ }
+ }
+ updateServiceConnectionLocked(mName, mUserId);
}
}
}
}
private final class ServiceCallback extends ITvInputServiceCallback.Stub {
- private final String mInputId;
+ private final ComponentName mName;
private final int mUserId;
- ServiceCallback(String inputId, int userId) {
- mInputId = inputId;
+ ServiceCallback(ComponentName name, int userId) {
+ mName = name;
mUserId = userId;
}
@Override
- public void onInputStateChanged(int state) {
- if (DEBUG) {
- Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
- + state + ")");
- }
+ public void addTvInput(TvInputInfo inputInfo) {
synchronized (mLock) {
- setStateLocked(mInputId, state, mUserId);
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "The caller does not have permission to add a TV input ("
+ + inputInfo + ").");
+ return;
+ }
+
+ ServiceState serviceState = getServiceStateLocked(mName, mUserId);
+ serviceState.mInputList.add(inputInfo);
+ buildTvInputListLocked(mUserId);
+ }
+ }
+
+ @Override
+ public void removeTvInput(String inputId) {
+ synchronized (mLock) {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "The caller does not have permission to remove a TV input ("
+ + inputId + ").");
+ return;
+ }
+
+ ServiceState serviceState = getServiceStateLocked(mName, mUserId);
+ boolean removed = false;
+ for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
+ it.hasNext(); ) {
+ if (it.next().getId().equals(inputId)) {
+ it.remove();
+ removed = true;
+ break;
+ }
+ }
+ if (removed) {
+ buildTvInputListLocked(mUserId);
+ } else {
+ Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
+ }
}
}
}
@@ -1586,11 +1792,58 @@
}
}
- final class Client {
- public void setState(String inputId, int state) {
+ final class HardwareListener implements TvInputHardwareManager.Listener {
+ @Override
+ public void onStateChanged(String inputId, int state) {
synchronized (mLock) {
setStateLocked(inputId, state, mCurrentUserId);
}
}
+
+ @Override
+ public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ // Broadcast the event to all hardware inputs.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+ try {
+ serviceState.mService.notifyHardwareAdded(info);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareAdded", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onHardwareDeviceRemoved(int deviceId) {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ // Broadcast the event to all hardware inputs.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (!serviceState.mIsHardware || serviceState.mService == null) continue;
+ try {
+ serviceState.mService.notifyHardwareRemoved(deviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHardwareRemoved", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice) {
+ synchronized (mLock) {
+ // TODO
+ }
+ }
+
+ @Override
+ public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice) {
+ synchronized (mLock) {
+ // TODO
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index e8ae97c..60724e7 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,10 +16,15 @@
package com.android.server.webkit;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Binder;
import android.os.Process;
import android.util.Log;
import android.webkit.IWebViewUpdateService;
+import android.webkit.WebViewFactory;
/**
* Private service to wait for the updatable WebView to be ready for use.
@@ -32,7 +37,22 @@
private boolean mRelroReady32Bit = false;
private boolean mRelroReady64Bit = false;
- public WebViewUpdateService() {
+ private BroadcastReceiver mWebViewUpdatedReceiver;
+
+ public WebViewUpdateService(Context context) {
+ mWebViewUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
+ if (webviewPackage.equals(intent.getDataString())) {
+ onWebViewUpdateInstalled();
+ }
+ }
+ };
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ context.registerReceiver(mWebViewUpdatedReceiver, filter);
}
/**
@@ -75,4 +95,14 @@
}
}
}
+
+ private void onWebViewUpdateInstalled() {
+ Log.d(TAG, "WebView Package updated!");
+
+ synchronized (this) {
+ mRelroReady32Bit = false;
+ mRelroReady64Bit = false;
+ }
+ WebViewFactory.prepareWebViewInSystemServer();
+ }
}
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
index 673a6e2..e842eeb 100644
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
@@ -21,9 +21,13 @@
#include <utils/misc.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
+#include <utils/Log.h>
+
#include <inttypes.h>
#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
namespace android {
@@ -40,7 +44,39 @@
return size;
}
- static jlong com_android_server_PeristentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) {
+ int wipe_block_device(int fd)
+ {
+ uint64_t range[2];
+ int ret;
+ uint64_t len = get_block_device_size(fd);
+
+ range[0] = 0;
+ range[1] = len;
+
+ if (range[1] == 0)
+ return 0;
+
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Discard failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+ return 0;
+ }
+
+ }
+
+ return ret;
+ }
+
+ static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
+ {
const char *path = env->GetStringUTFChars(jpath, 0);
int fd = open(path, O_RDONLY);
@@ -50,9 +86,20 @@
return get_block_device_size(fd);
}
+ static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
+ const char *path = env->GetStringUTFChars(jpath, 0);
+ int fd = open(path, O_WRONLY);
+
+ if (fd < 0)
+ return 0;
+
+ return wipe_block_device(fd);
+ }
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"getBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PeristentDataBlockService_getBlockDeviceSize},
+ {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
+ {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
};
int register_android_server_PersistentDataBlockService(JNIEnv* env)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a6030cf..de929cb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -72,6 +72,7 @@
import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
+import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;
@@ -411,7 +412,7 @@
SystemConfig.getInstance();
Slog.i(TAG, "WebView Update Service");
- ServiceManager.addService("webviewupdate", new WebViewUpdateService());
+ ServiceManager.addService("webviewupdate", new WebViewUpdateService(context));
Slog.i(TAG, "WebViewFactory preparation");
WebViewFactory.prepareWebViewInSystemServer();
@@ -949,6 +950,10 @@
mSystemServiceManager.startService(LauncherAppsService.class);
}
+ if (!disableNonCoreServices) {
+ mSystemServiceManager.startService(MediaProjectionManagerService.class);
+ }
+
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index bed85fc..27bec9f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -105,17 +105,18 @@
if (db.insertWithOnConflict(
SoundModelContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) {
for (Keyphrase keyphrase : soundModel.keyphrases) {
- status &= addOrUpdateKeyphrase(soundModel.uuid, keyphrase);
+ status &= addOrUpdateKeyphrase(db, soundModel.uuid, keyphrase);
}
+ db.close();
return status;
} else {
Slog.w(TAG, "Failed to persist sound model to database");
+ db.close();
return false;
}
}
- private boolean addOrUpdateKeyphrase(UUID modelId, Keyphrase keyphrase) {
- SQLiteDatabase db = getWritableDatabase();
+ private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) {
ContentValues values = new ContentValues();
values.put(KeyphraseContract.KEY_ID, keyphrase.id);
values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModeFlags);
@@ -148,6 +149,7 @@
Slog.w(TAG, "No keyphrases deleted from the database");
status = false;
}
+ db.close();
return status;
}
@@ -157,7 +159,7 @@
public List<KeyphraseSoundModel> getKephraseSoundModels() {
List<KeyphraseSoundModel> models = new ArrayList<>();
String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE;
- SQLiteDatabase db = this.getReadableDatabase();
+ SQLiteDatabase db = getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
@@ -172,17 +174,18 @@
byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
// Get all the keyphrases for this this sound model.
models.add(new KeyphraseSoundModel(
- UUID.fromString(id), data, getKeyphrasesForSoundModel(id)));
+ UUID.fromString(id), data, getKeyphrasesForSoundModel(db, id)));
} while (c.moveToNext());
}
+ c.close();
+ db.close();
return models;
}
- private Keyphrase[] getKeyphrasesForSoundModel(String modelId) {
+ private Keyphrase[] getKeyphrasesForSoundModel(SQLiteDatabase db, String modelId) {
List<Keyphrase> keyphrases = new ArrayList<>();
String selectQuery = "SELECT * FROM " + KeyphraseContract.TABLE
+ " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'";
- SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
@@ -199,6 +202,7 @@
}
Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()];
keyphrases.toArray(keyphraseArr);
+ c.close();
return keyphraseArr;
}
}
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 130364f..c050f30 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -37,6 +37,7 @@
public void onHandleChanged(Connection c, Uri newHandle, int presentation) {}
public void onCallerDisplayNameChanged(
Connection c, String callerDisplayName, int presentation) {}
+ public void onVideoStateChanged(Connection c, int videoState) {}
public void onSignalChanged(Connection c, Bundle details) {}
public void onDisconnected(Connection c, int cause, String message) {}
public void onPostDialWait(Connection c, String remaining) {}
@@ -75,6 +76,7 @@
private CallVideoProvider mCallVideoProvider;
private boolean mAudioModeIsVoip;
private StatusHints mStatusHints;
+ private int mVideoState;
/**
* Create a new Connection.
@@ -118,6 +120,19 @@
}
/**
+ * Returns the video state of the call.
+ * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
+ *
+ * @return The video state of the call.
+ */
+ public final int getVideoState() {
+ return mVideoState;
+ }
+
+ /**
* @return The audio state of the call, 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.
@@ -285,6 +300,23 @@
}
/**
+ * Set the video state for the connection.
+ * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
+ *
+ * @param videoState The new video state.
+ */
+ public final void setVideoState(int videoState) {
+ Log.d(this, "setVideoState %d", videoState);
+ mVideoState = videoState;
+ for (Listener l : mListeners) {
+ l.onVideoStateChanged(this, mVideoState);
+ }
+ }
+
+ /**
* Sets state to active (e.g., an ongoing call where two or more parties can actively
* communicate).
*/
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 5855470..9e04c6e 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -324,6 +324,13 @@
}
@Override
+ public void onVideoStateChanged(Connection c, int videoState) {
+ String id = mIdByConnection.get(c);
+ Log.d(this, "Adapter set video state %d", videoState);
+ mAdapter.setVideoState(id, videoState);
+ }
+
+ @Override
public void onHandleChanged(Connection c, Uri handle, int presentation) {
String id = mIdByConnection.get(c);
mAdapter.setHandle(id, handle, presentation);
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index a812fa4..63546d3 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -323,4 +323,25 @@
}
}
}
+
+ /**
+ * Sets the video state associated with a call.
+ *
+ * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
+ * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
+ *
+ * @param callId The unique ID of the call to set the video state for.
+ * @param videoState The video state.
+ */
+ void setVideoState(String callId, int videoState) {
+ Log.v(this, "setVideoState: %d", videoState);
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setVideoState(callId, videoState);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
}
diff --git a/telecomm/java/android/telecomm/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java
index b246d92..bb335c3 100644
--- a/telecomm/java/android/telecomm/PhoneAccount.java
+++ b/telecomm/java/android/telecomm/PhoneAccount.java
@@ -16,8 +16,10 @@
package android.telecomm;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import android.content.ComponentName;
-import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,55 +28,27 @@
/**
* Represents a distinct account, line of service or call placement method that
* the system can use to place phone calls.
+ *
+ * TODO: Per feedback from API Council, rename to "PhoneAccountHandle". See also comment on class
+ * PhoneAccountMetadata.
*/
public class PhoneAccount implements Parcelable {
-
/**
- * Flag indicating that this {@code PhoneAccount} can act as a call manager for traditional
- * SIM-based telephony calls. The {@link ConnectionService} associated with this phone-account
- * will be allowed to manage SIM-based phone calls including using its own proprietary
- * phone-call implementation (like VoIP calling) to make calls instead of the telephony stack.
- * When a user opts to place a call using the SIM-based telephony stack, the connection-service
- * associated with this phone-account will be attempted first if the user has explicitly
- * selected it to be used as the default call-manager.
+ * Flag indicating that this {@code PhoneAccount} represents built-in PSTN SIM subscription.
* <p>
- * See {@link #getCapabilities}
- */
- public static final int CAPABILITY_SIM_CALL_MANAGER = 0x1;
-
- /**
- * Flag indicating that this {@code PhoneAccount} can make phone calls in place of traditional
- * SIM-based telephony calls. This account will be treated as a distinct method for placing
- * calls alongside the traditional SIM-based telephony stack. This flag is distinct from
- * {@link #CAPABILITY_SIM_CALL_MANAGER} in that it is not allowed to manage calls from or use
- * the built-in telephony stack to place its calls.
- * <p>
- * See {@link #getCapabilities}
- */
- public static final int CAPABILITY_CALL_PROVIDER = 0x2;
-
- /**
- * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM subscription.
- * <p>
- * Only the android framework can set this capability on a phone-account.
+ * Only the android framework can set this capability on a phone account.
*/
public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
private ComponentName mComponentName;
private String mId;
- private Uri mHandle;
- private int mCapabilities;
public PhoneAccount(
ComponentName componentName,
- String id,
- Uri handle,
- int capabilities) {
+ String id) {
mComponentName = componentName;
mId = id;
- mHandle = handle;
- mCapabilities = capabilities;
}
/**
@@ -97,31 +71,9 @@
return mId;
}
- /**
- * The handle (e.g., a phone number) associated with this {@code PhoneAccount}. This represents
- * the destination from which outgoing calls using this {@code PhoneAccount} will appear to
- * come, if applicable, and the destination to which incoming calls using this
- * {@code PhoneAccount} may be addressed.
- *
- * @return A handle expressed as a {@code Uri}, for example, a phone number.
- */
- public Uri getHandle() {
- return mHandle;
- }
-
- /**
- * The capabilities of this {@code PhoneAccount}.
- *
- * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities.
- */
- public int getCapabilities() {
- return mCapabilities;
- }
-
@Override
public int hashCode() {
- return Objects.hashCode(mComponentName) + Objects.hashCode(mId) +
- Objects.hashCode(mHandle) + mCapabilities;
+ return Objects.hashCode(mComponentName) + Objects.hashCode(mId);
}
@Override
@@ -130,20 +82,16 @@
.append(", ")
.append(mId)
.append(", ")
- .append(Log.pii(mHandle))
.append(", ")
- .append(String.valueOf(mCapabilities))
.toString();
}
- /**
- * TODO: Change this to just be equals() and use Set<> in Telecomm code instead of Lists.
- * @hide
- */
- public boolean equalsComponentAndId(PhoneAccount other) {
+ @Override
+ public boolean equals(Object other) {
return other != null &&
- Objects.equals(other.getComponentName(), getComponentName()) &&
- Objects.equals(other.getId(), getId());
+ other instanceof PhoneAccount &&
+ Objects.equals(((PhoneAccount) other).getComponentName(), getComponentName()) &&
+ Objects.equals(((PhoneAccount) other).getId(), getId());
}
//
@@ -159,8 +107,6 @@
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(mComponentName, flags);
out.writeString(mId);
- out.writeString(mHandle != null ? mHandle.toString() : "");
- out.writeInt(mCapabilities);
}
public static final Creator<PhoneAccount> CREATOR = new Creator<PhoneAccount>() {
@@ -178,8 +124,5 @@
private PhoneAccount(Parcel in) {
mComponentName = in.readParcelable(getClass().getClassLoader());
mId = in.readString();
- String uriString = in.readString();
- mHandle = uriString.length() > 0 ? Uri.parse(uriString) : null;
- mCapabilities = in.readInt();
}
}
diff --git a/telecomm/java/android/telecomm/PhoneAccountMetadata.java b/telecomm/java/android/telecomm/PhoneAccountMetadata.java
index 20a4d47..e5e41ff 100644
--- a/telecomm/java/android/telecomm/PhoneAccountMetadata.java
+++ b/telecomm/java/android/telecomm/PhoneAccountMetadata.java
@@ -19,32 +19,67 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import java.io.IOException;
-import java.io.ObjectStreamException;
-import java.io.Serializable;
import java.util.MissingResourceException;
/**
* Provides user interface description information for a {@code PhoneAccount}.
+ *
+ * TODO: Per feedback from API Council, rename to "PhoneAccount". See also comment on class
+ * PhoneAccount.
*/
public class PhoneAccountMetadata implements Parcelable {
- private PhoneAccount mAccount;
- private int mIconResId;
- private String mLabel;
- private String mShortDescription;
+
+ /**
+ * Flag indicating that this {@code PhoneAccount} can act as a call manager for traditional
+ * SIM-based telephony calls. The {@link ConnectionService} associated with this phone-account
+ * will be allowed to manage SIM-based phone calls including using its own proprietary
+ * phone-call implementation (like VoIP calling) to make calls instead of the telephony stack.
+ * When a user opts to place a call using the SIM-based telephony stack, the connection-service
+ * associated with this phone-account will be attempted first if the user has explicitly
+ * selected it to be used as the default call-manager.
+ * <p>
+ * See {@link #getCapabilities}
+ */
+ public static final int CAPABILITY_SIM_CALL_MANAGER = 0x1;
+
+ /**
+ * Flag indicating that this {@code PhoneAccount} can make phone calls in place of traditional
+ * SIM-based telephony calls. This account will be treated as a distinct method for placing
+ * calls alongside the traditional SIM-based telephony stack. This flag is distinct from
+ * {@link #CAPABILITY_SIM_CALL_MANAGER} in that it is not allowed to manage calls from or use
+ * the built-in telephony stack to place its calls.
+ * <p>
+ * See {@link #getCapabilities}
+ */
+ public static final int CAPABILITY_CALL_PROVIDER = 0x2;
+
+ private final PhoneAccount mAccount;
+ private final Uri mHandle;
+ private final int mCapabilities;
+ private final int mIconResId;
+ private final String mLabel;
+ private final String mShortDescription;
+ private boolean mVideoCallingSupported;
public PhoneAccountMetadata(
PhoneAccount account,
+ Uri handle,
+ int capabilities,
int iconResId,
String label,
- String shortDescription) {
+ String shortDescription,
+ boolean supportsVideoCalling) {
mAccount = account;
+ mHandle = handle;
+ mCapabilities = capabilities;
mIconResId = iconResId;
mLabel = label;
mShortDescription = shortDescription;
+ mVideoCallingSupported = supportsVideoCalling;
}
/**
@@ -57,6 +92,27 @@
}
/**
+ * The handle (e.g., a phone number) associated with this {@code PhoneAccount}. This represents
+ * the destination from which outgoing calls using this {@code PhoneAccount} will appear to
+ * come, if applicable, and the destination to which incoming calls using this
+ * {@code PhoneAccount} may be addressed.
+ *
+ * @return A handle expressed as a {@code Uri}, for example, a phone number.
+ */
+ public Uri getHandle() {
+ return mHandle;
+ }
+
+ /**
+ * The capabilities of this {@code PhoneAccount}.
+ *
+ * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities.
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
* A short string label describing a {@code PhoneAccount}.
*
* @return A label for this {@code PhoneAccount}.
@@ -75,6 +131,15 @@
}
/**
+ * The icon resource ID for the icon of this {@code PhoneAccount}.
+ *
+ * @return A resource ID.
+ */
+ public int getIconResId() {
+ return mIconResId;
+ }
+
+ /**
* An icon to represent this {@code PhoneAccount} in a user interface.
*
* @return An icon for this {@code PhoneAccount}.
@@ -101,6 +166,15 @@
}
}
+ /**
+ * Determines whether this {@code PhoneAccount} supports video calling.
+ *
+ * @return {@code true} if this {@code PhoneAccount} supports video calling.
+ */
+ public boolean isVideoCallingSupported() {
+ return mVideoCallingSupported;
+ }
+
//
// Parcelable implementation
//
@@ -113,9 +187,12 @@
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(mAccount, 0);
+ out.writeParcelable(mHandle, 0);
+ out.writeInt(mCapabilities);
out.writeInt(mIconResId);
out.writeString(mLabel);
out.writeString(mShortDescription);
+ out.writeInt(mVideoCallingSupported ? 1 : 0);
}
public static final Creator<PhoneAccountMetadata> CREATOR
@@ -133,8 +210,11 @@
private PhoneAccountMetadata(Parcel in) {
mAccount = in.readParcelable(getClass().getClassLoader());
+ mHandle = in.readParcelable(getClass().getClassLoader());
+ mCapabilities = in.readInt();
mIconResId = in.readInt();
mLabel = in.readString();
mShortDescription = in.readString();
+ mVideoCallingSupported = in.readInt() == 1;
}
}
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index 5d8579e..d17e62a 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -42,6 +42,7 @@
void onHandleChanged(RemoteConnection connection, Uri handle, int presentation);
void onCallerDisplayNameChanged(
RemoteConnection connection, String callerDisplayName, int presentation);
+ void onVideoStateChanged(RemoteConnection connection, int videoState);
void onDestroyed(RemoteConnection connection);
}
@@ -55,6 +56,7 @@
private boolean mRequestingRingback;
private boolean mConnected;
private int mCallCapabilities;
+ private int mVideoState;
private boolean mAudioModeIsVoip;
private StatusHints mStatusHints;
private Uri mHandle;
@@ -120,6 +122,10 @@
return mCallerDisplayNamePresentation;
}
+ public int getVideoState() {
+ return mVideoState;
+ }
+
public void abort() {
try {
if (mConnected) {
@@ -297,6 +303,16 @@
}
}
+ /**
+ * @hide
+ */
+ void setVideoState(int videoState) {
+ mVideoState = videoState;
+ for (Listener l : mListeners) {
+ l.onVideoStateChanged(this, videoState);
+ }
+ }
+
/** @hide */
void setAudioModeIsVoip(boolean isVoip) {
mAudioModeIsVoip = isVoip;
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index 7fd8f93..c2b574c 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -167,6 +167,13 @@
}
@Override
+ public void setVideoState(String connectionId, int videoState) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setVideoState(videoState);
+ }
+ }
+
+ @Override
public final void setAudioModeIsVoip(String connectionId, boolean isVoip) {
if (isCurrentConnection(connectionId)) {
mConnection.setAudioModeIsVoip(isVoip);
@@ -253,9 +260,7 @@
List<PhoneAccount> accounts = new LinkedList<>();
accounts.add(new PhoneAccount(
mComponentName,
- null /* id */,
- null /* handle */,
- 0 /* capabilities */));
+ null /* id */));
return accounts;
}
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index 8bf80bb..89fcdb5 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -56,6 +56,34 @@
}
/**
+ * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing
+ * phone calls. This {@code PhoneAccount} will always be a member of the list which is
+ * returned from calling {@link #getEnabledPhoneAccounts()}.
+ * <p>
+ * Apps must be prepared for this method to return {@code null}, indicating that there
+ * currently exists no user-chosen default {@code PhoneAccount}. In this case, apps wishing to
+ * initiate a phone call must either create their {@link android.content.Intent#ACTION_CALL} or
+ * {@link android.content.Intent#ACTION_DIAL} {@code Intent} with no
+ * {@link TelecommConstants#EXTRA_PHONE_ACCOUNT}, or present the user with an affordance
+ * to select one of the elements of {@link #getEnabledPhoneAccounts()}.
+ * <p>
+ * An {@link android.content.Intent#ACTION_CALL} or {@link android.content.Intent#ACTION_DIAL}
+ * {@code Intent} with no {@link TelecommConstants#EXTRA_PHONE_ACCOUNT} is valid, and subsequent
+ * steps in the phone call flow are responsible for presenting the user with an affordance, if
+ * necessary, to choose a {@code PhoneAccount}.
+ */
+ public PhoneAccount getDefaultOutgoingPhoneAccount() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getDefaultOutgoingPhoneAccount();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#getDefaultOutgoingPhoneAccount", e);
+ }
+ return null;
+ }
+
+ /**
* Return a list of {@link PhoneAccount}s which can be used to make and receive phone calls.
*
* @see #EXTRA_PHONE_ACCOUNT
@@ -94,13 +122,12 @@
/**
* Register a {@link PhoneAccount} for use by the system.
*
- * @param account The {@link PhoneAccount}.
- * @param metadata The metadata for the account.
+ * @param metadata The complete {@link PhoneAccountMetadata}.
*/
- public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
+ public void registerPhoneAccount(PhoneAccountMetadata metadata) {
try {
if (isServiceConnected()) {
- getTelecommService().registerPhoneAccount(account, metadata);
+ getTelecommService().registerPhoneAccount(metadata);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecommService#registerPhoneAccount", e);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index b36f72c..e12cfca 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -64,6 +64,8 @@
void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider);
+ void setVideoState(String callId, int videoState);
+
void setAudioModeIsVoip(String callId, boolean isVoip);
void setStatusHints(String callId, in StatusHints statusHints);
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 43caa1e..59393ed 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -34,6 +34,11 @@
void showCallScreen(boolean showDialpad);
/**
+ * @see TelecommManager#getDefaultOutgoingPhoneAccount
+ */
+ PhoneAccount getDefaultOutgoingPhoneAccount();
+
+ /**
* @see TelecommManager#getEnabledPhoneAccounts
*/
List<PhoneAccount> getEnabledPhoneAccounts();
@@ -46,7 +51,7 @@
/**
* @see TelecommManager#registerPhoneAccount
*/
- void registerPhoneAccount(in PhoneAccount account, in PhoneAccountMetadata metadata);
+ void registerPhoneAccount(in PhoneAccountMetadata metadata);
/**
* @see TelecommManager#unregisterPhoneAccount
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 208f467..6896f4d 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telecomm.VideoCallProfile;
/**
* Parcelable object to handle IMS call profile.
@@ -286,4 +287,63 @@
return new ImsCallProfile[size];
}
};
+
+ /**
+ * Converts from the call types defined in {@link com.android.ims.ImsCallProfile} to the
+ * video state values defined in {@link android.telecomm.VideoCallProfile}.
+ *
+ * @param callType The call type.
+ * @return The video state.
+ */
+ public static int getVideoStateFromCallType(int callType) {
+ switch (callType) {
+ case CALL_TYPE_VT_NODIR:
+ return VideoCallProfile.VIDEO_STATE_PAUSED |
+ VideoCallProfile.VIDEO_STATE_BIDIRECTIONAL;
+ case CALL_TYPE_VT_TX:
+ return VideoCallProfile.VIDEO_STATE_TX_ENABLED;
+ case CALL_TYPE_VT_RX:
+ return VideoCallProfile.VIDEO_STATE_RX_ENABLED;
+ case CALL_TYPE_VT:
+ return VideoCallProfile.VIDEO_STATE_BIDIRECTIONAL;
+ case CALL_TYPE_VOICE:
+ return VideoCallProfile.VIDEO_STATE_AUDIO_ONLY;
+ default:
+ return VideoCallProfile.VIDEO_STATE_AUDIO_ONLY;
+ }
+ }
+
+ /**
+ * Converts from the video state values defined in {@link android.telecomm.VideoCallProfile}
+ * to the call types defined in {@link ImsCallProfile}.
+ *
+ * @param videoState The video state.
+ * @return The call type.
+ */
+ public static int getCallTypeFromVideoState(int videoState) {
+ boolean videoTx = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_TX_ENABLED);
+ boolean videoRx = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_RX_ENABLED);
+ boolean isPaused = isVideoStateSet(videoState, VideoCallProfile.VIDEO_STATE_PAUSED);
+ if (isPaused) {
+ return ImsCallProfile.CALL_TYPE_VT_NODIR;
+ } else if (videoTx && !videoRx) {
+ return ImsCallProfile.CALL_TYPE_VT_TX;
+ } else if (!videoTx && videoRx) {
+ return ImsCallProfile.CALL_TYPE_VT_RX;
+ } else if (videoTx && videoRx) {
+ return ImsCallProfile.CALL_TYPE_VT;
+ }
+ return ImsCallProfile.CALL_TYPE_VOICE;
+ }
+
+ /**
+ * Determines if a video state is set in a video state bit-mask.
+ *
+ * @param videoState The video state bit mask.
+ * @param videoStateToCheck The particular video state to check.
+ * @return True if the video state is set in the bit-mask.
+ */
+ private static boolean isVideoStateSet(int videoState, int videoStateToCheck) {
+ return (videoState & videoStateToCheck) == videoStateToCheck;
+ }
}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index ab2e8ac..edb28ea 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -18,6 +18,8 @@
import android.content.Intent;
import android.os.Bundle;
+import android.service.voice.AlwaysOnHotwordDetector;
+import android.service.voice.AlwaysOnHotwordDetector.Callback;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
@@ -26,6 +28,25 @@
public class MainInteractionService extends VoiceInteractionService {
static final String TAG = "MainInteractionService";
+ private final Callback mHotwordCallback = new Callback() {
+ @Override
+ public void onDetected(byte[] data) {
+ Log.i(TAG, "onDetected");
+ }
+
+ @Override
+ public void onDetectionStarted() {
+ Log.i(TAG, "onDetectionStarted");
+ }
+
+ @Override
+ public void onDetectionStopped() {
+ Log.i(TAG, "onDetectionStopped");
+ }
+ };
+
+ private AlwaysOnHotwordDetector mHotwordDetector;
+
@Override
public void onReady() {
super.onReady();
@@ -33,6 +54,31 @@
Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError());
Log.i(TAG, "Keyphrase enrollment meta-data: "
+ Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata()));
+
+ mHotwordDetector = getAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback);
+ int availability = mHotwordDetector.getAvailability();
+ Log.i(TAG, "Hotword availability = " + availability);
+
+ switch (availability) {
+ case AlwaysOnHotwordDetector.KEYPHRASE_HARDWARE_UNAVAILABLE:
+ Log.i(TAG, "KEYPHRASE_HARDWARE_UNAVAILABLE");
+ break;
+ case AlwaysOnHotwordDetector.KEYPHRASE_UNSUPPORTED:
+ Log.i(TAG, "KEYPHRASE_UNSUPPORTED");
+ break;
+ case AlwaysOnHotwordDetector.KEYPHRASE_UNENROLLED:
+ Log.i(TAG, "KEYPHRASE_UNENROLLED");
+ Intent enroll = mHotwordDetector.getManageIntent(
+ AlwaysOnHotwordDetector.MANAGE_ACTION_ENROLL);
+ Log.i(TAG, "Need to enroll with " + enroll);
+ break;
+ case AlwaysOnHotwordDetector.KEYPHRASE_ENROLLED:
+ Log.i(TAG, "KEYPHRASE_ENROLLED");
+ int status = mHotwordDetector.startRecognition(
+ AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE);
+ Log.i(TAG, "startRecognition status = " + status);
+ break;
+ }
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
index a953918..93814b2 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java
@@ -35,7 +35,7 @@
// Note that AssetManager() creates a system AssetManager and we override it
// with our BridgeAssetManager.
AssetManager.sSystem = new BridgeAssetManager();
- AssetManager.sSystem.makeStringBlocks(false);
+ AssetManager.sSystem.makeStringBlocks(null);
}
return AssetManager.sSystem;
}