diff options
225 files changed, 8728 insertions, 3103 deletions
diff --git a/api/current.txt b/api/current.txt index 8230785..c5fc314 100644 --- a/api/current.txt +++ b/api/current.txt @@ -244,6 +244,7 @@ package android { field public static final int action = 16842797; // 0x101002d field public static final int actionBarDivider = 16843675; // 0x101039b field public static final int actionBarItemBackground = 16843676; // 0x101039c + field public static final int actionBarPopupTheme = 16843919; // 0x101048f field public static final int actionBarSize = 16843499; // 0x10102eb field public static final int actionBarSplitStyle = 16843656; // 0x1010388 field public static final int actionBarStyle = 16843470; // 0x10102ce @@ -852,7 +853,7 @@ package android { field public static final int mirrorForRtl = 16843726; // 0x10103ce field public static final int mode = 16843134; // 0x101017e field public static final int moreIcon = 16843061; // 0x1010135 - field public static final int multiArch = 16843918; // 0x101048e + field public static final int multiArch = 16843920; // 0x1010490 field public static final int multiprocess = 16842771; // 0x1010013 field public static final int name = 16842755; // 0x1010003 field public static final int navigationBarColor = 16843860; // 0x1010454 @@ -918,6 +919,7 @@ package android { field public static final int popupAnimationStyle = 16843465; // 0x10102c9 field public static final int popupBackground = 16843126; // 0x1010176 field public static final int popupCharacters = 16843332; // 0x1010244 + field public static final int popupElevation = 16843918; // 0x101048e field public static final int popupKeyboard = 16843331; // 0x1010243 field public static final int popupLayout = 16843323; // 0x101023b field public static final int popupMenuStyle = 16843520; // 0x1010300 @@ -1281,6 +1283,7 @@ package android { field public static final int topLeftRadius = 16843177; // 0x10101a9 field public static final int topOffset = 16843352; // 0x1010258 field public static final int topRightRadius = 16843178; // 0x10101aa + field public static final int touchscreenBlocksFocus = 16843921; // 0x1010491 field public static final int track = 16843631; // 0x101036f field public static final int transcriptMode = 16843008; // 0x1010100 field public static final int transformPivotX = 16843552; // 0x1010320 @@ -2601,6 +2604,7 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_BACK = 1; // 0x1 field public static final int GLOBAL_ACTION_HOME = 2; // 0x2 field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4 + field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6 field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5 field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final java.lang.String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; @@ -3157,6 +3161,7 @@ package android.app { method public abstract deprecated void addTab(android.app.ActionBar.Tab, int, boolean); method public abstract android.view.View getCustomView(); method public abstract int getDisplayOptions(); + method public float getElevation(); method public abstract int getHeight(); method public int getHideOffset(); method public abstract deprecated int getNavigationItemCount(); @@ -3188,6 +3193,7 @@ package android.app { method public abstract void setDisplayShowHomeEnabled(boolean); method public abstract void setDisplayShowTitleEnabled(boolean); method public abstract void setDisplayUseLogoEnabled(boolean); + method public void setElevation(float); method public void setHideOffset(int); method public void setHideOnContentScrollEnabled(boolean); method public void setHomeActionContentDescription(java.lang.CharSequence); @@ -5342,6 +5348,7 @@ package android.app.admin { method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName); method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public int setStorageEncryption(android.content.ComponentName, boolean); + method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void uninstallCaCert(android.content.ComponentName, byte[]); method public void wipeData(int); field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN"; @@ -5490,6 +5497,7 @@ package android.app.job { method public int getNetworkCapabilities(); method public android.content.ComponentName getService(); method public boolean isPeriodic(); + method public boolean isPersisted(); method public boolean isRequireCharging(); method public boolean isRequireDeviceIdle(); method public void writeToParcel(android.os.Parcel, int); @@ -5506,6 +5514,7 @@ package android.app.job { method public android.app.job.JobInfo build(); method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int); method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle); + method public android.app.job.JobInfo.Builder setIsPersisted(boolean); method public android.app.job.JobInfo.Builder setMinimumLatency(long); method public android.app.job.JobInfo.Builder setOverrideDeadline(long); method public android.app.job.JobInfo.Builder setPeriodic(long); @@ -8358,12 +8367,10 @@ package android.content.pm { public class LauncherApps { method public void addOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); - method public void addOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public boolean isActivityEnabledForProfile(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabledForProfile(java.lang.String, android.os.UserHandle); method public void removeOnAppsChangedCallback(android.content.pm.LauncherApps.OnAppsChangedCallback); - method public void removeOnAppsChangedListener(android.content.pm.LauncherApps.OnAppsChangedListener); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startActivityForProfile(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); } @@ -8377,14 +8384,6 @@ package android.content.pm { method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); } - public static abstract interface LauncherApps.OnAppsChangedListener { - method public abstract void onPackageAdded(android.os.UserHandle, java.lang.String); - method public abstract void onPackageChanged(android.os.UserHandle, java.lang.String); - method public abstract void onPackageRemoved(android.os.UserHandle, java.lang.String); - method public abstract void onPackagesAvailable(android.os.UserHandle, java.lang.String[], boolean); - method public abstract void onPackagesUnavailable(android.os.UserHandle, java.lang.String[], boolean); - } - public class PackageInfo implements android.os.Parcelable { ctor public PackageInfo(); method public int describeContents(); @@ -12831,7 +12830,15 @@ package android.hardware.camera2 { public final class DngCreator implements java.lang.AutoCloseable { ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult); method public void close(); + method public android.hardware.camera2.DngCreator setDescription(java.lang.String); + method public android.hardware.camera2.DngCreator setLocation(android.location.Location); + method public android.hardware.camera2.DngCreator setOrientation(int); + method public android.hardware.camera2.DngCreator setThumbnail(android.graphics.Bitmap); + method public android.hardware.camera2.DngCreator setThumbnail(android.media.Image); + method public void writeByteBuffer(java.io.OutputStream, android.util.Size, java.nio.ByteBuffer, long) throws java.io.IOException; method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException; + method public void writeInputStream(java.io.OutputStream, android.util.Size, java.io.InputStream, long) throws java.io.IOException; + field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult { @@ -12842,6 +12849,12 @@ package android.hardware.camera2 { package android.hardware.camera2.params { + public final class BlackLevelPattern { + method public void copyTo(int[], int); + method public int getOffsetForIndex(int, int); + field public static final int COUNT = 4; // 0x4 + } + public final class ColorSpaceTransform { ctor public ColorSpaceTransform(android.util.Rational[]); ctor public ColorSpaceTransform(int[]); @@ -12998,12 +13011,14 @@ package android.hardware.location { field public static final int GEOFENCE_ENTERED = 1; // 0x1 field public static final int GEOFENCE_ERROR_ID_EXISTS = 2; // 0x2 field public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3; // 0x3 + field public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; // 0x6 field public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4; // 0x4 field public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1; // 0x1 field public static final int GEOFENCE_EXITED = 2; // 0x2 field public static final int GEOFENCE_FAILURE = 5; // 0x5 field public static final int GEOFENCE_SUCCESS = 0; // 0x0 field public static final int GEOFENCE_UNCERTAIN = 4; // 0x4 + field public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; // 0x1 field public static final int MONITORING_TYPE_GPS_HARDWARE = 0; // 0x0 field public static final int MONITOR_CURRENTLY_AVAILABLE = 0; // 0x0 field public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1; // 0x1 @@ -13309,7 +13324,6 @@ package android.inputmethodservice { method public void setBackDisposition(int); method public void setCandidatesView(android.view.View); method public void setCandidatesViewShown(boolean); - method public void setCursorAnchorMonitorMode(int); method public void setExtractView(android.view.View); method public void setExtractViewShown(boolean); method public void setInputView(android.view.View); @@ -13321,8 +13335,6 @@ package android.inputmethodservice { field public static final int BACK_DISPOSITION_DEFAULT = 0; // 0x0 field public static final int BACK_DISPOSITION_WILL_DISMISS = 2; // 0x2 field public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1; // 0x1 - field public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 1; // 0x1 - field public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0; // 0x0 } public class InputMethodService.InputMethodImpl extends android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl { @@ -16125,6 +16137,7 @@ package android.media.tv { public abstract class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(); method public void dispatchChannelRetuned(android.net.Uri); + method public void dispatchTrackInfoChanged(java.util.List<android.media.tv.TvTrackInfo>); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); method public boolean onKeyDown(int, android.view.KeyEvent); @@ -16132,26 +16145,61 @@ package android.media.tv { method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); method public abstract void onRelease(); + method public boolean onSelectTrack(android.media.tv.TvTrackInfo); method public abstract void onSetStreamVolume(float); method public abstract boolean onSetSurface(android.view.Surface); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); + method public boolean onUnselectTrack(android.media.tv.TvTrackInfo); method public void setOverlayViewEnabled(boolean); } + public final class TvTrackInfo implements android.os.Parcelable { + method public boolean containsKey(java.lang.String); + method public int describeContents(); + method public boolean getBoolean(java.lang.String); + method public int getInt(java.lang.String); + method public java.lang.String getString(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String KEY_CHANNEL_COUNT = "channel-count"; + field public static final java.lang.String KEY_HEIGHT = "height"; + field public static final java.lang.String KEY_IS_SELECTED = "is-selected"; + field public static final java.lang.String KEY_LANGUAGE = "language"; + field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; + field public static final java.lang.String KEY_TAG = "tag"; + field public static final java.lang.String KEY_TYPE = "type"; + field public static final java.lang.String KEY_WIDTH = "width"; + field public static final int VALUE_TYPE_AUDIO = 0; // 0x0 + field public static final int VALUE_TYPE_SUBTITLE = 2; // 0x2 + field public static final int VALUE_TYPE_VIDEO = 1; // 0x1 + } + + public static final class TvTrackInfo.Builder { + ctor public TvTrackInfo.Builder(int, java.lang.String, boolean); + ctor public TvTrackInfo.Builder(android.media.tv.TvTrackInfo); + method public android.media.tv.TvTrackInfo build(); + method public android.media.tv.TvTrackInfo.Builder putBoolean(java.lang.String, boolean); + method public android.media.tv.TvTrackInfo.Builder putInt(java.lang.String, int); + method public android.media.tv.TvTrackInfo.Builder putString(java.lang.String, java.lang.String); + } + public class TvView extends android.view.ViewGroup { ctor public TvView(android.content.Context); ctor public TvView(android.content.Context, android.util.AttributeSet); ctor public TvView(android.content.Context, android.util.AttributeSet, int); method public boolean dispatchUnhandledInputEvent(android.view.InputEvent); + method public java.util.List<android.media.tv.TvTrackInfo> getTracks(); method protected void onLayout(boolean, int, int, int, int); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void reset(); + method public void selectTrack(android.media.tv.TvTrackInfo); method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener); method public void setStreamVolume(float); method public void setTvInputListener(android.media.tv.TvView.TvInputListener); method public void tune(java.lang.String, android.net.Uri); + method public void unselectTrack(android.media.tv.TvTrackInfo); field public static final int ERROR_BUSY = 0; // 0x0 field public static final int ERROR_TV_INPUT_DISCONNECTED = 1; // 0x1 } @@ -16164,6 +16212,8 @@ package android.media.tv { ctor public TvView.TvInputListener(); method public void onChannelRetuned(java.lang.String, android.net.Uri); 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 onVideoSizeChanged(java.lang.String, int, int); } } @@ -16300,10 +16350,12 @@ package android.net { public class ConnectivityManager { method public android.net.NetworkInfo getActiveNetworkInfo(); method public android.net.NetworkInfo[] getAllNetworkInfo(); + method public android.net.Network[] getAllNetworks(); method public deprecated boolean getBackgroundDataSetting(); method public android.net.LinkProperties getLinkProperties(android.net.Network); method public android.net.NetworkCapabilities getNetworkCapabilities(android.net.Network); method public android.net.NetworkInfo getNetworkInfo(int); + method public android.net.NetworkInfo getNetworkInfo(android.net.Network); method public deprecated int getNetworkPreference(); method public static android.net.Network getProcessDefaultNetwork(); method public boolean isActiveNetworkMetered(); @@ -16507,6 +16559,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe @@ -16515,6 +16568,7 @@ package android.net { field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 field public static final int TRANSPORT_CELLULAR = 0; // 0x0 field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_VPN = 4; // 0x4 field public static final int TRANSPORT_WIFI = 1; // 0x1 } @@ -16578,6 +16632,7 @@ package android.net { method public android.net.NetworkRequest build(); method public android.net.NetworkRequest.Builder removeCapability(int); method public android.net.NetworkRequest.Builder removeTransportType(int); + method public android.net.NetworkRequest.Builder setNetworkSpecifier(java.lang.String); } public abstract interface PSKKeyManager { @@ -16824,12 +16879,14 @@ package android.net { public class VpnService extends android.app.Service { ctor public VpnService(); + method public boolean addAddress(java.net.InetAddress, int); method public android.os.IBinder onBind(android.content.Intent); method public void onRevoke(); method public static android.content.Intent prepare(android.content.Context); method public boolean protect(int); method public boolean protect(java.net.Socket); method public boolean protect(java.net.DatagramSocket); + method public boolean removeAddress(java.net.InetAddress, int); field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService"; } @@ -16837,11 +16894,15 @@ package android.net { ctor public VpnService.Builder(); method public android.net.VpnService.Builder addAddress(java.net.InetAddress, int); method public android.net.VpnService.Builder addAddress(java.lang.String, int); + method public android.net.VpnService.Builder addAllowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public android.net.VpnService.Builder addDisallowedApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public android.net.VpnService.Builder addDnsServer(java.net.InetAddress); method public android.net.VpnService.Builder addDnsServer(java.lang.String); method public android.net.VpnService.Builder addRoute(java.net.InetAddress, int); method public android.net.VpnService.Builder addRoute(java.lang.String, int); method public android.net.VpnService.Builder addSearchDomain(java.lang.String); + method public android.net.VpnService.Builder allowBypass(); + method public android.net.VpnService.Builder allowFamily(int); method public android.os.ParcelFileDescriptor establish(); method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent); method public android.net.VpnService.Builder setMtu(int); @@ -22964,9 +23025,14 @@ package android.provider { field public static final android.net.Uri CONTENT_URI; field public static final android.net.Uri CONTENT_URI_WITH_VOICEMAIL; field public static final java.lang.String COUNTRY_ISO = "countryiso"; + field public static final java.lang.String DATA_USAGE = "data_usage"; field public static final java.lang.String DATE = "date"; field public static final java.lang.String DEFAULT_SORT_ORDER = "date DESC"; field public static final java.lang.String DURATION = "duration"; + field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + field public static final java.lang.String FEATURES = "features"; + field public static final int FEATURES_NONE = 0; // 0x0 + field public static final int FEATURES_VIDEO = 1; // 0x1 field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; @@ -23588,6 +23654,7 @@ package android.provider { public static class ContactsContract.Contacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns { method public static android.net.Uri getLookupUri(android.content.ContentResolver, android.net.Uri); method public static android.net.Uri getLookupUri(long, java.lang.String); + method public static boolean isCorpContactId(long); method public static android.net.Uri lookupContact(android.content.ContentResolver, android.net.Uri); method public static deprecated void markAsContacted(android.content.ContentResolver, long); method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean); @@ -26321,16 +26388,17 @@ package android.service.notification { } public static class NotificationListenerService.Ranking { + ctor public NotificationListenerService.Ranking(); method public java.lang.String getKey(); method public int getRank(); method public boolean isAmbient(); - method public boolean isInterceptedByDoNotDisturb(); + method public boolean meetsInterruptionFilter(); } public static class NotificationListenerService.RankingMap implements android.os.Parcelable { method public int describeContents(); method public java.lang.String[] getOrderedKeys(); - method public android.service.notification.NotificationListenerService.Ranking getRanking(java.lang.String); + method public boolean getRanking(java.lang.String, android.service.notification.NotificationListenerService.Ranking); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -26395,8 +26463,37 @@ package android.service.trust { package android.service.voice { + public class AlwaysOnHotwordDetector { + method public int getAvailability(); + method public android.content.Intent getManageIntent(int); + method public int getRecognitionStatus(); + method public int startRecognition(); + method public int stopRecognition(); + field public static final int KEYPHRASE_ENROLLED = 2; // 0x2 + field public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe + field public static final int KEYPHRASE_UNENROLLED = 1; // 0x1 + field public static final int KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff + field public static final int MANAGE_ACTION_ENROLL = 0; // 0x0 + field public static final int MANAGE_ACTION_RE_ENROLL = 1; // 0x1 + field public static final int MANAGE_ACTION_UN_ENROLL = 2; // 0x2 + field public static final int RECOGNITION_ACTIVE = 2; // 0x2 + field public static final int RECOGNITION_DISABLED_TEMPORARILY = -1; // 0xffffffff + field public static final int RECOGNITION_NOT_AVAILABLE = -3; // 0xfffffffd + field public static final int RECOGNITION_NOT_REQUESTED = -2; // 0xfffffffe + field public static final int RECOGNITION_REQUESTED = 1; // 0x1 + field public static final int STATUS_ERROR = -2147483648; // 0x80000000 + field public static final int STATUS_OK = 1; // 0x1 + } + + public static abstract interface AlwaysOnHotwordDetector.Callback { + method public abstract void onDetected(); + method public abstract void onDetectionStarted(); + method public abstract void onDetectionStopped(); + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); + method public final android.service.voice.AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback); method public android.os.IBinder onBind(android.content.Intent); method public void startSession(android.os.Bundle); field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; @@ -27449,6 +27546,8 @@ package android.telecomm { field public static final int MERGE_CALLS = 4; // 0x4 field public static final int MUTE = 64; // 0x40 field public static final int RESPOND_VIA_TEXT = 32; // 0x20 + field public static final int SUPPORTS_VT_LOCAL = 256; // 0x100 + field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200 field public static final int SUPPORT_HOLD = 2; // 0x2 field public static final int SWAP_CALLS = 8; // 0x8 } @@ -27547,6 +27646,7 @@ package android.telecomm { method public final boolean getAudioModeIsVoip(); method public final android.telecomm.CallAudioState getCallAudioState(); method public final int getCallCapabilities(); + method public final android.telecomm.CallVideoProvider getCallVideoProvider(); method public final java.util.List<android.telecomm.Connection> getChildConnections(); method public final int getFeatures(); method public final android.net.Uri getHandle(); @@ -29400,6 +29500,7 @@ package android.text { ctor public SpannableStringBuilder(java.lang.CharSequence); ctor public SpannableStringBuilder(java.lang.CharSequence, int, int); method public android.text.SpannableStringBuilder append(java.lang.CharSequence); + method public android.text.SpannableStringBuilder append(java.lang.CharSequence, java.lang.Object, int); method public android.text.SpannableStringBuilder append(java.lang.CharSequence, int, int); method public android.text.SpannableStringBuilder append(char); method public char charAt(int); @@ -33513,6 +33614,7 @@ package android.view { method public android.animation.LayoutTransition getLayoutTransition(); method public int getNestedScrollAxes(); method public int getPersistentDrawingCache(); + method public boolean getTouchscreenBlocksFocus(); method public int indexOfChild(android.view.View); method public final void invalidateChild(android.view.View, android.graphics.Rect); method public android.view.ViewParent invalidateChildInParent(int[], android.graphics.Rect); @@ -33572,6 +33674,7 @@ package android.view { method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener); method public void setPersistentDrawingCache(int); method protected void setStaticTransformationsEnabled(boolean); + method public void setTouchscreenBlocksFocus(boolean); method public void setTransitionGroup(boolean); method public boolean shouldDelayChildPressedState(); method public boolean showContextMenuForChild(android.view.View); @@ -34870,6 +34973,7 @@ package android.view.inputmethod { method public boolean performPrivateCommand(java.lang.String, android.os.Bundle); method public static final void removeComposingSpans(android.text.Spannable); method public boolean reportFullscreenMode(boolean); + method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); method public static void setComposingSpans(android.text.Spannable); @@ -34935,6 +35039,25 @@ package android.view.inputmethod { method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int); } + public final class CursorAnchorInfoRequest implements android.os.Parcelable { + ctor public CursorAnchorInfoRequest(int, int); + ctor public CursorAnchorInfoRequest(android.os.Parcel); + method public int describeContents(); + method public int getRequestFlags(); + method public int getRequestType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 2; // 0x2 + field public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 1; // 0x1 + field public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 2; // 0x2 + field public static final int FLAG_CURSOR_RECT_MONITOR = 1; // 0x1 + field public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 4; // 0x4 + field public static final int RESULT_NOT_HANDLED = 0; // 0x0 + field public static final int RESULT_SCHEDULED = 1; // 0x1 + field public static final int TYPE_CURSOR_ANCHOR_INFO = 1; // 0x1 + field public static final int TYPE_CURSOR_RECT = 2; // 0x2 + } + public class EditorInfo implements android.text.InputType android.os.Parcelable { ctor public EditorInfo(); method public int describeContents(); @@ -35032,6 +35155,7 @@ package android.view.inputmethod { method public abstract boolean performEditorAction(int); method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle); method public abstract boolean reportFullscreenMode(boolean); + method public abstract int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest); method public abstract boolean sendKeyEvent(android.view.KeyEvent); method public abstract boolean setComposingRegion(int, int); method public abstract boolean setComposingText(java.lang.CharSequence, int); @@ -35059,6 +35183,7 @@ package android.view.inputmethod { method public boolean performEditorAction(int); method public boolean performPrivateCommand(java.lang.String, android.os.Bundle); method public boolean reportFullscreenMode(boolean); + method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); method public boolean setComposingText(java.lang.CharSequence, int); @@ -36081,11 +36206,13 @@ package android.widget { ctor public ActionMenuView(android.content.Context, android.util.AttributeSet); method public void dismissPopupMenus(); method public android.view.Menu getMenu(); + method public int getPopupTheme(); method public boolean hideOverflowMenu(); method public boolean isOverflowMenuShowing(); method public void onConfigurationChanged(android.content.res.Configuration); method public void onDetachedFromWindow(); method public void setOnMenuItemClickListener(android.widget.ActionMenuView.OnMenuItemClickListener); + method public void setPopupTheme(int); method public boolean showOverflowMenu(); } @@ -37284,6 +37411,7 @@ package android.widget { method public int getAnimationStyle(); method public android.graphics.drawable.Drawable getBackground(); method public android.view.View getContentView(); + method public float getElevation(); method public int getHeight(); method public int getInputMethodMode(); method public int getMaxAvailableHeight(android.view.View); @@ -37301,6 +37429,7 @@ package android.widget { method public void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setClippingEnabled(boolean); method public void setContentView(android.view.View); + method public void setElevation(float); method public void setFocusable(boolean); method public void setHeight(int); method public void setIgnoreCheekPress(); @@ -38311,6 +38440,7 @@ package android.widget { method public android.view.Menu getMenu(); method public java.lang.CharSequence getNavigationContentDescription(); method public android.graphics.drawable.Drawable getNavigationIcon(); + method public int getPopupTheme(); method public java.lang.CharSequence getSubtitle(); method public java.lang.CharSequence getTitle(); method public boolean hasExpandedActionView(); @@ -38332,6 +38462,7 @@ package android.widget { method public void setNavigationIcon(android.graphics.drawable.Drawable); method public void setNavigationOnClickListener(android.view.View.OnClickListener); method public void setOnMenuItemClickListener(android.widget.Toolbar.OnMenuItemClickListener); + method public void setPopupTheme(int); method public void setSubtitle(int); method public void setSubtitle(java.lang.CharSequence); method public void setSubtitleTextAppearance(android.content.Context, int); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index cbc8150..19a91a6 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -353,6 +353,11 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; + /** + * Action to open the power long-press dialog. + */ + public static final int GLOBAL_ACTION_POWER_DIALOG = 6; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 5c98180..b74c824 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -993,6 +993,33 @@ public abstract class ActionBar { } } + /** + * Set the Z-axis elevation of the action bar in pixels. + * + * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher + * values are closer to the user.</p> + * + * @param elevation Elevation value in pixels + */ + public void setElevation(float elevation) { + if (elevation != 0) { + throw new UnsupportedOperationException("Setting a non-zero elevation is " + + "not supported in this action bar configuration."); + } + } + + /** + * Get the Z-axis elevation of the action bar in pixels. + * + * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher + * values are closer to the user.</p> + * + * @return Elevation value in pixels + */ + public float getElevation() { + return 0; + } + /** @hide */ public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index cacb2df..bbfb05e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -565,9 +565,7 @@ class ContextImpl extends Context { registerService(PHONE_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(TELECOMM_SERVICE); - return new PhoneManager(ctx.getOuterContext(), - ITelecommService.Stub.asInterface(b)); + return new PhoneManager(ctx.getOuterContext()); }}); registerService(UI_MODE_SERVICE, new ServiceFetcher() { diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 64e3484..4aec9e0 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -673,6 +673,7 @@ public final class UiAutomation { canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2); canvas.drawBitmap(screenShot, 0, 0, null); canvas.setBitmap(null); + screenShot.recycle(); screenShot = unrotatedScreenShot; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 2feec1b..6ea6b4b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2375,6 +2375,24 @@ public class DevicePolicyManager { } /** + * Called by a device owner to switch the specified user to the foreground. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userHandle the user to switch to; null will switch to primary. + * @return {@code true} if the switch was successful, {@code false} otherwise. + * + * @see Intent#ACTION_USER_FOREGROUND + */ + public boolean switchUser(ComponentName admin, UserHandle userHandle) { + try { + return mService.switchUser(admin, userHandle); + } catch (RemoteException re) { + Log.w(TAG, "Could not switch user ", re); + return false; + } + } + + /** * Called by a profile or device owner to get the application restrictions for a given target * application running in the managed profile. * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 40bd7d1..c27d1cc 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -139,6 +139,7 @@ interface IDevicePolicyManager { UserHandle createUser(in ComponentName who, in String name); UserHandle createAndInitializeUser(in ComponentName who, in String name, in String profileOwnerName, in ComponentName profileOwnerComponent, in Bundle adminExtras); boolean removeUser(in ComponentName who, in UserHandle userHandle); + boolean switchUser(in ComponentName who, in UserHandle userHandle); void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); String[] getAccountTypesWithManagementDisabled(); diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java index a22e4cd..70f6966 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -64,7 +64,6 @@ public class JobInfo implements Parcelable { } private final int jobId; - // TODO: Change this to use PersistableBundle when that lands in master. private final PersistableBundle extras; private final ComponentName service; private final boolean requireCharging; @@ -75,6 +74,7 @@ public class JobInfo implements Parcelable { private final long minLatencyMillis; private final long maxExecutionDelayMillis; private final boolean isPeriodic; + private final boolean isPersisted; private final long intervalMillis; private final long initialBackoffMillis; private final int backoffPolicy; @@ -145,6 +145,13 @@ public class JobInfo implements Parcelable { } /** + * @return Whether or not this job should be persisted across device reboots. + */ + public boolean isPersisted() { + return isPersisted; + } + + /** * Set to the interval between occurrences of this job. This value is <b>not</b> set if the * job does not recur periodically. */ @@ -197,6 +204,7 @@ public class JobInfo implements Parcelable { minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); isPeriodic = in.readInt() == 1; + isPersisted = in.readInt() == 1; intervalMillis = in.readLong(); initialBackoffMillis = in.readLong(); backoffPolicy = in.readInt(); @@ -214,6 +222,7 @@ public class JobInfo implements Parcelable { minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; isPeriodic = b.mIsPeriodic; + isPersisted = b.mIsPersisted; intervalMillis = b.mIntervalMillis; initialBackoffMillis = b.mInitialBackoffMillis; backoffPolicy = b.mBackoffPolicy; @@ -237,6 +246,7 @@ public class JobInfo implements Parcelable { out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); out.writeInt(isPeriodic ? 1 : 0); + out.writeInt(isPersisted ? 1 : 0); out.writeLong(intervalMillis); out.writeLong(initialBackoffMillis); out.writeInt(backoffPolicy); @@ -265,6 +275,7 @@ public class JobInfo implements Parcelable { private boolean mRequiresCharging; private boolean mRequiresDeviceIdle; private int mNetworkCapabilities; + private boolean mIsPersisted; // One-off parameters. private long mMinLatencyMillis; private long mMaxExecutionDelayMillis; @@ -342,11 +353,6 @@ public class JobInfo implements Parcelable { * Specify that this job should recur with the provided interval, not more than once per * period. You have no control over when within this interval this job will be executed, * only the guarantee that it will be executed at most once within this interval. - * A periodic job will be repeated until the phone is turned off, however it will only be - * persisted beyond boot if the client app has declared the - * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule - * periodic jobs without this permission, they simply will cease to exist after the phone - * restarts. * Setting this function on the builder with {@link #setMinimumLatency(long)} or * {@link #setOverrideDeadline(long)} will result in an error. * @param intervalMillis Millisecond interval for which this job will repeat. @@ -407,6 +413,19 @@ public class JobInfo implements Parcelable { } /** + * Set whether or not to persist this job across device reboots. This will only have an + * effect if your application holds the permission + * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED}. Otherwise an exception will + * be thrown. + * @param isPersisted True to indicate that the job will be written to disk and loaded at + * boot. + */ + public Builder setIsPersisted(boolean isPersisted) { + mIsPersisted = isPersisted; + return this; + } + + /** * @return The job object to hand to the JobScheduler. This object is immutable. */ public JobInfo build() { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 04275d8..535aaa1 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2422,6 +2422,7 @@ public abstract class Context { * @see android.net.wifi.WifiScanner * @hide */ + @SystemApi public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 4939fb6..b93bbe0 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -491,18 +491,23 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String nativeLibraryDir; /** - * The path under the apps data directory we store unpacked libraries. For - * new installs, we create subdirectories under legacyNativeLibraryDir that are - * architecture specific. For legacy installs, the shared libraries are - * placed directly under this path. + * The root path where unpacked native libraries are stored. + * <p> + * When {@link #nativeLibraryRootRequiresIsa} is set, the libraries are + * placed in ISA-specific subdirectories under this path, otherwise the + * libraries are placed directly at this path. * - * For "legacy" installs {@code nativeLibraryDir} will be equal to this path. - * For newer installs, it will be derived based on the codePath and the primary - * cpu abi. + * @hide + */ + public String nativeLibraryRootDir; + + /** + * Flag indicating that ISA must be appended to + * {@link #nativeLibraryRootDir} to be useful. * - * @hide. + * @hide */ - public String legacyNativeLibraryDir; + public boolean nativeLibraryRootRequiresIsa; /** * The primary ABI that this application requires, This is inferred from the ABIs @@ -683,7 +688,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { splitSourceDirs = orig.splitSourceDirs; splitPublicSourceDirs = orig.splitPublicSourceDirs; nativeLibraryDir = orig.nativeLibraryDir; - legacyNativeLibraryDir = orig.legacyNativeLibraryDir; + nativeLibraryRootDir = orig.nativeLibraryRootDir; + nativeLibraryRootRequiresIsa = orig.nativeLibraryRootRequiresIsa; primaryCpuAbi = orig.primaryCpuAbi; secondaryCpuAbi = orig.secondaryCpuAbi; apkRoot = orig.apkRoot; @@ -730,7 +736,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeStringArray(splitSourceDirs); dest.writeStringArray(splitPublicSourceDirs); dest.writeString(nativeLibraryDir); - dest.writeString(legacyNativeLibraryDir); + dest.writeString(nativeLibraryRootDir); + dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0); dest.writeString(primaryCpuAbi); dest.writeString(secondaryCpuAbi); dest.writeString(apkRoot); @@ -776,7 +783,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { splitSourceDirs = source.readStringArray(); splitPublicSourceDirs = source.readStringArray(); nativeLibraryDir = source.readString(); - legacyNativeLibraryDir = source.readString(); + nativeLibraryRootDir = source.readString(); + nativeLibraryRootRequiresIsa = source.readInt() != 0; primaryCpuAbi = source.readString(); secondaryCpuAbi = source.readString(); apkRoot = source.readString(); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 6c10bb8..6e7a418 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -55,8 +55,6 @@ public class LauncherApps { private ILauncherApps mService; private PackageManager mPm; - private List<OnAppsChangedListener> mListeners - = new ArrayList<OnAppsChangedListener>(); private List<OnAppsChangedCallback> mCallbacks = new ArrayList<OnAppsChangedCallback>(); @@ -117,62 +115,6 @@ public class LauncherApps { boolean replacing); } - /** - * Callbacks for package changes to this and related managed profiles. - */ - public interface OnAppsChangedListener { - /** - * Indicates that a package was removed from the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that was removed. - */ - void onPackageRemoved(UserHandle user, String packageName); - - /** - * Indicates that a package was added to the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that was added. - */ - void onPackageAdded(UserHandle user, String packageName); - - /** - * Indicates that a package was modified in the specified profile. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageName The name of the package that has changed. - */ - void onPackageChanged(UserHandle user, String packageName); - - /** - * Indicates that one or more packages have become available. For - * example, this can happen when a removable storage card has - * reappeared. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageNames The names of the packages that have become - * available. - * @param replacing Indicates whether these packages are replacing - * existing ones. - */ - void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing); - - /** - * Indicates that one or more packages have become unavailable. For - * example, this can happen when a removable storage card has been - * removed. - * - * @param user The UserHandle of the profile that generated the change. - * @param packageNames The names of the packages that have become - * unavailable. - * @param replacing Indicates whether the packages are about to be - * replaced with new versions. - */ - void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing); - - } - /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; @@ -321,43 +263,6 @@ public class LauncherApps { /** - * Adds a listener for changes to packages in current and managed profiles. - * - * @param listener The listener to add. - */ - public void addOnAppsChangedListener(OnAppsChangedListener listener) { - synchronized (this) { - if (listener != null && !mListeners.contains(listener)) { - mListeners.add(listener); - if (mListeners.size() == 1 && mCallbacks.size() == 0) { - try { - mService.addOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { - } - } - } - } - } - - /** - * Removes a listener that was previously added. - * - * @param listener The listener to remove. - * @see #addOnAppsChangedListener(OnAppsChangedListener) - */ - public void removeOnAppsChangedListener(OnAppsChangedListener listener) { - synchronized (this) { - mListeners.remove(listener); - if (mListeners.size() == 0 && mCallbacks.size() == 0) { - try { - mService.removeOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { - } - } - } - } - - /** * Adds a callback for changes to packages in current and managed profiles. * * @param callback The callback to add. @@ -366,7 +271,7 @@ public class LauncherApps { synchronized (this) { if (callback != null && !mCallbacks.contains(callback)) { mCallbacks.add(callback); - if (mCallbacks.size() == 1 && mListeners.size() == 0) { + if (mCallbacks.size() == 1) { try { mService.addOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -384,8 +289,8 @@ public class LauncherApps { */ public void removeOnAppsChangedCallback(OnAppsChangedCallback callback) { synchronized (this) { - mListeners.remove(callback); - if (mListeners.size() == 0 && mCallbacks.size() == 0) { + mCallbacks.remove(callback); + if (mCallbacks.size() == 0) { try { mService.removeOnAppsChangedListener(mAppsChangedListener); } catch (RemoteException re) { @@ -402,9 +307,6 @@ public class LauncherApps { Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageRemoved(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageRemoved(packageName, user); } @@ -417,9 +319,6 @@ public class LauncherApps { Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageChanged(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageChanged(packageName, user); } @@ -432,9 +331,6 @@ public class LauncherApps { Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackageAdded(user, packageName); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackageAdded(packageName, user); } @@ -448,9 +344,6 @@ public class LauncherApps { Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesAvailable(user, packageNames, replacing); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackagesAvailable(packageNames, user, replacing); } @@ -464,9 +357,6 @@ public class LauncherApps { Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames); } synchronized (LauncherApps.this) { - for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesUnavailable(user, packageNames, replacing); - } for (OnAppsChangedCallback callback : mCallbacks) { callback.onPackagesUnavailable(packageNames, user, replacing); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 625005b..a0e56f6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -841,8 +841,9 @@ public abstract class PackageManager { public static final int DELETE_FAILED_USER_RESTRICTED = -3; /** - * Deletion failed return code: this is returned from the PackageInstaller - * activity if it failed to delete a package because the a profile + * Deletion failed return code: this is passed to the + * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system + * failed to delete the package because a profile * or device owner has marked the package as uninstallable. * * @hide diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 6e0ca50..0b6a926 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -333,7 +333,7 @@ public class PackageParser { } public static final boolean isApkFile(File file) { - return file.isFile() && file.getName().endsWith(".apk"); + return file.getName().endsWith(".apk"); } /* @@ -1312,8 +1312,8 @@ public class PackageParser { } XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("keys")) { - if (!parseKeys(pkg, res, parser, attrs, outError)) { + } else if (tagName.equals("key-sets")) { + if (!parseKeySets(pkg, res, parser, attrs, outError)) { return null; } } else if (tagName.equals("permission-group")) { @@ -1328,17 +1328,6 @@ public class PackageParser { if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) { return null; } - } else if (tagName.equals("upgrade-keyset")) { - sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AndroidManifestUpgradeKeySet); - String name = sa.getNonResourceString( - com.android.internal.R.styleable.AndroidManifestUpgradeKeySet_name); - sa.recycle(); - if (pkg.mUpgradeKeySets == null) { - pkg.mUpgradeKeySets = new ArraySet<String>(); - } - pkg.mUpgradeKeySets.add(name); - XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("uses-permission")) { if (!parseUsesPermission(pkg, res, parser, attrs, outError)) { return null; @@ -1849,84 +1838,141 @@ public class PackageParser { return buildCompoundName(pkg, procSeq, "taskAffinity", outError); } - private boolean parseKeys(Package owner, Resources res, + private boolean parseKeySets(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, String[] outError) throws XmlPullParserException, IOException { - // we've encountered the 'keys' tag + // we've encountered the 'key-sets' tag // all the keys and keysets that we want must be defined here // so we're going to iterate over the parser and pull out the things we want int outerDepth = parser.getDepth(); - + int currentKeySetDepth = -1; int type; - PublicKey currentKey = null; - int currentKeyDepth = -1; - Map<PublicKey, Set<String>> definedKeySets = new HashMap<PublicKey, Set<String>>(); + String currentKeySet = null; + ArrayMap<String, PublicKey> publicKeys = new ArrayMap<String, PublicKey>(); + ArraySet<String> upgradeKeySets = new ArraySet<String>(); + ArrayMap<String, ArraySet<String>> definedKeySets = new ArrayMap<String, ArraySet<String>>(); + ArraySet<String> improperKeySets = new ArraySet<String>(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG) { - if (parser.getDepth() == currentKeyDepth) { - currentKey = null; - currentKeyDepth = -1; + if (parser.getDepth() == currentKeySetDepth) { + currentKeySet = null; + currentKeySetDepth = -1; } continue; } - String tagname = parser.getName(); - if (tagname.equals("publicKey")) { - final TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.PublicKey); - final String encodedKey = sa.getNonResourceString( - com.android.internal.R.styleable.PublicKey_value); - currentKey = parsePublicKey(encodedKey); - if (currentKey == null) { - Slog.w(TAG, "No valid key in 'publicKey' tag at " + String tagName = parser.getName(); + if (tagName.equals("key-set")) { + if (currentKeySet != null) { + Slog.w(TAG, "Improperly nested 'key-set' tag at " + parser.getPositionDescription()); - sa.recycle(); - continue; + return false; } - currentKeyDepth = parser.getDepth(); - definedKeySets.put(currentKey, new HashSet<String>()); + final TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestKeySet); + final String keysetName = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestKeySet_name); + definedKeySets.put(keysetName, new ArraySet<String>()); + currentKeySet = keysetName; + currentKeySetDepth = parser.getDepth(); sa.recycle(); - } else if (tagname.equals("keyset")) { - if (currentKey == null) { - Slog.i(TAG, "'keyset' not in 'publicKey' tag at " + } else if (tagName.equals("public-key")) { + if (currentKeySet == null) { + Slog.w(TAG, "Improperly nested 'public-key' tag at " + parser.getPositionDescription()); - continue; + return false; } final TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.KeySet); - final String name = sa.getNonResourceString( - com.android.internal.R.styleable.KeySet_name); - definedKeySets.get(currentKey).add(name); + com.android.internal.R.styleable.AndroidManifestPublicKey); + final String publicKeyName = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPublicKey_name); + final String encodedKey = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPublicKey_value); + if (encodedKey == null && publicKeys.get(publicKeyName) == null) { + Slog.w(TAG, "'public-key' " + publicKeyName + " must define a public-key value" + + " on first use at " + parser.getPositionDescription()); + sa.recycle(); + return false; + } else if (encodedKey != null) { + PublicKey currentKey = parsePublicKey(encodedKey); + if (currentKey == null) { + Slog.w(TAG, "No recognized valid key in 'public-key' tag at " + + parser.getPositionDescription() + " key-set " + currentKeySet + + " will not be added to the package's defined key-sets."); + sa.recycle(); + improperKeySets.add(currentKeySet); + XmlUtils.skipCurrentTag(parser); + continue; + } + if (publicKeys.get(publicKeyName) == null + || publicKeys.get(publicKeyName).equals(currentKey)) { + + /* public-key first definition, or matches old definition */ + publicKeys.put(publicKeyName, currentKey); + } else { + Slog.w(TAG, "Value of 'public-key' " + publicKeyName + + " conflicts with previously defined value at " + + parser.getPositionDescription()); + sa.recycle(); + return false; + } + } + definedKeySets.get(currentKeySet).add(publicKeyName); + sa.recycle(); + XmlUtils.skipCurrentTag(parser); + } else if (tagName.equals("upgrade-key-set")) { + final TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestUpgradeKeySet); + String name = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestUpgradeKeySet_name); + upgradeKeySets.add(name); sa.recycle(); + XmlUtils.skipCurrentTag(parser); } else if (RIGID_PARSER) { - Slog.w(TAG, "Bad element under <keys>: " + parser.getName() + Slog.w(TAG, "Bad element under <key-sets>: " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); return false; } else { - Slog.w(TAG, "Unknown element under <keys>: " + parser.getName() + Slog.w(TAG, "Unknown element under <key-sets>: " + parser.getName() + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } } - - owner.mKeySetMapping = new ArrayMap<String, Set<PublicKey>>(); - for (Map.Entry<PublicKey, Set<String>> e : definedKeySets.entrySet()) { - PublicKey key = e.getKey(); - Set<String> keySetNames = e.getValue(); - for (String alias : keySetNames) { - if (owner.mKeySetMapping.containsKey(alias)) { - owner.mKeySetMapping.get(alias).add(key); - } else { - Set<PublicKey> keys = new ArraySet<PublicKey>(); - keys.add(key); - owner.mKeySetMapping.put(alias, keys); - } + Set<String> publicKeyNames = publicKeys.keySet(); + if (publicKeyNames.removeAll(definedKeySets.keySet())) { + Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml " + + "'key-set' and 'public-key' names must be distinct."); + return false; + } + owner.mKeySetMapping = new ArrayMap<String, ArraySet<PublicKey>>(); + for (ArrayMap.Entry<String, ArraySet<String>> e: definedKeySets.entrySet()) { + final String keySetName = e.getKey(); + if (e.getValue().size() == 0) { + Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml " + + "'key-set' " + keySetName + " has no valid associated 'public-key'." + + " Not including in package's defined key-sets."); + continue; + } else if (improperKeySets.contains(keySetName)) { + Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml " + + "'key-set' " + keySetName + " contained improper 'public-key'" + + " tags. Not including in package's defined key-sets."); + continue; + } + owner.mKeySetMapping.put(keySetName, new ArraySet<PublicKey>()); + for (String s : e.getValue()) { + owner.mKeySetMapping.get(keySetName).add(publicKeys.get(s)); } } - + if (owner.mKeySetMapping.keySet().containsAll(upgradeKeySets)) { + owner.mUpgradeKeySets = upgradeKeySets; + } else { + Slog.w(TAG, "Package" + owner.packageName + " AndroidManifext.xml " + + "does not define all 'upgrade-key-set's ."); + return false; + } return true; } @@ -3925,11 +3971,11 @@ public class PackageParser { public boolean mTrustedOverlay; /** - * Data used to feed the KeySetManager + * Data used to feed the KeySetManagerService */ - public Set<PublicKey> mSigningKeys; - public Set<String> mUpgradeKeySets; - public Map<String, Set<PublicKey>> mKeySetMapping; + public ArraySet<PublicKey> mSigningKeys; + public ArraySet<String> mUpgradeKeySets; + public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping; public Package(String packageName) { this.packageName = packageName; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 44e3f76..4b1659f 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1680,14 +1680,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>This tag specifies the zero light value for each of the CFA mosaic * channels in the camera sensor. The maximal value output by the * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p> - * <p>The values are given in row-column scan order, with the first value - * corresponding to the element of the CFA in row=0, column=0.</p> + * <p>The values are given in the same order as channels listed for the CFA + * layout tag (see {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}), i.e. the + * nth value given corresponds to the black level offset for the nth + * color channel listed in the CFA.</p> * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL */ - public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN = - new Key<int[]>("android.sensor.blackLevelPattern", int[].class); + public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_BLACK_LEVEL_PATTERN = + new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.blackLevelPattern", android.hardware.camera2.params.BlackLevelPattern.class); /** * <p>Maximum sensitivity that is implemented diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 6eeeff2..fa35f44 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1131,7 +1131,8 @@ public abstract class CameraMetadata<TKey> { * image while recording video) use case.</p> * <p>The camera device should take the highest-quality image * possible (given the other settings) without disrupting the - * frame rate of video recording. </p> + * frame rate of video recording.<br /> + * </p> * @see CaptureRequest#CONTROL_CAPTURE_INTENT */ public static final int CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT = 4; diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java index 3e3303c..6fc99ac 100644 --- a/core/java/android/hardware/camera2/DngCreator.java +++ b/core/java/android/hardware/camera2/DngCreator.java @@ -17,6 +17,7 @@ package android.hardware.camera2; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.ImageFormat; import android.hardware.camera2.impl.CameraMetadataNative; import android.location.Location; @@ -31,6 +32,7 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.TimeZone; /** @@ -68,17 +70,19 @@ public final class DngCreator implements AutoCloseable { * </p> * <p> * DNG metadata tags will be generated from the corresponding parameters in the - * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides - * all previous tags set. + * {@link android.hardware.camera2.CaptureResult} object. + * </p> + * <p> + * For best quality DNG files, it is strongly recommended that lens shading map output is + * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}. * </p> - * * @param characteristics an object containing the static * {@link android.hardware.camera2.CameraCharacteristics}. * @param metadata a metadata object to generate tags from. */ public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) { if (characteristics == null || metadata == null) { - throw new NullPointerException("Null argument to DngCreator constructor"); + throw new IllegalArgumentException("Null argument to DngCreator constructor"); } // Find current time @@ -121,10 +125,8 @@ public final class DngCreator implements AutoCloseable { * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li> * </ul> * @return this {@link #DngCreator} object. - * @hide */ public DngCreator setOrientation(int orientation) { - if (orientation < ExifInterface.ORIENTATION_UNDEFINED || orientation > ExifInterface.ORIENTATION_ROTATE_270) { throw new IllegalArgumentException("Orientation " + orientation + @@ -139,32 +141,32 @@ public final class DngCreator implements AutoCloseable { * * <p> * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel. - * The alpha channel will be discarded. - * </p> - * - * <p> - * The given bitmap should not be altered while this object is in use. + * The alpha channel will be discarded. Thumbnail images with a dimension larger than + * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected. * </p> * * @param pixels a {@link android.graphics.Bitmap} of pixel data. * @return this {@link #DngCreator} object. - * @hide + * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension + * larger than {@link #MAX_THUMBNAIL_DIMENSION}. */ public DngCreator setThumbnail(Bitmap pixels) { if (pixels == null) { - throw new NullPointerException("Null argument to setThumbnail"); + throw new IllegalArgumentException("Null argument to setThumbnail"); } - Bitmap.Config config = pixels.getConfig(); + int width = pixels.getWidth(); + int height = pixels.getHeight(); - if (config != Bitmap.Config.ARGB_8888) { - pixels = pixels.copy(Bitmap.Config.ARGB_8888, false); - if (pixels == null) { - throw new IllegalArgumentException("Unsupported Bitmap format " + config); - } - nativeSetThumbnailBitmap(pixels); + if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) { + throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width + + "," + height + ") too large, dimensions must be smaller than " + + MAX_THUMBNAIL_DIMENSION); } + ByteBuffer rgbBuffer = convertToRGB(pixels); + nativeSetThumbnail(rgbBuffer, width, height); + return this; } @@ -173,37 +175,41 @@ public final class DngCreator implements AutoCloseable { * * <p> * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image. - * </p> - * - * <p> - * The given image should not be altered while this object is in use. + * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be + * rejected. * </p> * * @param pixels an {@link android.media.Image} object with the format * {@link android.graphics.ImageFormat#YUV_420_888}. * @return this {@link #DngCreator} object. - * @hide + * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension + * larger than {@link #MAX_THUMBNAIL_DIMENSION}. */ public DngCreator setThumbnail(Image pixels) { if (pixels == null) { - throw new NullPointerException("Null argument to setThumbnail"); + throw new IllegalArgumentException("Null argument to setThumbnail"); } int format = pixels.getFormat(); if (format != ImageFormat.YUV_420_888) { - throw new IllegalArgumentException("Unsupported image format " + format); + throw new IllegalArgumentException("Unsupported Image format " + format); } - Image.Plane[] planes = pixels.getPlanes(); - nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), - planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(), - planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(), - planes[1].getRowStride(), planes[1].getPixelStride()); + int width = pixels.getWidth(); + int height = pixels.getHeight(); + + if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) { + throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width + + "," + height + ") too large, dimensions must be smaller than " + + MAX_THUMBNAIL_DIMENSION); + } + + ByteBuffer rgbBuffer = convertToRGB(pixels); + nativeSetThumbnail(rgbBuffer, width, height); return this; } - /** * Set image location metadata. * @@ -219,10 +225,26 @@ public final class DngCreator implements AutoCloseable { * * @throws java.lang.IllegalArgumentException if the given location object doesn't * contain enough information to set location metadata. - * @hide */ public DngCreator setLocation(Location location) { - /*TODO*/ + if (location == null) { + throw new IllegalArgumentException("Null location passed to setLocation"); + } + double latitude = location.getLatitude(); + double longitude = location.getLongitude(); + long time = location.getTime(); + + int[] latTag = toExifLatLong(latitude); + int[] longTag = toExifLatLong(longitude); + String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH; + String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST; + + String dateTag = sExifGPSDateStamp.format(time); + mGPSTimeStampCalendar.setTimeInMillis(time); + int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1, + mGPSTimeStampCalendar.get(Calendar.MINUTE), 1, + mGPSTimeStampCalendar.get(Calendar.SECOND), 1 }; + nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag); return this; } @@ -235,10 +257,12 @@ public final class DngCreator implements AutoCloseable { * * @param description the user description string. * @return this {@link #DngCreator} object. - * @hide */ public DngCreator setDescription(String description) { - /*TODO*/ + if (description == null) { + throw new IllegalArgumentException("Null description passed to setDescription."); + } + nativeSetDescription(description); return this; } @@ -268,14 +292,26 @@ public final class DngCreator implements AutoCloseable { * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. * @throws java.lang.IllegalArgumentException if the size passed in does not match the - * @hide */ public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset) throws IOException { - if (dngOutput == null || pixels == null) { - throw new NullPointerException("Null argument to writeImage"); + if (dngOutput == null) { + throw new IllegalArgumentException("Null dngOutput passed to writeInputStream"); + } else if (size == null) { + throw new IllegalArgumentException("Null size passed to writeInputStream"); + } else if (pixels == null) { + throw new IllegalArgumentException("Null pixels passed to writeInputStream"); + } else if (offset < 0) { + throw new IllegalArgumentException("Negative offset passed to writeInputStream"); } - nativeWriteInputStream(dngOutput, pixels, offset); + + int width = size.getWidth(); + int height = size.getHeight(); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," + + height + ") passed to writeInputStream"); + } + nativeWriteInputStream(dngOutput, pixels, width, height, offset); } /** @@ -294,6 +330,11 @@ public final class DngCreator implements AutoCloseable { * {@link java.lang.IllegalStateException} will be thrown. * </p> * + * <p> + * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this + * method. + * </p> + * * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. * @param size the {@link Size} of the image to write, in pixels. * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write. @@ -303,14 +344,24 @@ public final class DngCreator implements AutoCloseable { * @throws IOException if an error was encountered in the input or output stream. * @throws java.lang.IllegalStateException if not enough metadata information has been * set to write a well-formatted DNG file. - * @hide */ public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset) throws IOException { - if (dngOutput == null || pixels == null) { - throw new NullPointerException("Null argument to writeImage"); + if (dngOutput == null) { + throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer"); + } else if (size == null) { + throw new IllegalArgumentException("Null size passed to writeByteBuffer"); + } else if (pixels == null) { + throw new IllegalArgumentException("Null pixels passed to writeByteBuffer"); + } else if (offset < 0) { + throw new IllegalArgumentException("Negative offset passed to writeByteBuffer"); } - nativeWriteByteBuffer(dngOutput, pixels, offset); + + int width = size.getWidth(); + int height = size.getHeight(); + + writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE, + width * DEFAULT_PIXEL_STRIDE, offset); } /** @@ -331,8 +382,10 @@ public final class DngCreator implements AutoCloseable { * set to write a well-formatted DNG file. */ public void writeImage(OutputStream dngOutput, Image pixels) throws IOException { - if (dngOutput == null || pixels == null) { - throw new NullPointerException("Null argument to writeImage"); + if (dngOutput == null) { + throw new IllegalArgumentException("Null dngOutput to writeImage"); + } else if (pixels == null) { + throw new IllegalArgumentException("Null pixels to writeImage"); } int format = pixels.getFormat(); @@ -341,8 +394,13 @@ public final class DngCreator implements AutoCloseable { } Image.Plane[] planes = pixels.getPlanes(); - nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), - planes[0].getRowStride(), planes[0].getPixelStride()); + if (planes == null || planes.length <= 0) { + throw new IllegalArgumentException("Image with no planes passed to writeImage"); + } + + ByteBuffer buf = planes[0].getBuffer(); + writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput, + planes[0].getPixelStride(), planes[0].getRowStride(), 0); } @Override @@ -350,6 +408,11 @@ public final class DngCreator implements AutoCloseable { nativeDestroy(); } + /** + * Max width or height dimension for thumbnails. + */ + public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP + @Override protected void finalize() throws Throwable { try { @@ -359,13 +422,181 @@ public final class DngCreator implements AutoCloseable { } } + private static final String GPS_LAT_REF_NORTH = "N"; + private static final String GPS_LAT_REF_SOUTH = "S"; + private static final String GPS_LONG_REF_EAST = "E"; + private static final String GPS_LONG_REF_WEST = "W"; + + private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss"; + private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR); private static final DateFormat sDateTimeStampFormat = new SimpleDateFormat(TIFF_DATETIME_FORMAT); + private final Calendar mGPSTimeStampCalendar = Calendar + .getInstance(TimeZone.getTimeZone("UTC")); static { sDateTimeStampFormat.setTimeZone(TimeZone.getDefault()); + sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample + private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel + + /** + * Offset, rowStride, and pixelStride are given in bytes. Height and width are given in pixels. + */ + private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput, + int pixelStride, int rowStride, long offset) throws IOException { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," + + height + ") passed to write"); + } + long capacity = pixels.capacity(); + long totalSize = rowStride * height + offset; + if (capacity < totalSize) { + throw new IllegalArgumentException("Image size " + capacity + + " is too small (must be larger than " + totalSize + ")"); + } + int minRowStride = pixelStride * width; + if (minRowStride > rowStride) { + throw new IllegalArgumentException("Invalid image pixel stride, row byte width " + + minRowStride + " is too large, expecting " + rowStride); + } + pixels.clear(); // Reset mark and limit + nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset, + pixels.isDirect()); + pixels.clear(); + } + + /** + * Convert a single YUV pixel to RGB. + */ + private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) { + final int COLOR_MAX = 255; + + float y = yuvData[0] & 0xFF; // Y channel + float cb = yuvData[1] & 0xFF; // U channel + float cr = yuvData[2] & 0xFF; // V channel + + // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section) + float r = y + 1.402f * (cr - 128); + float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128); + float b = y + 1.772f * (cb - 128); + + // clamp to [0,255] + rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r)); + rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g)); + rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b)); } + + /** + * Convert a single {@link Color} pixel to RGB. + */ + private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) { + rgbOut[outOffset] = (byte) Color.red(color); + rgbOut[outOffset + 1] = (byte) Color.green(color); + rgbOut[outOffset + 2] = (byte) Color.blue(color); + // Discards Alpha + } + + /** + * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}. + */ + private static ByteBuffer convertToRGB(Image yuvImage) { + // TODO: Optimize this with renderscript intrinsic. + int width = yuvImage.getWidth(); + int height = yuvImage.getHeight(); + ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height); + + Image.Plane yPlane = yuvImage.getPlanes()[0]; + Image.Plane uPlane = yuvImage.getPlanes()[1]; + Image.Plane vPlane = yuvImage.getPlanes()[2]; + + ByteBuffer yBuf = yPlane.getBuffer(); + ByteBuffer uBuf = uPlane.getBuffer(); + ByteBuffer vBuf = vPlane.getBuffer(); + + yBuf.rewind(); + uBuf.rewind(); + vBuf.rewind(); + + int yRowStride = yPlane.getRowStride(); + int vRowStride = vPlane.getRowStride(); + int uRowStride = uPlane.getRowStride(); + + int yPixStride = yPlane.getPixelStride(); + int vPixStride = vPlane.getPixelStride(); + int uPixStride = uPlane.getPixelStride(); + + byte[] yuvPixel = { 0, 0, 0 }; + byte[] yFullRow = new byte[yPixStride * width]; + byte[] uFullRow = new byte[uPixStride * width / 2]; + byte[] vFullRow = new byte[vPixStride * width / 2]; + byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width]; + for (int i = 0; i < height; i++) { + int halfH = i / 2; + yBuf.position(yRowStride * i); + yBuf.get(yFullRow); + uBuf.position(uRowStride * halfH); + uBuf.get(uFullRow); + vBuf.position(vRowStride * halfH); + vBuf.get(vFullRow); + for (int j = 0; j < width; j++) { + int halfW = j / 2; + yuvPixel[0] = yFullRow[yPixStride * j]; + yuvPixel[1] = uFullRow[uPixStride * halfW]; + yuvPixel[2] = vFullRow[vPixStride * halfW]; + yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow); + } + buf.put(finalRow); + } + + yBuf.rewind(); + uBuf.rewind(); + vBuf.rewind(); + buf.rewind(); + return buf; + } + + /** + * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}. + */ + private static ByteBuffer convertToRGB(Bitmap argbBitmap) { + // TODO: Optimize this. + int width = argbBitmap.getWidth(); + int height = argbBitmap.getHeight(); + ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height); + + int[] pixelRow = new int[width]; + byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width]; + for (int i = 0; i < height; i++) { + argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i, + /*width*/width, /*height*/1); + for (int j = 0; j < width; j++) { + colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow); + } + buf.put(finalRow); + } + + buf.rewind(); + return buf; + } + + /** + * Convert coordinate to EXIF GPS tag format. + */ + private static int[] toExifLatLong(double value) { + // convert to the format dd/1 mm/1 ssss/100 + value = Math.abs(value); + int degrees = (int) value; + value = (value - degrees) * 60; + int minutes = (int) value; + value = (value - minutes) * 6000; + int seconds = (int) value; + return new int[] { degrees, 1, minutes, 1, seconds, 100 }; + } + /** * This field is used by native code, do not access or modify. */ @@ -381,24 +612,22 @@ public final class DngCreator implements AutoCloseable { private synchronized native void nativeSetOrientation(int orientation); - private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap); + private synchronized native void nativeSetDescription(String description); - private synchronized native void nativeSetThumbnailImage(int width, int height, - ByteBuffer yBuffer, int yRowStride, - int yPixStride, ByteBuffer uBuffer, - int uRowStride, int uPixStride, - ByteBuffer vBuffer, int vRowStride, - int vPixStride); + private synchronized native void nativeSetGpsTags(int[] latTag, String latRef, int[] longTag, + String longRef, String dateTag, + int[] timeTag); + + private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height); private synchronized native void nativeWriteImage(OutputStream out, int width, int height, ByteBuffer rawBuffer, int rowStride, - int pixStride) throws IOException; - - private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer, - long offset) throws IOException; + int pixStride, long offset, boolean isDirect) + throws IOException; private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream, - long offset) throws IOException; + int width, int height, long offset) + throws IOException; static { nativeClassInit(); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 83aee5d..6de5c25 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -27,6 +27,7 @@ import android.hardware.camera2.marshal.MarshalQueryable; import android.hardware.camera2.marshal.MarshalRegistry; import android.hardware.camera2.marshal.impl.MarshalQueryableArray; import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean; +import android.hardware.camera2.marshal.impl.MarshalQueryableBlackLevelPattern; import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform; import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle; @@ -1013,6 +1014,7 @@ public class CameraMetadataNative implements Parcelable { new MarshalQueryableStreamConfiguration(), new MarshalQueryableStreamConfigurationDuration(), new MarshalQueryableRggbChannelVector(), + new MarshalQueryableBlackLevelPattern(), // generic parcelable marshaler (MUST BE LAST since it has lowest priority) new MarshalQueryableParcelable(), diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java new file mode 100644 index 0000000..b930ec2 --- /dev/null +++ b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.legacy; + +import android.os.SystemClock; +import android.util.Log; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +/** + * GPU and CPU performance measurement for the legacy implementation. + * + * <p>Measures CPU and GPU processing duration for a set of operations, and dumps + * the results into a file.</p> + * + * <p>Rough usage: + * <pre> + * {@code + * <set up workload> + * <start long-running workload> + * mPerfMeasurement.startTimer(); + * ...render a frame... + * mPerfMeasurement.stopTimer(); + * <end workload> + * mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt"); + * } + * </pre> + * </p> + * + * <p>All calls to this object must be made within the same thread, and the same GL context. + * PerfMeasurement cannot be used outside of a GL context. The only exception is + * dumpPerformanceData, which can be called outside of a valid GL context.</p> + */ +class PerfMeasurement { + private static final String TAG = "PerfMeasurement"; + + public static final int DEFAULT_MAX_QUERIES = 3; + + private final long mNativeContext; + + private int mCompletedQueryCount = 0; + + /** + * Values for completed measurements + */ + private ArrayList<Long> mCollectedGpuDurations = new ArrayList<>(); + private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>(); + private ArrayList<Long> mCollectedTimestamps = new ArrayList<>(); + + /** + * Values for in-progress measurements (waiting for async GPU results) + */ + private Queue<Long> mTimestampQueue = new LinkedList<>(); + private Queue<Long> mCpuDurationsQueue = new LinkedList<>(); + + private long mStartTimeNs; + + /** + * The value returned by {@link #nativeGetNextGlDuration} if no new timing + * measurement is available since the last call. + */ + private static final long NO_DURATION_YET = -1l; + + /** + * The value returned by {@link #nativeGetNextGlDuration} if timing failed for + * the next timing interval + */ + private static final long FAILED_TIMING = -2l; + + /** + * Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES} + * in-progess queries. + */ + public PerfMeasurement() { + mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES); + } + + /** + * Create a performance measurement object with maxQueries as the maximum number of + * in-progress queries. + * + * @param maxQueries maximum in-progress queries, must be larger than 0. + * @throws IllegalArgumentException if maxQueries is less than 1. + */ + public PerfMeasurement(int maxQueries) { + if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1"); + mNativeContext = nativeCreateContext(maxQueries); + } + + /** + * Returns true if the Gl timing methods will work, false otherwise. + * + * <p>Must be called within a valid GL context.</p> + */ + public static boolean isGlTimingSupported() { + return nativeQuerySupport(); + } + + /** + * Dump collected data to file, and clear the stored data. + * + * <p> + * Format is a simple csv-like text file with a header, + * followed by a 3-column list of values in nanoseconds: + * <pre> + * timestamp gpu_duration cpu_duration + * <long> <long> <long> + * <long> <long> <long> + * <long> <long> <long> + * .... + * </pre> + * </p> + */ + public void dumpPerformanceData(String path) { + try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) { + dump.write("timestamp gpu_duration cpu_duration\n"); + for (int i = 0; i < mCollectedGpuDurations.size(); i++) { + dump.write(String.format("%d %d %d\n", + mCollectedTimestamps.get(i), + mCollectedGpuDurations.get(i), + mCollectedCpuDurations.get(i))); + } + mCollectedTimestamps.clear(); + mCollectedGpuDurations.clear(); + mCollectedCpuDurations.clear(); + } catch (IOException e) { + Log.e(TAG, "Error writing data dump to " + path + ":" + e); + } + } + + /** + * Start a GPU/CPU timing measurement. + * + * <p>Call before starting a rendering pass. Only one timing measurement can be active at once, + * so {@link #stopTimer} must be called before the next call to this method.</p> + * + * @throws IllegalStateException if the maximum number of queries are in progress already, + * or the method is called multiple times in a row, or there is + * a GPU error. + */ + public void startTimer() { + nativeStartGlTimer(mNativeContext); + mStartTimeNs = SystemClock.elapsedRealtimeNanos(); + } + + /** + * Finish a GPU/CPU timing measurement. + * + * <p>Call after finishing all the drawing for a rendering pass. Only one timing measurement can + * be active at once, so {@link #startTimer} must be called before the next call to this + * method.</p> + * + * @throws IllegalStateException if no GL timer is currently started, or there is a GPU + * error. + */ + public void stopTimer() { + // Complete CPU timing + long endTimeNs = SystemClock.elapsedRealtimeNanos(); + mCpuDurationsQueue.add(endTimeNs - mStartTimeNs); + // Complete GL timing + nativeStopGlTimer(mNativeContext); + + // Poll to see if GL timing results have arrived; if so + // store the results for a frame + long duration = getNextGlDuration(); + if (duration > 0) { + mCollectedGpuDurations.add(duration); + mCollectedTimestamps.add(mTimestampQueue.isEmpty() ? + NO_DURATION_YET : mTimestampQueue.poll()); + mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ? + NO_DURATION_YET : mCpuDurationsQueue.poll()); + } + if (duration == FAILED_TIMING) { + // Discard timestamp and CPU measurement since GPU measurement failed + if (!mTimestampQueue.isEmpty()) { + mTimestampQueue.poll(); + } + if (!mCpuDurationsQueue.isEmpty()) { + mCpuDurationsQueue.poll(); + } + } + } + + /** + * Add a timestamp to a timing measurement. These are queued up and matched to completed + * workload measurements as they become available. + */ + public void addTimestamp(long timestamp) { + mTimestampQueue.add(timestamp); + } + + /** + * Get the next available GPU timing measurement. + * + * <p>Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement + * will only be available some time after the {@link #stopTimer} call is made. Poll this method + * until the result becomes available. If multiple start/endTimer measurements are made in a + * row, the results will be available in FIFO order.</p> + * + * @return The measured duration of the GPU workload for the next pending query, or + * {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not + * yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the + * measurement. + * + * @throws IllegalStateException If there is a GPU error. + * + */ + private long getNextGlDuration() { + long duration = nativeGetNextGlDuration(mNativeContext); + if (duration > 0) { + mCompletedQueryCount++; + } + return duration; + } + + /** + * Returns the number of measurements so far that returned a valid duration + * measurement. + */ + public int getCompletedQueryCount() { + return mCompletedQueryCount; + } + + @Override + protected void finalize() { + nativeDeleteContext(mNativeContext); + } + + /** + * Create a native performance measurement context. + * + * @param maxQueryCount maximum in-progress queries; must be >= 1. + */ + private static native long nativeCreateContext(int maxQueryCount); + + /** + * Delete the native context. + * + * <p>Not safe to call more than once.</p> + */ + private static native void nativeDeleteContext(long contextHandle); + + /** + * Query whether the relevant Gl extensions are available for Gl timing + */ + private static native boolean nativeQuerySupport(); + + /** + * Start a GL timing section. + * + * <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be + * included in the timing.</p> + * + * <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and + * {@link #nativeGetNextGlDuration}.</p> + * + * @throws IllegalStateException if a GL error occurs or start is called repeatedly. + */ + protected static native void nativeStartGlTimer(long contextHandle); + + /** + * Finish a GL timing section. + * + * <p>Some time after this call returns, the time the GPU took to + * execute all work submitted between the latest {@link #nativeStartGlTimer} and + * this call, will become available from calling {@link #nativeGetNextGlDuration}.</p> + * + * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and + * {@link #nativeGetNextGlDuration}.</p> + * + * @throws IllegalStateException if a GL error occurs or stop is called before start + */ + protected static native void nativeStopGlTimer(long contextHandle); + + /** + * Get the next available GL duration measurement, in nanoseconds. + * + * <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and + * {@link #nativeEndGlTimer}.</p> + * + * @return the next GL duration measurement, or {@link #NO_DURATION_YET} if + * no new measurement is available, or {@link #FAILED_TIMING} if timing + * failed for the next duration measurement. + * @throws IllegalStateException if a GL error occurs + */ + protected static native long nativeGetNextGlDuration(long contextHandle); + + +} diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java index 9969fd2..daa64c0 100644 --- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java +++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java @@ -17,6 +17,7 @@ package android.hardware.camera2.legacy; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; +import android.os.Environment; import android.opengl.EGL14; import android.opengl.EGLConfig; import android.opengl.EGLContext; @@ -25,10 +26,13 @@ import android.opengl.EGLSurface; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.Matrix; +import android.text.format.Time; import android.util.Log; import android.util.Size; import android.view.Surface; +import android.os.SystemProperties; +import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -126,6 +130,9 @@ public class SurfaceTextureRenderer { private int maPositionHandle; private int maTextureHandle; + private PerfMeasurement mPerfMeasurer = null; + private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf"; + public SurfaceTextureRenderer() { mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); @@ -219,7 +226,6 @@ public class SurfaceTextureRenderer { GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4); checkGlError("glDrawArrays"); - GLES20.glFinish(); } /** @@ -327,9 +333,16 @@ public class SurfaceTextureRenderer { EGL14.EGL_NONE }; for (EGLSurfaceHolder holder : surfaces) { - holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface, - surfaceAttribs, 0); - checkEglError("eglCreateWindowSurface"); + try { + Size size = LegacyCameraDevice.getSurfaceSize(holder.surface); + holder.width = size.getWidth(); + holder.height = size.getHeight(); + holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, + holder.surface, surfaceAttribs, /*offset*/ 0); + checkEglError("eglCreateWindowSurface"); + } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { + Log.w(TAG, "Surface abandoned, skipping...", e); + } } } @@ -367,6 +380,7 @@ public class SurfaceTextureRenderer { if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + dumpGlTiming(); if (mSurfaces != null) { for (EGLSurfaceHolder holder : mSurfaces) { if (holder.eglSurface != null) { @@ -418,6 +432,65 @@ public class SurfaceTextureRenderer { } /** + * Save a measurement dump to disk, in + * {@code /sdcard/CameraLegacy/durations_<time>_<width1>x<height1>_...txt} + */ + private void dumpGlTiming() { + if (mPerfMeasurer == null) return; + + File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy"); + if (!legacyStorageDir.exists()){ + if (!legacyStorageDir.mkdirs()){ + Log.e(TAG, "Failed to create directory for data dump"); + return; + } + } + + StringBuilder path = new StringBuilder(legacyStorageDir.getPath()); + path.append(File.separator); + path.append("durations_"); + + Time now = new Time(); + now.setToNow(); + path.append(now.format2445()); + path.append("_S"); + for (EGLSurfaceHolder surface : mSurfaces) { + path.append(String.format("_%d_%d", surface.width, surface.height)); + } + path.append("_C"); + for (EGLSurfaceHolder surface : mConversionSurfaces) { + path.append(String.format("_%d_%d", surface.width, surface.height)); + } + path.append(".txt"); + mPerfMeasurer.dumpPerformanceData(path.toString()); + } + + private void setupGlTiming() { + if (PerfMeasurement.isGlTimingSupported()) { + Log.d(TAG, "Enabling GL performance measurement"); + mPerfMeasurer = new PerfMeasurement(); + } else { + Log.d(TAG, "GL performance measurement not supported on this device"); + mPerfMeasurer = null; + } + } + + private void beginGlTiming() { + if (mPerfMeasurer == null) return; + mPerfMeasurer.startTimer(); + } + + private void addGlTimestamp(long timestamp) { + if (mPerfMeasurer == null) return; + mPerfMeasurer.addTimestamp(timestamp); + } + + private void endGlTiming() { + if (mPerfMeasurer == null) return; + mPerfMeasurer.stopTimer(); + } + + /** * Return the surface texture to draw to - this is the texture use to when producing output * surface buffers. * @@ -474,6 +547,11 @@ public class SurfaceTextureRenderer { mConversionSurfaces.get(0).eglSurface); initializeGLState(); mSurfaceTexture = new SurfaceTexture(getTextureId()); + + // Set up performance tracking if enabled + if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) { + setupGlTiming(); + } } /** @@ -494,8 +572,19 @@ public class SurfaceTextureRenderer { } checkGlError("before updateTexImage"); + + if (targetSurfaces == null) { + mSurfaceTexture.updateTexImage(); + return; + } + + beginGlTiming(); + mSurfaceTexture.updateTexImage(); - if (targetSurfaces == null) return; + + long timestamp = mSurfaceTexture.getTimestamp(); + addGlTimestamp(timestamp); + List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces); for (EGLSurfaceHolder holder : mSurfaces) { if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) { @@ -522,6 +611,8 @@ public class SurfaceTextureRenderer { } } } + + endGlTiming(); } /** diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java new file mode 100644 index 0000000..bcb035e --- /dev/null +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.camera2.marshal.impl; + +import android.hardware.camera2.marshal.MarshalQueryable; +import android.hardware.camera2.marshal.Marshaler; +import android.hardware.camera2.params.BlackLevelPattern; +import android.hardware.camera2.utils.TypeReference; + +import java.nio.ByteBuffer; + +import static android.hardware.camera2.impl.CameraMetadataNative.TYPE_INT32; +import static android.hardware.camera2.marshal.MarshalHelpers.SIZEOF_INT32; + +/** + * Marshal {@link BlackLevelPattern} to/from {@link #TYPE_INT32} {@code x 4} + */ +public class MarshalQueryableBlackLevelPattern implements MarshalQueryable<BlackLevelPattern> { + private static final int SIZE = SIZEOF_INT32 * BlackLevelPattern.COUNT; + + private class MarshalerBlackLevelPattern extends Marshaler<BlackLevelPattern> { + protected MarshalerBlackLevelPattern(TypeReference<BlackLevelPattern> typeReference, + int nativeType) { + super(MarshalQueryableBlackLevelPattern.this, typeReference, nativeType); + } + + @Override + public void marshal(BlackLevelPattern value, ByteBuffer buffer) { + for (int i = 0; i < BlackLevelPattern.COUNT / 2; ++i) { + for (int j = 0; j < BlackLevelPattern.COUNT / 2; ++j) { + buffer.putInt(value.getOffsetForIndex(j, i)); + } + } + } + + @Override + public BlackLevelPattern unmarshal(ByteBuffer buffer) { + int[] channelOffsets = new int[BlackLevelPattern.COUNT]; + for (int i = 0; i < BlackLevelPattern.COUNT; ++i) { + channelOffsets[i] = buffer.getInt(); + } + return new BlackLevelPattern(channelOffsets); + } + + @Override + public int getNativeSize() { + return SIZE; + } + } + + @Override + public Marshaler<BlackLevelPattern> createMarshaler( + TypeReference<BlackLevelPattern> managedType, int nativeType) { + return new MarshalerBlackLevelPattern(managedType, nativeType); + } + + @Override + public boolean isTypeMappingSupported( + TypeReference<BlackLevelPattern> managedType, int nativeType) { + return nativeType == TYPE_INT32 && + (BlackLevelPattern.class.equals(managedType.getType())); + } +} diff --git a/core/java/android/hardware/camera2/params/BlackLevelPattern.java b/core/java/android/hardware/camera2/params/BlackLevelPattern.java new file mode 100644 index 0000000..a09f3d9 --- /dev/null +++ b/core/java/android/hardware/camera2/params/BlackLevelPattern.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.params; + +import java.util.Arrays; + +import static com.android.internal.util.Preconditions.checkNotNull; + +/** + * Immutable class to store a 4-element vector of integers corresponding to a 2x2 pattern + * of color channel offsets used for the black level offsets of each color channel. + */ +public final class BlackLevelPattern { + + /** + * The number of offsets in this vector. + */ + public static final int COUNT = 4; + + /** + * Create a new {@link BlackLevelPattern} from a given offset array. + * + * <p>The given offset array must contain offsets for each color channel in + * a 2x2 pattern corresponding to the color filter arrangement. Offsets are + * given in row-column scan order.</p> + * + * @param offsets an array containing a 2x2 pattern of offsets. + * + * @throws IllegalArgumentException if the given array has an incorrect length. + * @throws NullPointerException if the given array is null. + * @hide + */ + public BlackLevelPattern(int[] offsets) { + if (offsets == null) { + throw new NullPointerException("Null offsets array passed to constructor"); + } + if (offsets.length < COUNT) { + throw new IllegalArgumentException("Invalid offsets array length"); + } + mCfaOffsets = Arrays.copyOf(offsets, COUNT); + } + + /** + * Return the color channel offset for a given index into the array of raw pixel values. + * + * @param column the column index in the the raw pixel array. + * @param row the row index in the raw pixel array. + * @return a color channel offset. + * + * @throws IllegalArgumentException if a column or row given is negative. + */ + public int getOffsetForIndex(int column, int row) { + if (row < 0 || column < 0) { + throw new IllegalArgumentException("column, row arguments must be positive"); + } + return mCfaOffsets[((row & 1) << 1) | (column & 1)]; + } + + /** + * Copy the ColorChannel offsets into the destination vector. + * + * <p>Offsets are given in row-column scan order for a given 2x2 color pattern.</p> + * + * @param destination an array big enough to hold at least {@value #COUNT} elements after the + * {@code offset} + * @param offset a non-negative offset into the array + * + * @throws IllegalArgumentException if the offset is invalid. + * @throws ArrayIndexOutOfBoundsException if the destination vector is too small. + * @throws NullPointerException if the destination is null. + */ + public void copyTo(int[] destination, int offset) { + checkNotNull(destination, "destination must not be null"); + if (offset < 0) { + throw new IllegalArgumentException("Null offset passed to copyTo"); + } + if (destination.length - offset < COUNT) { + throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); + } + for (int i = 0; i < COUNT; ++i) { + destination[offset + i] = mCfaOffsets[i]; + } + } + + /** + * Check if this {@link BlackLevelPattern} is equal to another {@link BlackLevelPattern}. + * + * <p>Two vectors are only equal if and only if each of the respective elements is equal.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (this == obj) { + return true; + } else if (obj instanceof BlackLevelPattern) { + final BlackLevelPattern other = (BlackLevelPattern) obj; + return Arrays.equals(other.mCfaOffsets, mCfaOffsets); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Arrays.hashCode(mCfaOffsets); + } + + private final int[] mCfaOffsets; +} diff --git a/core/java/android/hardware/camera2/params/RggbChannelVector.java b/core/java/android/hardware/camera2/params/RggbChannelVector.java index 30591f6..cf3e1de 100644 --- a/core/java/android/hardware/camera2/params/RggbChannelVector.java +++ b/core/java/android/hardware/camera2/params/RggbChannelVector.java @@ -146,7 +146,7 @@ public final class RggbChannelVector { */ public void copyTo(final float[] destination, final int offset) { checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT) { + if (destination.length - offset < COUNT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } @@ -167,11 +167,9 @@ public final class RggbChannelVector { public boolean equals(final Object obj) { if (obj == null) { return false; - } - if (this == obj) { + } else if (this == obj) { return true; - } - if (obj instanceof RggbChannelVector) { + } else if (obj instanceof RggbChannelVector) { final RggbChannelVector other = (RggbChannelVector) obj; return mRed == other.mRed && mGreenEven == other.mGreenEven && diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index bb0f4ba..c43e21a 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -24,6 +24,29 @@ public abstract class HdmiClient { mService = service; } + /** + * Send a key event to other logical device. + * + * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. + * @param isPressed true if this is key press event + */ + public void sendKeyEvent(int keyCode, boolean isPressed) { + try { + mService.sendKeyEvent(getDeviceType(), keyCode, isPressed); + } catch (RemoteException e) { + Log.e(TAG, "queryDisplayStatus threw exception ", e); + } + } + + /** + * Send vendor-specific command. + * + * @param targetAddress address of the target device + * @param params vendor-specific parameter. For <Vendor Command With ID> do not + * include the first 3 bytes (vendor ID). + * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>. + * {@code false} if the command will be <Vendor Command> + */ public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) { try { mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId); @@ -32,6 +55,11 @@ public abstract class HdmiClient { } } + /** + * Add a listener used to receive incoming vendor-specific command. + * + * @param listener listener object + */ public void addVendorCommandListener(VendorCommandListener listener) { try { mService.addVendorCommandListener(getListenerWrapper(listener), getDeviceType()); diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 3a477fb..f3931e3 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -43,7 +43,7 @@ interface IHdmiControlService { void addDeviceEventListener(IHdmiDeviceEventListener listener); void deviceSelect(int logicalAddress, IHdmiControlCallback callback); void portSelect(int portId, IHdmiControlCallback callback); - void sendKeyEvent(int keyCode, boolean isPressed); + void sendKeyEvent(int deviceType, int keyCode, boolean isPressed); List<HdmiPortInfo> getPortInfo(); boolean canChangeSystemAudioMode(); boolean getSystemAudioMode(); diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java index 4c074e9..2d7b7e1 100644 --- a/core/java/android/hardware/location/GeofenceHardware.java +++ b/core/java/android/hardware/location/GeofenceHardware.java @@ -56,8 +56,6 @@ public final class GeofenceHardware { /** * Constant for geofence monitoring done by the Fused hardware. - * - * @hide */ public static final int MONITORING_TYPE_FUSED_HARDWARE = 1; @@ -128,8 +126,6 @@ public final class GeofenceHardware { /** * The constant used to indicate that the operation failed due to insufficient memory. - * - * @hide */ public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6; diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/hardware/soundtrigger/DspInfo.java index 0862309..517159d 100644 --- a/core/java/android/service/voice/DspInfo.java +++ b/core/java/android/hardware/soundtrigger/DspInfo.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,12 +14,13 @@ * limitations under the License. */ -package android.service.voice; +package android.hardware.soundtrigger; import java.util.UUID; /** * Properties of the DSP hardware on the device. + * * @hide */ public class DspInfo { diff --git a/core/java/android/hardware/soundtrigger/Keyphrase.aidl b/core/java/android/hardware/soundtrigger/Keyphrase.aidl new file mode 100644 index 0000000..d9853a7 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/Keyphrase.aidl @@ -0,0 +1,4 @@ +package android.hardware.soundtrigger; + +// @hide +parcelable Keyphrase;
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/Keyphrase.java b/core/java/android/hardware/soundtrigger/Keyphrase.java new file mode 100644 index 0000000..42fd350 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/Keyphrase.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.soundtrigger; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A Voice Keyphrase. + * + * @hide + */ +public class Keyphrase implements Parcelable { + /** A unique identifier for this keyphrase */ + public final int id; + /** A hint text to display corresponding to this keyphrase, e.g. "Hello There". */ + public final String hintText; + /** The locale of interest when using this Keyphrase. */ + public String locale; + + public static final Parcelable.Creator<Keyphrase> CREATOR + = new Parcelable.Creator<Keyphrase>() { + public Keyphrase createFromParcel(Parcel in) { + return Keyphrase.fromParcel(in); + } + + public Keyphrase[] newArray(int size) { + return new Keyphrase[size]; + } + }; + + private static Keyphrase fromParcel(Parcel in) { + return new Keyphrase(in.readInt(), in.readString(), in.readString()); + } + + public Keyphrase(int id, String hintText, String locale) { + this.id = id; + this.hintText = hintText; + this.locale = locale; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(hintText); + dest.writeString(locale); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((hintText == null) ? 0 : hintText.hashCode()); + result = prime * result + id; + result = prime * result + ((locale == null) ? 0 : locale.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Keyphrase other = (Keyphrase) obj; + if (hintText == null) { + if (other.hintText != null) + return false; + } else if (!hintText.equals(other.hintText)) + return false; + if (id != other.id) + return false; + if (locale == null) { + if (other.locale != null) + return false; + } else if (!locale.equals(other.locale)) + return false; + return true; + } +} diff --git a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java index ebe41ce..2f5de6a 100644 --- a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.service.voice; +package android.hardware.soundtrigger; import android.Manifest; import android.content.Intent; @@ -24,6 +24,7 @@ import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.service.voice.AlwaysOnHotwordDetector; import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; @@ -34,7 +35,11 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.List; -/** @hide */ +/** + * Enrollment information about the different available keyphrases. + * + * @hide + */ public class KeyphraseEnrollmentInfo { private static final String TAG = "KeyphraseEnrollmentInfo"; /** @@ -53,10 +58,14 @@ public class KeyphraseEnrollmentInfo { public static final String ACTION_MANAGE_VOICE_KEYPHRASES = "com.android.intent.action.MANAGE_VOICE_KEYPHRASES"; /** - * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase. + * Intent extra: The intent extra for the specific manage action that needs to be performed. + * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, + * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} + * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}. */ - public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL = - "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL"; + public static final String EXTRA_VOICE_KEYPHRASE_ACTION = + "com.android.intent.extra.VOICE_KEYPHRASE_ACTION"; + /** * Intent extra: The hint text to be shown on the voice keyphrase management UI. */ @@ -68,7 +77,7 @@ public class KeyphraseEnrollmentInfo { public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE"; - private KeyphraseInfo[] mKeyphrases; + private KeyphraseMetadata[] mKeyphrases; private String mEnrollmentPackage; private String mParseError; @@ -156,8 +165,8 @@ public class KeyphraseEnrollmentInfo { && !searchKeyphraseSupportedLocales.isEmpty()) { supportedLocales = searchKeyphraseSupportedLocales.split(","); } - mKeyphrases = new KeyphraseInfo[1]; - mKeyphrases[0] = new KeyphraseInfo( + mKeyphrases = new KeyphraseMetadata[1]; + mKeyphrases[0] = new KeyphraseMetadata( searchKeyphraseId, searchKeyphrase, supportedLocales); } else { mParseError = "searchKeyphraseId not specified in meta-data"; @@ -188,7 +197,7 @@ public class KeyphraseEnrollmentInfo { * @return An array of available keyphrases that can be enrolled on the system. * It may be null if no keyphrases can be enrolled. */ - public KeyphraseInfo[] getKeyphrases() { + public KeyphraseMetadata[] listKeyphraseMetadata() { return mKeyphrases; } @@ -196,51 +205,56 @@ public class KeyphraseEnrollmentInfo { * Returns an intent to launch an activity that manages the given keyphrase * for the locale. * - * @param enroll Indicates if the intent should enroll the user or un-enroll them. + * @param action The enrollment related action that this intent is supposed to perform. + * This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, + * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} + * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL} * @param keyphrase The keyphrase that the user needs to be enrolled to. * @param locale The locale for which the enrollment needs to be performed. + * This is a Java locale, for example "en_US". * @return An {@link Intent} to manage the keyphrase. This can be null if managing the * given keyphrase/locale combination isn't possible. */ - public Intent getManageKeyphraseIntent(boolean enroll, String keyphrase, String locale) { + public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) { if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { Slog.w(TAG, "No enrollment application exists"); return null; } - if (isKeyphraseEnrollmentSupported(keyphrase, locale)) { + if (getKeyphraseMetadata(keyphrase, locale) != null) { Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES) .setPackage(mEnrollmentPackage) .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase) - .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale); - if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true); + .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale) + .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action); return intent; } return null; } /** - * Indicates if enrollment is supported for the given keyphrase & locale. + * Gets the {@link KeyphraseMetadata} for the given keyphrase and locale, null if any metadata + * isn't available for the given combination. * * @param keyphrase The keyphrase that the user needs to be enrolled to. * @param locale The locale for which the enrollment needs to be performed. + * This is a Java locale, for example "en_US". * @return true, if an enrollment client supports the given keyphrase and the given locale. */ - public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) { + public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) { if (mKeyphrases == null || mKeyphrases.length == 0) { Slog.w(TAG, "Enrollment application doesn't support keyphrases"); - return false; + return null; } - for (KeyphraseInfo keyphraseInfo : mKeyphrases) { + for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) { // Check if the given keyphrase is supported in the locale provided by // the enrollment application. - String supportedKeyphrase = keyphraseInfo.keyphrase; - if (supportedKeyphrase.equalsIgnoreCase(keyphrase) - && keyphraseInfo.supportedLocales.contains(locale)) { - return true; + if (keyphraseMetadata.supportsPhrase(keyphrase) + && keyphraseMetadata.supportsLocale(locale)) { + return keyphraseMetadata; } } - Slog.w(TAG, "Enrollment application doesn't support the given keyphrase"); - return false; + Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale"); + return null; } } diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java new file mode 100644 index 0000000..03a4939 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java @@ -0,0 +1,60 @@ +/** + * 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.soundtrigger; + +import android.util.ArraySet; + +/** + * A Voice Keyphrase metadata read from the enrollment application. + * + * @hide + */ +public class KeyphraseMetadata { + public final int id; + public final String keyphrase; + public final ArraySet<String> supportedLocales; + + public KeyphraseMetadata(int id, String keyphrase, String[] supportedLocales) { + this.id = id; + this.keyphrase = keyphrase; + this.supportedLocales = new ArraySet<String>(supportedLocales.length); + for (String locale : supportedLocales) { + this.supportedLocales.add(locale); + } + } + + @Override + public String toString() { + return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales; + } + + /** + * @return Indicates if we support the given phrase. + */ + public boolean supportsPhrase(String phrase) { + // TODO(sansid): Come up with a scheme for custom keyphrases that should always match. + return keyphrase.equalsIgnoreCase(phrase); + } + + /** + * @return Indicates if we support the given locale. + */ + public boolean supportsLocale(String locale) { + // TODO(sansid): Come up with a scheme for keyphrases that are available in all locales. + return supportedLocales.contains(locale); + } +} diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl new file mode 100644 index 0000000..39b33cc --- /dev/null +++ b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl @@ -0,0 +1,4 @@ +package android.hardware.soundtrigger; + +// @hide +parcelable KeyphraseSoundModel;
\ No newline at end of file diff --git a/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java new file mode 100644 index 0000000..4ddba6a --- /dev/null +++ b/core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java @@ -0,0 +1,68 @@ +package android.hardware.soundtrigger; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.UUID; + +/** + * A KeyphraseSoundModel is a sound model capable of detecting voice keyphrases. + * It contains data needed by the hardware to detect a given number of key phrases + * and the list of corresponding {@link Keyphrase}s. + * + * @hide + */ +public class KeyphraseSoundModel implements Parcelable { + + /** Key phrases in this sound model */ + public final Keyphrase[] keyphrases; + public final byte[] data; + public final UUID uuid; + + public static final Parcelable.Creator<KeyphraseSoundModel> CREATOR + = new Parcelable.Creator<KeyphraseSoundModel>() { + public KeyphraseSoundModel createFromParcel(Parcel in) { + return KeyphraseSoundModel.fromParcel(in); + } + + public KeyphraseSoundModel[] newArray(int size) { + return new KeyphraseSoundModel[size]; + } + }; + + public KeyphraseSoundModel(UUID uuid, byte[] data,Keyphrase[] keyPhrases) { + this.uuid = uuid; + this.data = data; + this.keyphrases = keyPhrases; + } + + private static KeyphraseSoundModel fromParcel(Parcel in) { + UUID uuid = UUID.fromString(in.readString()); + int dataLength = in.readInt(); + byte[] data = null; + if (dataLength > 0) { + data = new byte[in.readInt()]; + in.readByteArray(data); + } + Keyphrase[] keyphrases = + (Keyphrase[]) in.readParcelableArray(Keyphrase.class.getClassLoader()); + return new KeyphraseSoundModel(uuid, data, keyphrases); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(uuid.toString()); + if (data != null) { + dest.writeInt(data.length); + dest.writeByteArray(data); + } else { + dest.writeInt(0); + } + dest.writeParcelableArray(keyphrases, 0); + } +} diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 7a4e5a5..1f48a92 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java new file mode 100644 index 0000000..0be068d --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTriggerHelper.java @@ -0,0 +1,217 @@ +/** + * 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.soundtrigger; + +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; +import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.util.Slog; +import android.util.SparseArray; + +import java.util.ArrayList; + +/** + * Helper for {@link SoundTrigger} APIs. + * Currently this just acts as an abstraction over all SoundTrigger API calls. + * + * @hide + */ +public class SoundTriggerHelper implements SoundTrigger.StatusListener { + static final String TAG = "SoundTriggerHelper"; + // TODO: Remove this. + static final int TEMP_KEYPHRASE_ID = 1; + + /** + * Return codes for {@link #startRecognition(Keyphrase)}, {@link #stopRecognition(Keyphrase)} + * Note: Keep in sync with AlwaysOnKeyphraseInteractor.java + */ + public static final int STATUS_ERROR = Integer.MIN_VALUE; + public static final int STATUS_OK = 1; + + /** + * States for {@link Listener#onListeningStateChanged(int, int)}. + */ + public static final int STATE_STOPPED = 0; + public static final int STATE_STARTED = 1; + + private static final int INVALID_SOUND_MODEL_HANDLE = -1; + + /** The {@link DspInfo} for the system, or null if none exists. */ + public final DspInfo dspInfo; + + /** The properties for the DSP module */ + private final ModuleProperties mModuleProperties; + private final SoundTriggerModule mModule; + + private final SparseArray<Listener> mListeners; + + private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; + + /** + * The callback for sound trigger events. + */ + public interface Listener { + /** Called when the given keyphrase is spoken. */ + void onKeyphraseSpoken(); + + /** + * Called when the listening state for the given keyphrase changes. + * @param state Indicates the current state. + */ + void onListeningStateChanged(int state); + } + + public SoundTriggerHelper() { + ArrayList <ModuleProperties> modules = new ArrayList<>(); + int status = SoundTrigger.listModules(modules); + mListeners = new SparseArray<>(1); + if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { + // TODO: Figure out how to handle errors in listing the modules here. + dspInfo = null; + mModuleProperties = null; + mModule = null; + } else { + // TODO: Figure out how to determine which module corresponds to the DSP hardware. + mModuleProperties = modules.get(0); + dspInfo = new DspInfo(mModuleProperties.uuid, mModuleProperties.implementor, + mModuleProperties.description, mModuleProperties.version, + mModuleProperties.powerConsumptionMw); + mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null); + } + } + + /** + * @return True, if the given {@link Keyphrase} is supported on DSP. + */ + public boolean isKeyphraseSupported(Keyphrase keyphrase) { + // TODO: We also need to look into a SoundTrigger API that let's us + // query this. For now just return true. + return true; + } + + /** + * @return True, if the given {@link Keyphrase} has been enrolled. + */ + public boolean isKeyphraseEnrolled(Keyphrase keyphrase) { + // TODO: Query VoiceInteractionManagerService + // to list registered sound models. + return false; + } + + /** + * @return True, if a recognition for the given {@link Keyphrase} is active. + */ + public boolean isKeyphraseActive(Keyphrase keyphrase) { + // TODO: Check if the recognition for the keyphrase is currently active. + return false; + } + + /** + * Starts recognition for the given {@link Keyphrase}. + * + * @param keyphraseId The identifier of the keyphrase for which + * the recognition is to be started. + * @param listener The listener for the recognition events related to the given keyphrase. + * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + */ + public int startRecognition(int keyphraseId, Listener listener) { + if (dspInfo == null || mModule == null) { + Slog.w(TAG, "Attempting startRecognition without the capability"); + return STATUS_ERROR; + } + + if (mListeners.get(keyphraseId) != listener) { + if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) { + Slog.w(TAG, "Canceling previous recognition"); + // TODO: Inspect the return codes here. + mModule.unloadSoundModel(mCurrentSoundModelHandle); + } + mListeners.get(keyphraseId).onListeningStateChanged(STATE_STOPPED); + } + + // Register the new listener. This replaces the old one. + // There can only be a maximum of one active listener for a keyphrase + // at any given time. + mListeners.put(keyphraseId, listener); + // TODO: Get the sound model for the given keyphrase here. + // mModule.loadSoundModel(model, soundModelHandle); + // mModule.startRecognition(soundModelHandle, data); + // mCurrentSoundModelHandle = soundModelHandle; + return STATUS_ERROR; + } + + /** + * Stops recognition for the given {@link Keyphrase} if a recognition is currently active. + * + * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + */ + public int stopRecognition(int id, Listener listener) { + if (dspInfo == null || mModule == null) { + Slog.w(TAG, "Attempting stopRecognition without the capability"); + return STATUS_ERROR; + } + + if (mListeners.get(id) != listener) { + Slog.w(TAG, "Attempting stopRecognition for another recognition"); + return STATUS_ERROR; + } else { + // Stop recognition if it's the current one, ignore otherwise. + // TODO: Inspect the return codes here. + mModule.stopRecognition(mCurrentSoundModelHandle); + mModule.unloadSoundModel(mCurrentSoundModelHandle); + mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; + return STATUS_OK; + } + } + + //---- SoundTrigger.StatusListener methods + @Override + public void onRecognition(RecognitionEvent event) { + // Check which keyphrase triggered, and fire the appropriate event. + // TODO: Get the keyphrase out of the event and fire events on it. + // For now, as a nasty workaround, we fire all events to the listener for + // keyphrase with TEMP_KEYPHRASE_ID. + + switch (event.status) { + case SoundTrigger.RECOGNITION_STATUS_SUCCESS: + // TODO: The keyphrase should come from the recognition event + // as it may be for a different keyphrase than the current one. + if (mListeners.get(TEMP_KEYPHRASE_ID) != null) { + mListeners.get(TEMP_KEYPHRASE_ID).onKeyphraseSpoken(); + } + break; + case SoundTrigger.RECOGNITION_STATUS_ABORT: + // TODO: The keyphrase should come from the recognition event + // as it may be for a different keyphrase than the current one. + if (mListeners.get(TEMP_KEYPHRASE_ID) != null) { + mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED); + } + break; + case SoundTrigger.RECOGNITION_STATUS_FAILURE: + // TODO: The keyphrase should come from the recognition event + // as it may be for a different keyphrase than the current one. + if (mListeners.get(TEMP_KEYPHRASE_ID) != null) { + mListeners.get(TEMP_KEYPHRASE_ID).onListeningStateChanged(STATE_STOPPED); + } + break; + } + } + + @Override + public void onServiceDied() { + // TODO: Figure out how to restart the recognition here. + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 3417de1..8423d09 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -53,6 +53,7 @@ import android.view.WindowManager.BadTokenException; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -251,18 +252,6 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_VISIBLE = 0x2; - /** - * The IME does not require cursor/anchor position. - */ - public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0x0; - - /** - * Passing this flag into a call to {@link #setCursorAnchorMonitorMode(int)} will result in - * the cursor rectangle being provided in screen coordinates to subsequent - * {@link #onUpdateCursor(Rect)} callbacks. - */ - public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1; - InputMethodManager mImm; int mTheme = 0; @@ -1722,8 +1711,9 @@ public class InputMethodService extends AbstractInputMethodService { * Called when the application has reported a new location of its text cursor. This is only * called if explicitly requested by the input method. The default implementation does nothing. * @param newCursor The new cursor position, in screen coordinates if the input method calls - * {@link #setCursorAnchorMonitorMode} with {@link #CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT}. - * Otherwise, this is in local coordinates. + * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with + * {@link CursorAnchorInfoRequest#FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}. Otherwise, + * this is in local coordinates. */ public void onUpdateCursor(Rect newCursor) { // Intentionally empty @@ -1741,13 +1731,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Update the cursor/anthor monitor mode. - */ - public void setCursorAnchorMonitorMode(int monitorMode) { - mImm.setCursorAnchorMonitorMode(mToken, monitorMode); - } - - /** * Close this input method's soft input area, removing it from the display. * The input method will continue running, but the user can no longer use * it to generate input by touching the screen. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fb4912f..a7e03fc 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -743,7 +743,7 @@ public class ConnectivityManager { * network type or {@code null} if the type is not * supported by the device. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public NetworkInfo getNetworkInfo(int networkType) { @@ -755,13 +755,34 @@ public class ConnectivityManager { } /** + * Returns connection status information about a particular + * Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + */ + public NetworkInfo getNetworkInfo(Network network) { + try { + return mService.getNetworkInfoForNetwork(network); + } catch (RemoteException e) { + return null; + } + } + + /** * Returns connection status information about all network * types supported by the device. * * @return an array of {@link NetworkInfo} objects. Check each * {@link NetworkInfo#getType} for which type each applies. * - * <p>This method requires the call to hold the permission + * <p>This method requires the caller to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. */ public NetworkInfo[] getAllNetworkInfo() { @@ -773,6 +794,23 @@ public class ConnectivityManager { } /** + * Returns an array of all {@link Network} currently tracked by the + * framework. + * + * @return an array of {@link Network} objects. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. + */ + public Network[] getAllNetworks() { + try { + return mService.getAllNetworks(); + } catch (RemoteException e) { + return null; + } + } + + /** * Returns details about the Provisioning or currently active default data network. When * connected, this network is the default route for outgoing connections. * You should always check {@link NetworkInfo#isConnected()} before initiating @@ -1462,6 +1500,20 @@ public class ConnectivityManager { } /** + * Get the set of tethered dhcp ranges. + * + * @return an array of 0 or more {@code String} of tethered dhcp ranges. + * {@hide} + */ + public String[] getTetheredDhcpRanges() { + try { + return mService.getTetheredDhcpRanges(); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** * Attempt to tether the named interface. This will setup a dhcp server * on the interface, forward and NAT IP packets and forward DNS requests * to the best active upstream network interface. Note that if no upstream diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 51d9b8c..b9c6491 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -48,7 +48,9 @@ interface IConnectivityManager NetworkInfo getActiveNetworkInfo(); NetworkInfo getActiveNetworkInfoForUid(int uid); NetworkInfo getNetworkInfo(int networkType); + NetworkInfo getNetworkInfoForNetwork(in Network network); NetworkInfo[] getAllNetworkInfo(); + Network[] getAllNetworks(); NetworkInfo getProvisioningOrActiveNetworkInfo(); @@ -91,6 +93,8 @@ interface IConnectivityManager String[] getTetheringErroredIfaces(); + String[] getTetheredDhcpRanges(); + String[] getTetherableUsbRegexs(); String[] getTetherableWifiRegexs(); @@ -111,8 +115,6 @@ interface IConnectivityManager void setDataDependency(int networkType, boolean met); - boolean protectVpn(in ParcelFileDescriptor socket); - boolean prepareVpn(String oldPackage, String newPackage); ParcelFileDescriptor establishVpn(in VpnConfig config); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 3d0874b..41eab02 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -92,6 +92,20 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; + /** + * Sent by the NetworkAgent to ConnectivityService to add new UID ranges + * to be forced into this Network. For VPNs only. + * obj = UidRange[] to forward + */ + public static final int EVENT_UID_RANGES_ADDED = BASE + 5; + + /** + * Sent by the NetworkAgent to ConnectivityService to remove UID ranges + * from being forced into this Network. For VPNs only. + * obj = UidRange[] to stop forwarding + */ + public static final int EVENT_UID_RANGES_REMOVED = BASE + 6; + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score) { super(looper); @@ -194,6 +208,22 @@ public abstract class NetworkAgent extends Handler { } /** + * Called by the VPN code when it wants to add ranges of UIDs to be routed + * through the VPN network. + */ + public void addUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_ADDED, ranges); + } + + /** + * Called by the VPN code when it wants to remove ranges of UIDs from being routed + * through the VPN network. + */ + public void removeUidRanges(UidRange[] ranges) { + queueOrSendMessage(EVENT_UID_RANGES_REMOVED, ranges); + } + + /** * Called when ConnectivityService has indicated they no longer want this network. * The parent factory should (previously) have received indication of the change * as well, either canceling NetworkRequests or altering their score such that this diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 00200d0..53f9fcd 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -18,6 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.Log; import java.lang.IllegalArgumentException; @@ -56,6 +57,7 @@ public final class NetworkCapabilities implements Parcelable { mTransportTypes = nc.mTransportTypes; mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps; mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; + mNetworkSpecifier = nc.mNetworkSpecifier; } } @@ -64,7 +66,7 @@ public final class NetworkCapabilities implements Parcelable { * by any Network that matches all of them. */ private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED); + (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); /** * Indicates this is a network that has the ability to reach the @@ -158,9 +160,15 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_TRUSTED = 14; + /* + * Indicates that this network is not a VPN. This capability is set by default and should be + * explicitly cleared when creating VPN networks. + */ + public static final int NET_CAPABILITY_NOT_VPN = 15; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TRUSTED; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VPN; /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -271,8 +279,13 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int TRANSPORT_ETHERNET = 3; + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; - private static final int MAX_TRANSPORT = TRANSPORT_ETHERNET; + private static final int MAX_TRANSPORT = TRANSPORT_VPN; /** * Adds the given transport type to this {@code NetworkCapability} instance. @@ -292,6 +305,7 @@ public final class NetworkCapabilities implements Parcelable { throw new IllegalArgumentException("TransportType out of range"); } mTransportTypes |= 1 << transportType; + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking return this; } @@ -307,6 +321,7 @@ public final class NetworkCapabilities implements Parcelable { throw new IllegalArgumentException("TransportType out of range"); } mTransportTypes &= ~(1 << transportType); + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking return this; } @@ -426,6 +441,62 @@ public final class NetworkCapabilities implements Parcelable { this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps); } + private String mNetworkSpecifier; + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + * </p> + * The interpretation of this {@code String} is bearer specific and bearers that use + * it should document their particulars. For example, Bluetooth may use some sort of + * device id while WiFi could used SSID and/or BSSID. Cellular may use carrier SPN (name) + * or Subscription ID. + * + * @param networkSpecifier An {@code String} of opaque format used to specify the bearer + * specific network specifier where the bearer has a choice of + * networks. + * @hide + */ + public void setNetworkSpecifier(String networkSpecifier) { + if (TextUtils.isEmpty(networkSpecifier) == false && Long.bitCount(mTransportTypes) != 1) { + throw new IllegalStateException("Must have a single transport specified to use " + + "setNetworkSpecifier"); + } + mNetworkSpecifier = networkSpecifier; + } + + /** + * Gets the optional bearer specific network specifier. + * + * @return The optional {@code String} specifying the bearer specific network specifier. + * See {@link #setNetworkSpecifier}. + * @hide + */ + public String getNetworkSpecifier() { + return mNetworkSpecifier; + } + + private void combineSpecifiers(NetworkCapabilities nc) { + String otherSpecifier = nc.getNetworkSpecifier(); + if (TextUtils.isEmpty(otherSpecifier)) return; + if (TextUtils.isEmpty(mNetworkSpecifier) == false) { + throw new IllegalStateException("Can't combine two networkSpecifiers"); + } + setNetworkSpecifier(otherSpecifier); + } + private boolean satisfiedBySpecifier(NetworkCapabilities nc) { + return (TextUtils.isEmpty(mNetworkSpecifier) || + mNetworkSpecifier.equals(nc.mNetworkSpecifier)); + } + private boolean equalsSpecifier(NetworkCapabilities nc) { + if (TextUtils.isEmpty(mNetworkSpecifier)) { + return TextUtils.isEmpty(nc.mNetworkSpecifier); + } else { + return mNetworkSpecifier.equals(nc.mNetworkSpecifier); + } + } + /** * Combine a set of Capabilities to this one. Useful for coming up with the complete set * {@hide} @@ -434,6 +505,7 @@ public final class NetworkCapabilities implements Parcelable { combineNetCapabilities(nc); combineTransportTypes(nc); combineLinkBandwidths(nc); + combineSpecifiers(nc); } /** @@ -444,7 +516,8 @@ public final class NetworkCapabilities implements Parcelable { return (nc != null && satisfiedByNetCapabilities(nc) && satisfiedByTransportTypes(nc) && - satisfiedByLinkBandwidths(nc)); + satisfiedByLinkBandwidths(nc) && + satisfiedBySpecifier(nc)); } @Override @@ -453,7 +526,8 @@ public final class NetworkCapabilities implements Parcelable { NetworkCapabilities that = (NetworkCapabilities)obj; return (equalsNetCapabilities(that) && equalsTransportTypes(that) && - equalsLinkBandwidths(that)); + equalsLinkBandwidths(that) && + equalsSpecifier(that)); } @Override @@ -463,7 +537,8 @@ public final class NetworkCapabilities implements Parcelable { ((int)(mTransportTypes & 0xFFFFFFFF) * 5) + ((int)(mTransportTypes >> 32) * 7) + (mLinkUpBandwidthKbps * 11) + - (mLinkDownBandwidthKbps * 13)); + (mLinkDownBandwidthKbps * 13) + + (TextUtils.isEmpty(mNetworkSpecifier) ? 0 : mNetworkSpecifier.hashCode() * 17)); } public int describeContents() { @@ -474,6 +549,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeLong(mTransportTypes); dest.writeInt(mLinkUpBandwidthKbps); dest.writeInt(mLinkDownBandwidthKbps); + dest.writeString(mNetworkSpecifier); } public static final Creator<NetworkCapabilities> CREATOR = new Creator<NetworkCapabilities>() { @@ -484,6 +560,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mTransportTypes = in.readLong(); netCap.mLinkUpBandwidthKbps = in.readInt(); netCap.mLinkDownBandwidthKbps = in.readInt(); + netCap.mNetworkSpecifier = in.readString(); return netCap; } public NetworkCapabilities[] newArray(int size) { @@ -500,6 +577,7 @@ public final class NetworkCapabilities implements Parcelable { case TRANSPORT_WIFI: transports += "WIFI"; break; case TRANSPORT_BLUETOOTH: transports += "BLUETOOTH"; break; case TRANSPORT_ETHERNET: transports += "ETHERNET"; break; + case TRANSPORT_VPN: transports += "VPN"; break; } if (++i < types.length) transports += "|"; } @@ -523,6 +601,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_INTERNET: capabilities += "INTERNET"; break; case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break; case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break; + case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break; } if (++i < types.length) capabilities += "&"; } @@ -532,6 +611,9 @@ public final class NetworkCapabilities implements Parcelable { String dnBand = ((mLinkDownBandwidthKbps > 0) ? " LinkDnBandwidth>=" + mLinkDownBandwidthKbps + "Kbps" : ""); - return "[" + transports + capabilities + upBand + dnBand + "]"; + String specifier = (mNetworkSpecifier == null ? + "" : " Specifier: <" + mNetworkSpecifier + ">"); + + return "[" + transports + capabilities + upBand + dnBand + specifier + "]"; } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 36dc573..83bdfaa 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -153,6 +153,25 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps); return this; } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + * </p> + * The interpretation of this {@code String} is bearer specific and bearers that use + * it should document their particulars. For example, Bluetooth may use some sort of + * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn. + * + * @param networkSpecifier An {@code String} of opaque format used to specify the bearer + * specific network specifier where the bearer has a choice of + * networks. + */ + public Builder setNetworkSpecifier(String networkSpecifier) { + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + return this; + } } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index c4b17b6..aa1e123 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -155,6 +155,13 @@ public class NetworkUtils { public native static boolean bindSocketToNetwork(int socketfd, int netId); /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public native static boolean protectFromVpn(int socketfd); + + /** * Convert a IPv4 address from an integer to an InetAddress. * @param hostAddress an int corresponding to the IPv4 address in network byte order */ diff --git a/core/java/android/net/UidRange.aidl b/core/java/android/net/UidRange.aidl new file mode 100644 index 0000000..f9be628 --- /dev/null +++ b/core/java/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java new file mode 100644 index 0000000..2e586b3 --- /dev/null +++ b/core/java/android/net/UidRange.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.os.UserHandle.PER_USER_RANGE; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.IllegalArgumentException; + +/** + * An inclusive range of UIDs. + * + * @hide + */ +public final class UidRange implements Parcelable { + public final int start; + public final int stop; + + public UidRange(int startUid, int stopUid) { + if (startUid < 0) throw new IllegalArgumentException("Invalid start UID."); + if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID."); + if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range."); + start = startUid; + stop = stopUid; + } + + public static UidRange createForUser(int userId) { + return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); + } + + public int getStartUser() { + return start / PER_USER_RANGE; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + start; + result = 31 * result + stop; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof UidRange) { + UidRange other = (UidRange) o; + return start == other.start && stop == other.stop; + } + return false; + } + + @Override + public String toString() { + return start + "-" + stop; + } + + // implement the Parcelable interface + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(start); + dest.writeInt(stop); + } + + public static final Creator<UidRange> CREATOR = + new Creator<UidRange>() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); + + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } + }; +} diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 7385dff..5d61de2 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -16,11 +16,16 @@ package android.net; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + import android.app.Activity; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.NetworkUtils; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; @@ -165,19 +170,7 @@ public class VpnService extends Service { * @return {@code true} on success. */ public boolean protect(int socket) { - ParcelFileDescriptor dup = null; - try { - dup = ParcelFileDescriptor.fromFd(socket); - return getService().protectVpn(dup); - } catch (Exception e) { - return false; - } finally { - try { - dup.close(); - } catch (Exception e) { - // ignore - } - } + return NetworkUtils.protectFromVpn(socket); } /** @@ -202,6 +195,52 @@ public class VpnService extends Service { } /** + * Adds a network address to the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is already in use or cannot be assigned to the interface for any other reason. + * + * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to + * be routed over the VPN. @see Builder#allowFamily + * + * @throws {@link IllegalArgumentException} if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * @see Builder#addAddress + */ + public boolean addAddress(InetAddress address, int prefixLength) { + // TODO + return true; + } + + /** + * Removes a network address from the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is not assigned to the VPN interface, or if it is the only address assigned (thus + * cannot be removed), or if the address cannot be removed for any other reason. + * + * After removing an address, if there are no addresses, routes or DNS servers of a particular + * address family (i.e., IPv4 or IPv6) configured on the VPN, that <b>DOES NOT</b> block that + * family from being routed. In other words, once an address family has been allowed, it stays + * allowed for the rest of the VPN's session. @see Builder#allowFamily + * + * @throws {@link IllegalArgumentException} if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + */ + public boolean removeAddress(InetAddress address, int prefixLength) { + // TODO + return true; + } + + /** * Return the communication interface to the service. This method returns * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} * action. Applications overriding this method must identify the intent @@ -322,6 +361,9 @@ public class VpnService extends Service { * addresses are supported. At least one address must be set before * calling {@link #establish}. * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. */ public Builder addAddress(InetAddress address, int prefixLength) { @@ -339,6 +381,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. * @see #addAddress(InetAddress, int) */ @@ -350,6 +395,9 @@ public class VpnService extends Service { * Add a network route to the VPN interface. Both IPv4 and IPv6 * routes are supported. * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the route is invalid. */ public Builder addRoute(InetAddress address, int prefixLength) { @@ -373,6 +421,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the route is invalid. * @see #addRoute(InetAddress, int) */ @@ -385,6 +436,9 @@ public class VpnService extends Service { * addresses are supported. If none is set, the DNS servers of * the default network will be used. * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. */ public Builder addDnsServer(InetAddress address) { @@ -403,6 +457,9 @@ public class VpnService extends Service { * using a numeric address string. See {@link InetAddress} for the * definitions of numeric address formats. * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * * @throws IllegalArgumentException if the address is invalid. * @see #addDnsServer(InetAddress) */ @@ -422,6 +479,95 @@ public class VpnService extends Service { } /** + * Allows traffic from the specified address family. + * + * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is + * added to this VPN, then all outgoing traffic of that family is blocked. If any address, + * route or DNS server is added, that family is allowed. + * + * This method allows an address family to be unblocked even without adding an address, + * route or DNS server of that family. Traffic of that family will then typically + * fall-through to the underlying network if it's supported. + * + * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6). + * {@link IllegalArgumentException} is thrown if it's neither. + * + * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + public Builder allowFamily(int family) { + // TODO + return this; + } + + /** + * Adds an application that's allowed to access the VPN connection. + * + * If this method is called at least once, only applications added through this method (and + * no others) are allowed access. Else (if this method is never called), all applications + * are allowed by default. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addDisallowedApplication} has + * already been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder addAllowedApplication(String packageName) + throws PackageManager.NameNotFoundException { + // TODO + return this; + } + + /** + * Adds an application that's denied access to the VPN connection. + * + * By default, all applications are allowed access, except for those denied through this + * method. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addAllowedApplication} has already + * been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws {@link PackageManager.NameNotFoundException} If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + public Builder addDisallowedApplication(String packageName) + throws PackageManager.NameNotFoundException { + // TODO + return this; + } + + /** + * Allows all apps to bypass this VPN connection. + * + * By default, all traffic from apps is forwarded through the VPN interface and it is not + * possible for apps to side-step the VPN. If this method is called, apps may use methods + * such as {@link ConnectivityManager#setProcessDefaultNetwork} to instead send/receive + * directly over the underlying network or any other network they have permissions for. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + public Builder allowBypass() { + // TODO + return this; + } + + /** * Create a VPN interface using the parameters supplied to this * builder. The interface works on IP packets, and a file descriptor * is returned for the application to access them. Each read diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d0a6f08..c22c4b6 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2076,7 +2076,7 @@ public abstract class BatteryStats implements Parcelable { if (val != 0) hasData = true; } if (hasData) { - dumpLine(pw, 0 /* uid */, category, USER_ACTIVITY_DATA, args); + dumpLine(pw, uid /* uid */, category, USER_ACTIVITY_DATA, args); } } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index eb9ba13..d997e44 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -22,6 +22,7 @@ import android.net.INetworkManagementEventObserver; import android.net.LinkAddress; import android.net.NetworkStats; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.os.INetworkActivityListener; @@ -325,28 +326,14 @@ interface INetworkManagementService void setFirewallUidRule(int uid, boolean allow); /** - * Set all packets from users [uid_start,uid_end] to go through interface iface - * iface must already be set for marked forwarding by {@link setMarkedForwarding} + * Set all packets from users in ranges to go through VPN specified by netId. */ - void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns); + void addVpnUidRanges(int netId, in UidRange[] ranges); /** - * Clears the special routing rules for users [uid_start,uid_end] + * Clears the special VPN rules for users in ranges and VPN specified by netId. */ - void clearUidRangeRoute(String iface, int uid_start, int uid_end); - - /** - * Setup an interface for routing packets marked by {@link setUidRangeRoute} - * - * This sets up a dedicated routing table for packets marked for {@code iface} and adds - * source-NAT rules so that the marked packets have the correct source address. - */ - void setMarkedForwarding(String iface); - - /** - * Removes marked forwarding for an interface - */ - void clearMarkedForwarding(String iface); + void removeVpnUidRanges(int netId, in UidRange[] ranges); /** * Get the SO_MARK associated with routing packets for user {@code uid} @@ -410,9 +397,14 @@ interface INetworkManagementService boolean isNetworkActive(); /** - * Setup a new network. + * Setup a new physical network. + */ + void createPhysicalNetwork(int netId); + + /** + * Setup a new VPN. */ - void createNetwork(int netId); + void createVirtualNetwork(int netId, boolean hasDNS); /** * Remove a network. @@ -437,4 +429,14 @@ interface INetworkManagementService void setPermission(boolean internal, boolean changeNetState, in int[] uids); void clearPermission(in int[] uids); + + /** + * Allow UID to call protect(). + */ + void allowProtect(int uid); + + /** + * Deny UID from calling protect(). + */ + void denyProtect(int uid); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 86c749a..8caea25 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1085,4 +1085,18 @@ public class Process { */ public boolean usingWrapper; } + + /** + * Kill all processes in a process group started for the given + * pid. + * @hide + */ + public static final native int killProcessGroup(int uid, int pid); + + /** + * Remove all process groups. Expected to be called when ActivityManager + * is restarted. + * @hide + */ + public static final native void removeAllProcessGroups(); } diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index 928be6c..e4e753e 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -308,7 +308,7 @@ public final class PrintDocumentInfo implements Parcelable { public Builder setPageCount(int pageCount) { if (pageCount < 0 && pageCount != PAGE_COUNT_UNKNOWN) { throw new IllegalArgumentException("pageCount" - + " must be greater than or euqal to zero or" + + " must be greater than or equal to zero or" + " DocumentInfo#PAGE_COUNT_UNKNOWN"); } mPrototype.mPageCount = pageCount; @@ -338,6 +338,12 @@ public final class PrintDocumentInfo implements Parcelable { * @return The new instance. */ public PrintDocumentInfo build() { + // Zero pages is the same as unknown as in this case + // we will have to ask for all pages and look a the + // wiritten PDF file for the page count. + if (mPrototype.mPageCount == 0) { + mPrototype.mPageCount = PAGE_COUNT_UNKNOWN; + } return new PrintDocumentInfo(mPrototype); } } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index acc74d8..760f2a5 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -20,6 +20,7 @@ package android.provider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Callable; @@ -87,6 +88,27 @@ public class CallLog { public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; /** + * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and + * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be + * filtered for a particular call type. + * + * Applications implementing a call log UI should check for this extra, and display a + * filtered list of calls based on the specified call type. If not applicable within the + * application's UI, it should be silently ignored. + * + * <p> + * The following example brings up the call log, showing only missed calls. + * <pre> + * Intent intent = new Intent(Intent.ACTION_VIEW); + * intent.setType(CallLog.Calls.CONTENT_TYPE); + * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE); + * startActivity(intent); + * </pre> + * </p> + */ + public static final String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter"; + + /** * Content uri used to access call log entries, including voicemail records. You must have * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log. */ @@ -127,6 +149,18 @@ public class CallLog { public static final int VOICEMAIL_TYPE = 4; /** + * Bit-mask describing features of the call (e.g. video). + * + * <P>Type: INTEGER (int)</P> + */ + public static final String FEATURES = "features"; + + /** Call had no associated features (e.g. voice-only). */ + public static final int FEATURES_NONE = 0x0; + /** Call had video. */ + public static final int FEATURES_VIDEO = 0x1; + + /** * The phone number as the user entered it. * <P>Type: TEXT</P> */ @@ -180,6 +214,12 @@ public class CallLog { public static final String DURATION = "duration"; /** + * The data usage of the call in bytes. + * <P>Type: INTEGER (long)</P> + */ + public static final String DATA_USAGE = "data_usage"; + + /** * Whether or not the call has been acknowledged * <P>Type: INTEGER (boolean)</P> */ @@ -302,14 +342,18 @@ public class CallLog { * is set by the network and denotes the number presenting rules for * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" + * @param features features of the call (e.g. Video). * @param account The account object describing the provider of the call * @param start time stamp for the call in milliseconds * @param duration call duration in seconds + * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for + * the call. * * {@hide} */ public static Uri addCall(CallerInfo ci, Context context, String number, - int presentation, int callType, PhoneAccount account, long start, int duration) { + int presentation, int callType, int features, PhoneAccount account, long start, + int duration, Long dataUsage) { final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; @@ -346,8 +390,12 @@ public class CallLog { values.put(NUMBER, number); values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation)); values.put(TYPE, Integer.valueOf(callType)); + values.put(FEATURES, features); values.put(DATE, Long.valueOf(start)); values.put(DURATION, Long.valueOf(duration)); + if (dataUsage != null) { + values.put(DATA_USAGE, dataUsage); + } values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); values.put(PHONE_ACCOUNT_ID, accountId); values.put(NEW, Integer.valueOf(1)); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index cfe926d..93f834a 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1670,6 +1670,24 @@ public final class ContactsContract { */ public static final String CONTENT_VCARD_TYPE = "text/x-vcard"; + + /** + * Mimimal ID for corp contacts returned from + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. + * + * @hide + */ + public static long CORP_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30 + + /** + * Return TRUE if a contact ID is from the contacts provider on the corp profile. + * + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact. + */ + public static boolean isCorpContactId(long contactId) { + return (contactId >= CORP_CONTACT_ID_BASE) && (contactId < Profile.MIN_ID); + } + /** * A sub-directory of a single contact that contains all of the constituent raw contact * {@link ContactsContract.Data} rows. This directory can be used either @@ -4842,20 +4860,15 @@ public final class ContactsContract { * <p> * If a result is from the corp profile, it makes the following changes to the data: * <ul> - * <li>The following columns will be set to null, as they don't make sense on a - * different profile: - * {@link #_ID}, - * {@link #PHOTO_ID}, - * {@link #PHOTO_FILE_ID}, - * {@link #LOOKUP_KEY}, - * {@link #CUSTOM_RINGTONE}, - * {@link #IN_VISIBLE_GROUP}, - * and {@link #IN_DEFAULT_DIRECTORY}. - * </li> * <li> * {@link #PHOTO_THUMBNAIL_URI} and {@link #PHOTO_URI} will be rewritten to special * URIs. Use {@link ContentResolver#openAssetFileDescriptor} or its siblings to * load pictures from them. + * {@link #PHOTO_ID} and {@link #PHOTO_FILE_ID} will be set to null. Do not use them. + * </li> + * <li> + * Corp contacts will get artificial {@link #_ID}s. In order to tell whether a contact + * is from the corp profile, use {@link ContactsContract.Contacts#isCorpContactId(long)}. * </li> * </ul> * <p> diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java index a8b4cfb..1b5f72a 100644 --- a/core/java/android/provider/SearchIndexablesContract.java +++ b/core/java/android/provider/SearchIndexablesContract.java @@ -65,7 +65,7 @@ public class SearchIndexablesContract { public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS; /** - * Indexable xml resources colums. + * Indexable xml resources columns. */ public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] { XmlResource.COLUMN_RANK, // 0 @@ -78,7 +78,7 @@ public class SearchIndexablesContract { }; /** - * Indexable xml resources colums indices. + * Indexable xml resources columns indices. */ public static final int COLUMN_INDEX_XML_RES_RANK = 0; public static final int COLUMN_INDEX_XML_RES_RESID = 1; @@ -89,7 +89,7 @@ public class SearchIndexablesContract { public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS = 6; /** - * Indexable raw data colums. + * Indexable raw data columns. */ public static final String[] INDEXABLES_RAW_COLUMNS = new String[] { RawData.COLUMN_RANK, // 0 @@ -105,10 +105,11 @@ public class SearchIndexablesContract { RawData.COLUMN_INTENT_TARGET_PACKAGE, // 10 RawData.COLUMN_INTENT_TARGET_CLASS, // 11 RawData.COLUMN_KEY, // 12 + RawData.COLUMN_USER_ID, // 13 }; /** - * Indexable raw data colums indices. + * Indexable raw data columns indices. */ public static final int COLUMN_INDEX_RAW_RANK = 0; public static final int COLUMN_INDEX_RAW_TITLE = 1; @@ -123,16 +124,17 @@ public class SearchIndexablesContract { public static final int COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE = 10; public static final int COLUMN_INDEX_RAW_INTENT_TARGET_CLASS = 11; public static final int COLUMN_INDEX_RAW_KEY = 12; + public static final int COLUMN_INDEX_RAW_USER_ID = 13; /** - * Indexable raw data colums. + * Indexable raw data columns. */ public static final String[] NON_INDEXABLES_KEYS_COLUMNS = new String[] { NonIndexableKey.COLUMN_KEY_VALUE // 0 }; /** - * Non indexable data keys colums indices. + * Non indexable data keys columns indices. */ public static final int COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE = 0; @@ -204,6 +206,11 @@ public class SearchIndexablesContract { * Key associated with the raw data. The key needs to be unique. */ public static final String COLUMN_KEY = "key"; + + /** + * UserId associated with the raw data. + */ + public static final String COLUMN_USER_ID = "user_id"; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f48855a..07397973 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5112,6 +5112,61 @@ public final class Settings { public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS; /** + * Whether HDMI control shall be enabled. If disabled, no CEC/MHL command will be + * sent or processed. (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled"; + + /** + * Whether HDMI system audio is enabled. If enabled, TV internal speaker is muted, + * and the output is redirected to AV Receiver connected via + * {@Global#HDMI_SYSTEM_AUDIO_OUTPUT}. + * @hide + */ + public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled"; + + /** + * Output of the audio to be used for system audio mode, as defined in AudioSystem.java. + * <ul> + * <li>DEVICE_OUT_SPDIF</li> + * <li>DEVICE_OUT_HDMI_ARC</li> + * <li>DEVICE_OUT_LINE</li> + * </ul> + * @hide + */ + public static final String HDMI_SYSTEM_AUDIO_OUTPUT = "hdmi_system_audio_output"; + + /** + * Whether TV will automatically turn on upon reception of the CEC command + * <Text View On> or <Image View On>. (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED = + "hdmi_control_auto_wakeup_enabled"; + + /** + * Whether TV will also turn off other CEC devices when it goes to standby mode. + * (0 = false, 1 = true) + * @hide + */ + public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED = + "hdmi_control_auto_device_off_enabled"; + + /** + * Whether TV will switch to MHL port when a mobile device is plugged in. + * (0 = false, 1 = true) + * @hide + */ + public static final String MHL_INPUT_SWITCHING_ENABLED = "mhl_input_switching_enabled"; + + /** + * Whether TV will charge the mobile device connected at MHL port. (0 = false, 1 = true) + * @hide + */ + public static final String MHL_POWER_CHARGE_ENABLED = "mhl_power_charge_enabled"; + + /** * Whether mobile data connections are allowed by the user. See * ConnectivityManager for more info. * @hide diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 8bd0f4d..7d5ff33 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -29,7 +29,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.ArrayMap; import android.util.Log; import java.util.List; @@ -410,28 +409,20 @@ public abstract class NotificationListenerService extends Service { } /** - * Provides access to ranking information on a currently active - * notification. + * Stores ranking related information on a currently active notification. * * <p> - * Note that this object is not updated on notification events (such as - * {@link #onNotificationPosted(StatusBarNotification, RankingMap)}, - * {@link #onNotificationRemoved(StatusBarNotification)}, etc.). Make sure - * to retrieve a new Ranking from the current {@link RankingMap} whenever - * a notification event occurs. + * Ranking objects aren't automatically updated as notification events + * occur. Instead, ranking information has to be retrieved again via the + * current {@link RankingMap}. */ public static class Ranking { - private final String mKey; - private final int mRank; - private final boolean mIsAmbient; - private final boolean mIsInterceptedByDnd; + private String mKey; + private int mRank = -1; + private boolean mIsAmbient; + private boolean mMeetsInterruptionFilter; - private Ranking(String key, int rank, boolean isAmbient, boolean isInterceptedByDnd) { - mKey = key; - mRank = rank; - mIsAmbient = isAmbient; - mIsInterceptedByDnd = isInterceptedByDnd; - } + public Ranking() {} /** * Returns the key of the notification this Ranking applies to. @@ -459,11 +450,19 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns whether the notification was intercepted by - * "Do not disturb". + * Returns whether the notification meets the user's interruption + * filter. */ - public boolean isInterceptedByDoNotDisturb() { - return mIsInterceptedByDnd; + public boolean meetsInterruptionFilter() { + return mMeetsInterruptionFilter; + } + + private void populate(String key, int rank, boolean isAmbient, + boolean meetsInterruptionFilter) { + mKey = key; + mRank = rank; + mIsAmbient = isAmbient; + mMeetsInterruptionFilter = meetsInterruptionFilter; } } @@ -477,12 +476,9 @@ public abstract class NotificationListenerService extends Service { */ public static class RankingMap implements Parcelable { private final NotificationRankingUpdate mRankingUpdate; - private final ArrayMap<String, Ranking> mRankingCache; - private boolean mRankingCacheInitialized; private RankingMap(NotificationRankingUpdate rankingUpdate) { mRankingUpdate = rankingUpdate; - mRankingCache = new ArrayMap<>(rankingUpdate.getOrderedKeys().length); } /** @@ -496,37 +492,42 @@ public abstract class NotificationListenerService extends Service { } /** - * Returns the Ranking for the notification with the given key. + * Populates outRanking with ranking information for the notification + * with the given key. * - * @return the Ranking of the notification with the given key; - * <code>null</code> when the key is unknown. + * @return true if a valid key has been passed and outRanking has + * been populated; false otherwise */ - public Ranking getRanking(String key) { - synchronized (mRankingCache) { - if (!mRankingCacheInitialized) { - initializeRankingCache(); - mRankingCacheInitialized = true; - } - } - return mRankingCache.get(key); + public boolean getRanking(String key, Ranking outRanking) { + int rank = getRank(key); + outRanking.populate(key, rank, isAmbient(key), !isIntercepted(key)); + return rank >= 0; } - private void initializeRankingCache() { + private int getRank(String key) { + // TODO: Optimize. String[] orderedKeys = mRankingUpdate.getOrderedKeys(); - int firstAmbientIndex = mRankingUpdate.getFirstAmbientIndex(); for (int i = 0; i < orderedKeys.length; i++) { - String key = orderedKeys[i]; - boolean isAmbient = firstAmbientIndex > -1 && firstAmbientIndex <= i; - boolean isInterceptedByDnd = false; - // TODO: Optimize. - for (String s : mRankingUpdate.getDndInterceptedKeys()) { - if (s.equals(key)) { - isInterceptedByDnd = true; - break; - } + if (orderedKeys[i].equals(key)) { + return i; } - mRankingCache.put(key, new Ranking(key, i, isAmbient, isInterceptedByDnd)); } + return -1; + } + + private boolean isAmbient(String key) { + int rank = getRank(key); + return rank >= 0 && rank >= mRankingUpdate.getFirstAmbientIndex(); + } + + private boolean isIntercepted(String key) { + // TODO: Optimize. + for (String interceptedKey : mRankingUpdate.getInterceptedKeys()) { + if (interceptedKey.equals(key)) { + return true; + } + } + return false; } // ----------- Parcelable diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 4b13d95..26af38b 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -24,20 +24,20 @@ import android.os.Parcelable; public class NotificationRankingUpdate implements Parcelable { // TODO: Support incremental updates. private final String[] mKeys; - private final String[] mDndInterceptedKeys; + private final String[] mInterceptedKeys; private final int mFirstAmbientIndex; - public NotificationRankingUpdate(String[] keys, String[] dndInterceptedKeys, + public NotificationRankingUpdate(String[] keys, String[] interceptedKeys, int firstAmbientIndex) { mKeys = keys; mFirstAmbientIndex = firstAmbientIndex; - mDndInterceptedKeys = dndInterceptedKeys; + mInterceptedKeys = interceptedKeys; } public NotificationRankingUpdate(Parcel in) { mKeys = in.readStringArray(); mFirstAmbientIndex = in.readInt(); - mDndInterceptedKeys = in.readStringArray(); + mInterceptedKeys = in.readStringArray(); } @Override @@ -49,7 +49,7 @@ public class NotificationRankingUpdate implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeStringArray(mKeys); out.writeInt(mFirstAmbientIndex); - out.writeStringArray(mDndInterceptedKeys); + out.writeStringArray(mInterceptedKeys); } public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR @@ -71,7 +71,7 @@ public class NotificationRankingUpdate implements Parcelable { return mFirstAmbientIndex; } - public String[] getDndInterceptedKeys() { - return mDndInterceptedKeys; + public String[] getInterceptedKeys() { + return mInterceptedKeys; } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java new file mode 100644 index 0000000..67ce31e --- /dev/null +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -0,0 +1,270 @@ +/** + * 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.voice; + +import android.content.Intent; +import android.hardware.soundtrigger.Keyphrase; +import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; +import android.hardware.soundtrigger.KeyphraseMetadata; +import android.hardware.soundtrigger.SoundTriggerHelper; +import android.util.Slog; + +/** + * A class that lets a VoiceInteractionService implementation interact with + * always-on keyphrase detection APIs. + */ +public class AlwaysOnHotwordDetector { + //---- States of Keyphrase availability ----// + /** + * Indicates that the given keyphrase is not available on the system because of the + * hardware configuration. + */ + public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2; + /** + * Indicates that the given keyphrase is not supported. + */ + public static final int KEYPHRASE_UNSUPPORTED = -1; + /** + * Indicates that the given keyphrase is not enrolled. + */ + public static final int KEYPHRASE_UNENROLLED = 1; + /** + * Indicates that the given keyphrase is currently enrolled but not being actively listened for. + */ + public static final int KEYPHRASE_ENROLLED = 2; + + // Keyphrase management actions ----// + /** Indicates that we need to enroll. */ + public static final int MANAGE_ACTION_ENROLL = 0; + /** Indicates that we need to re-enroll. */ + public static final int MANAGE_ACTION_RE_ENROLL = 1; + /** Indicates that we need to un-enroll. */ + public static final int MANAGE_ACTION_UN_ENROLL = 2; + + /** + * Return codes for {@link #startRecognition()}, {@link #stopRecognition()} + */ + public static final int STATUS_ERROR = Integer.MIN_VALUE; + public static final int STATUS_OK = 1; + + //---- Keyphrase recognition status ----// + // TODO: Figure out if they are exclusive or should be flags instead? + public static final int RECOGNITION_NOT_AVAILABLE = -3; + public static final int RECOGNITION_NOT_REQUESTED = -2; + public static final int RECOGNITION_DISABLED_TEMPORARILY = -1; + public static final int RECOGNITION_REQUESTED = 1; + public static final int RECOGNITION_ACTIVE = 2; + static final String TAG = "AlwaysOnHotwordDetector"; + + private final String mText; + private final String mLocale; + private final Keyphrase mKeyphrase; + private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; + private final SoundTriggerHelper mSoundTriggerHelper; + private final SoundTriggerHelper.Listener mListener; + private final int mAvailability; + + private int mRecognitionState; + + /** + * Callbacks for always-on hotword detection. + */ + public interface Callback { + /** + * Called when the keyphrase is spoken. + * TODO: Add more data to the callback. + */ + void onDetected(); + /** + * Called when the detection for the associated keyphrase starts. + */ + void onDetectionStarted(); + /** + * Called when the detection for the associated keyphrase stops. + */ + void onDetectionStopped(); + } + + /** + * @param text The keyphrase text to get the detector for. + * @param locale The java locale for the detector. + * @param callback A non-null Callback for receiving the recognition events. + * + * @hide + */ + public AlwaysOnHotwordDetector(String text, String locale, Callback callback, + KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, + SoundTriggerHelper soundTriggerHelper) { + mText = text; + mLocale = locale; + mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; + KeyphraseMetadata keyphraseMetadata = + mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); + if (keyphraseMetadata != null) { + mKeyphrase = new Keyphrase(keyphraseMetadata.id, text, locale); + } else { + mKeyphrase = null; + } + mListener = new SoundTriggerListener(callback); + mSoundTriggerHelper = soundTriggerHelper; + mAvailability = getAvailabilityInternal(); + } + + /** + * Gets the state of always-on hotword detection for the given keyphrase and locale + * on this system. + * Availability implies that the hardware on this system is capable of listening for + * the given keyphrase or not. + * + * @return Indicates if always-on hotword detection is available for the given keyphrase. + * The return code is one of {@link #KEYPHRASE_HARDWARE_UNAVAILABLE}, + * {@link #KEYPHRASE_UNSUPPORTED}, {@link #KEYPHRASE_UNENROLLED} or + * {@link #KEYPHRASE_ENROLLED}. + */ + public int getAvailability() { + return mAvailability; + } + + /** + * Gets the status of the recognition. + * @return One of {@link #RECOGNITION_NOT_AVAILABLE}, {@link #RECOGNITION_NOT_REQUESTED}, + * {@link #RECOGNITION_DISABLED_TEMPORARILY} or {@link #RECOGNITION_ACTIVE}. + * @throws UnsupportedOperationException if the recognition isn't supported. + * Callers should check the availability by calling {@link #getAvailability()} + * before calling this method to avoid this exception. + */ + public int getRecognitionStatus() { + if (mAvailability != KEYPHRASE_ENROLLED) { + throw new UnsupportedOperationException( + "Recognition for the given keyphrase is not supported"); + } + + return mRecognitionState; + } + + /** + * Starts recognition for the associated keyphrase. + * + * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + * @throws UnsupportedOperationException if the recognition isn't supported. + * Callers should check the availability by calling {@link #getAvailability()} + * before calling this method to avoid this exception. + */ + public int startRecognition() { + if (mAvailability != KEYPHRASE_ENROLLED) { + throw new UnsupportedOperationException( + "Recognition for the given keyphrase is not supported"); + } + + mRecognitionState = RECOGNITION_REQUESTED; + int code = mSoundTriggerHelper.startRecognition(mKeyphrase.id, mListener); + if (code != SoundTriggerHelper.STATUS_OK) { + Slog.w(TAG, "startRecognition() failed with error code " + code); + return STATUS_ERROR; + } else { + return STATUS_OK; + } + } + + /** + * Stops recognition for the associated keyphrase. + * + * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. + * @throws UnsupportedOperationException if the recognition isn't supported. + * Callers should check the availability by calling {@link #getAvailability()} + * before calling this method to avoid this exception. + */ + public int stopRecognition() { + if (mAvailability != KEYPHRASE_ENROLLED) { + throw new UnsupportedOperationException( + "Recognition for the given keyphrase is not supported"); + } + + mRecognitionState = RECOGNITION_NOT_REQUESTED; + int code = mSoundTriggerHelper.stopRecognition(mKeyphrase.id, mListener); + if (code != SoundTriggerHelper.STATUS_OK) { + Slog.w(TAG, "stopRecognition() failed with error code " + code); + return STATUS_ERROR; + } else { + return STATUS_OK; + } + } + + /** + * Gets an intent to manage the associated keyphrase. + * + * @param action The manage action that needs to be performed. + * One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or + * {@link #MANAGE_ACTION_UN_ENROLL}. + * @return An {@link Intent} to manage the given keyphrase. + * @throws UnsupportedOperationException if managing they keyphrase isn't supported. + * Callers should check the availability by calling {@link #getAvailability()} + * before calling this method to avoid this exception. + */ + public Intent getManageIntent(int action) { + if (mAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE + || mAvailability == KEYPHRASE_UNSUPPORTED) { + throw new UnsupportedOperationException( + "Managing the given keyphrase is not supported"); + } + if (action != MANAGE_ACTION_ENROLL + && action != MANAGE_ACTION_RE_ENROLL + && action != MANAGE_ACTION_UN_ENROLL) { + throw new IllegalArgumentException("Invalid action specified " + action); + } + + return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); + } + + private int getAvailabilityInternal() { + if (mSoundTriggerHelper.dspInfo == null) { + return KEYPHRASE_HARDWARE_UNAVAILABLE; + } + if (mKeyphrase == null || !mSoundTriggerHelper.isKeyphraseSupported(mKeyphrase)) { + return KEYPHRASE_UNSUPPORTED; + } + if (!mSoundTriggerHelper.isKeyphraseEnrolled(mKeyphrase)) { + return KEYPHRASE_UNENROLLED; + } + return KEYPHRASE_ENROLLED; + } + + /** @hide */ + static final class SoundTriggerListener implements SoundTriggerHelper.Listener { + private final Callback mCallback; + + public SoundTriggerListener(Callback callback) { + this.mCallback = callback; + } + + @Override + public void onKeyphraseSpoken() { + Slog.i(TAG, "onKeyphraseSpoken"); + mCallback.onDetected(); + } + + @Override + public void onListeningStateChanged(int state) { + Slog.i(TAG, "onListeningStateChanged: state=" + state); + if (state == SoundTriggerHelper.STATE_STARTED) { + mCallback.onDetectionStarted(); + } else if (state == SoundTriggerHelper.STATE_STOPPED) { + mCallback.onDetectionStopped(); + } + } + } +} diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java deleted file mode 100644 index d266e1a..0000000 --- a/core/java/android/service/voice/KeyphraseInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package android.service.voice; - -import android.util.ArraySet; - -/** - * A Voice Keyphrase. - * @hide - */ -public class KeyphraseInfo { - public final int id; - public final String keyphrase; - public final ArraySet<String> supportedLocales; - - public KeyphraseInfo(int id, String keyphrase, String[] supportedLocales) { - this.id = id; - this.keyphrase = keyphrase; - this.supportedLocales = new ArraySet<String>(supportedLocales.length); - for (String locale : supportedLocales) { - this.supportedLocales.add(locale); - } - } - - @Override - public String toString() { - return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales; - } -} diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java deleted file mode 100644 index 2d049b9..0000000 --- a/core/java/android/service/voice/SoundTriggerManager.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.service.voice; - -import android.hardware.soundtrigger.SoundTrigger; -import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; - -import java.util.ArrayList; - -/** - * Manager for {@link SoundTrigger} APIs. - * Currently this just acts as an abstraction over all SoundTrigger API calls. - * @hide - */ -public class SoundTriggerManager { - /** The {@link DspInfo} for the system, or null if none exists. */ - public DspInfo dspInfo; - - public SoundTriggerManager() { - ArrayList <ModuleProperties> modules = new ArrayList<>(); - int status = SoundTrigger.listModules(modules); - if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { - // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here. - dspInfo = null; - } else { - // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the - // DSP hardware. - ModuleProperties properties = modules.get(0); - dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description, - properties.version, properties.powerConsumptionMw); - } - } - - /** - * @return True, if the keyphrase is supported on DSP for the given locale. - */ - public boolean isKeyphraseSupported(String keyphrase, String locale) { - // TODO(sansid): We also need to look into a SoundTrigger API that let's us - // query this. For now just return supported if there's a DSP available. - return dspInfo != null; - } - - /** - * @return True, if the keyphrase is has been enrolled for the given locale. - */ - public boolean isKeyphraseEnrolled(String keyphrase, String locale) { - // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models. - // They have been enrolled. - return false; - } - - /** - * @return True, if a recognition for the keyphrase is active for the given locale. - */ - public boolean isKeyphraseActive(String keyphrase, String locale) { - // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active. - return false; - } -} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index e0329f8..cf8d502 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -20,6 +20,8 @@ import android.annotation.SdkConstant; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; +import android.hardware.soundtrigger.SoundTriggerHelper; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -53,16 +55,6 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; - // TODO(sansid): Unhide these. - /** @hide */ - public static final int KEYPHRASE_UNAVAILABLE = 0; - /** @hide */ - public static final int KEYPHRASE_UNENROLLED = 1; - /** @hide */ - public static final int KEYPHRASE_ENROLLED = 2; - /** @hide */ - public static final int KEYPHRASE_ACTIVE = 3; - /** * Name under which a VoiceInteractionService component publishes information about itself. * This meta-data should reference an XML resource containing a @@ -76,8 +68,8 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; - private SoundTriggerManager mSoundTriggerManager; private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; + private SoundTriggerHelper mSoundTriggerHelper; public void startSession(Bundle args) { try { @@ -92,7 +84,7 @@ public class VoiceInteractionService extends Service { mSystemService = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); - mSoundTriggerManager = new SoundTriggerManager(); + mSoundTriggerHelper = new SoundTriggerHelper(); } @Override @@ -104,34 +96,18 @@ public class VoiceInteractionService extends Service { } /** - * Gets the state of always-on hotword detection for the given keyphrase and locale - * on this system. - * Availability implies that the hardware on this system is capable of listening for - * the given keyphrase or not. - * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED} - * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}. - * - * @param keyphrase The keyphrase whose availability is being checked. - * @param locale The locale for which the availability is being checked. - * @return Indicates if always-on hotword detection is available for the given keyphrase. - * TODO(sansid): Unhide this. - * @hide + * @param keyphrase The keyphrase that's being used, for example "Hello Android". + * @param locale The locale for which the enrollment needs to be performed. + * This is a Java locale, for example "en_US". + * @param callback The callback to notify of detection events. + * @return An always-on hotword detector for the given keyphrase and locale. */ - public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) { - // The available keyphrases is a combination of DSP availability and - // the keyphrases that have an enrollment application for them. - if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale) - || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) { - return KEYPHRASE_UNAVAILABLE; - } - if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) { - return KEYPHRASE_UNENROLLED; - } - if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) { - return KEYPHRASE_ENROLLED; - } else { - return KEYPHRASE_ACTIVE; - } + public final AlwaysOnHotwordDetector getAlwaysOnHotwordDetector( + String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) { + // TODO: Cache instances and return the same one instead of creating a new interactor + // for the same keyphrase/locale combination. + return new AlwaysOnHotwordDetector(keyphrase, locale, callback, + mKeyphraseEnrollmentInfo, mSoundTriggerHelper); } /** diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 6b984d6..06df683 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -251,6 +251,21 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return replace(length, length, text, 0, text.length()); } + /** + * Appends the character sequence {@code text} and spans {@code what} over the appended part. + * See {@link Spanned} for an explanation of what the flags mean. + * @param text the character sequence to append. + * @param what the object to be spanned over the appended text. + * @param flags see {@link Spanned}. + * @return this {@code SpannableStringBuilder}. + */ + public SpannableStringBuilder append(CharSequence text, Object what, int flags) { + int start = length(); + append(text); + setSpan(what, start, length(), flags); + return this; + } + // Documentation from interface public SpannableStringBuilder append(CharSequence text, int start, int end) { int length = length(); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d69d01d..be677ea 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -18,6 +18,7 @@ package android.view; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface.OutOfResourcesException; @@ -247,21 +248,24 @@ public abstract class HardwareRenderer { abstract void detachSurfaceTexture(long hardwareLayer); /** - * Setup the hardware renderer for drawing. This is called whenever the - * size of the target surface changes or when the surface is first created. + * Setup the hardware renderer for drawing. This is called whenever the size + * of the target surface changes or when the surface is first created. * * @param width Width of the drawing surface. * @param height Height of the drawing surface. + * @param surfaceInsets Insets between the drawing surface and actual + * surface bounds. * @param lightX X position of the shadow casting light * @param lightY Y position of the shadow casting light * @param lightZ Z position of the shadow casting light * @param lightRadius radius of the shadow casting light */ - abstract void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius); + abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, + float lightZ, float lightRadius); /** * Gets the current width of the surface. This is the width that the surface - * was last set to in a call to {@link #setup(int, int, float, float, float, float)}. + * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}. * * @return the current width of the surface */ @@ -269,7 +273,7 @@ public abstract class HardwareRenderer { /** * Gets the current height of the surface. This is the height that the surface - * was last set to in a call to {@link #setup(int, int, float, float, float, float)}. + * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}. * * @return the current width of the surface */ @@ -344,7 +348,6 @@ public abstract class HardwareRenderer { * @param view The view to draw. * @param attachInfo AttachInfo tied to the specified view. * @param callbacks Callbacks invoked when drawing happens. - * @param dirty The dirty rectangle to update, can be null. */ abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks); @@ -369,17 +372,18 @@ public abstract class HardwareRenderer { * @param height The height of the drawing surface. * @param surface The surface to hardware accelerate * @param metrics The display metrics used to draw the output. + * @param surfaceInsets The drawing surface insets to apply * * @return true if the surface was initialized, false otherwise. Returning * false might mean that the surface was already initialized. */ - boolean initializeIfNeeded(int width, int height, Surface surface, DisplayMetrics metrics) + boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics) throws OutOfResourcesException { if (isRequested()) { // We lost the gl context, so recreate it. if (!isEnabled()) { if (initialize(surface)) { - setup(width, height, metrics); + setup(width, height, surfaceInsets, metrics); return true; } } @@ -387,12 +391,12 @@ public abstract class HardwareRenderer { return false; } - void setup(int width, int height, DisplayMetrics metrics) { + void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) { float lightX = width / 2.0f; float lightY = -400 * metrics.density; float lightZ = 800 * metrics.density; float lightRadius = 800 * metrics.density; - setup(width, height, lightX, lightY, lightZ, lightRadius); + setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius); } /** diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 57d1beb..acb2fe4 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -19,6 +19,7 @@ package android.view; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.IBinder; import android.os.RemoteException; @@ -67,7 +68,16 @@ public class ThreadedRenderer extends HardwareRenderer { PROFILE_PROPERTY_VISUALIZE_BARS, }; + // Size of the rendered content. private int mWidth, mHeight; + + // Actual size of the drawing surface. + private int mSurfaceWidth, mSurfaceHeight; + + // Insets between the drawing surface and rendered content. These are + // applied as translation when updating the root render node. + private int mInsetTop, mInsetLeft; + private long mNativeProxy; private boolean mInitialized = false; private RenderNode mRootNode; @@ -154,11 +164,23 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) { + void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ, + float lightRadius) { mWidth = width; mHeight = height; - mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight); - nSetup(mNativeProxy, width, height, lightX, lightY, lightZ, lightRadius); + if (surfaceInsets != null) { + mInsetLeft = surfaceInsets.left; + mInsetTop = surfaceInsets.top; + mSurfaceWidth = width + mInsetLeft + surfaceInsets.right; + mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom; + } else { + mInsetLeft = 0; + mInsetTop = 0; + mSurfaceWidth = width; + mSurfaceHeight = height; + } + mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight); + nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius); } @Override @@ -214,9 +236,10 @@ public class ThreadedRenderer extends HardwareRenderer { view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); - HardwareCanvas canvas = mRootNode.start(mWidth, mHeight); + HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight); try { canvas.save(); + canvas.translate(mInsetLeft, mInsetTop); callbacks.onHardwarePreDraw(canvas); canvas.drawRenderNode(view.getDisplayList()); callbacks.onHardwarePostDraw(canvas); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 675b96d..31d2b1e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7431,7 +7431,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ViewParent ancestor = mParent; while (ancestor instanceof ViewGroup) { final ViewGroup vgAncestor = (ViewGroup) ancestor; - if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS + || vgAncestor.shouldBlockFocusForTouchscreen()) { return true; } else { ancestor = vgAncestor.getParent(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 36e5996..edc9971 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -18,6 +18,7 @@ package android.view; import android.animation.LayoutTransition; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Bitmap; @@ -361,6 +362,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000; /** + * When set, focus will not be permitted to enter this group if a touchscreen is present. + */ + static final int FLAG_TOUCHSCREEN_BLOCKS_FOCUS = 0x4000000; + + /** * Indicates which types of drawing caches are to be kept in memory. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -567,6 +573,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager case R.styleable.ViewGroup_transitionGroup: setTransitionGroup(a.getBoolean(attr, false)); break; + case R.styleable.ViewGroup_touchscreenBlocksFocus: + setTouchscreenBlocksFocus(a.getBoolean(attr, false)); + break; } } @@ -660,6 +669,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // shortcut: don't report a new focusable view if we block our descendants from // getting focus && (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS) + && !shouldBlockFocusForTouchscreen() // shortcut: don't report a new focusable view if we already are focused // (and we don't prefer our descendants) // @@ -901,7 +911,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } final int descendantFocusability = getDescendantFocusability(); - if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS && + !shouldBlockFocusForTouchscreen()) { final int count = mChildrenCount; final View[] children = mChildren; @@ -925,7 +936,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int descendantFocusability = getDescendantFocusability(); - if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS && + !shouldBlockFocusForTouchscreen()) { final int count = mChildrenCount; final View[] children = mChildren; @@ -941,13 +953,46 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is // to avoid the focus search finding layouts when a more precise search // among the focusable children would be more interesting. - if (descendantFocusability != FOCUS_AFTER_DESCENDANTS + if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS // No focusable descendants - || (focusableCount == views.size())) { + || (focusableCount == views.size())) && !shouldBlockFocusForTouchscreen()) { super.addFocusables(views, direction, focusableMode); } } + /** + * Set whether this ViewGroup should ignore focus requests for itself and its children. + * If this option is enabled and the ViewGroup or a descendant currently has focus, focus + * will proceed forward. + * + * @param touchscreenBlocksFocus true to enable blocking focus in the presence of a touchscreen + */ + public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) { + if (touchscreenBlocksFocus) { + mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS; + if (hasFocus()) { + final View newFocus = focusSearch(FOCUS_FORWARD); + if (newFocus != null) { + newFocus.requestFocus(); + } + } + } else { + mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS; + } + } + + /** + * Check whether this ViewGroup should ignore focus requests for itself and its children. + */ + public boolean getTouchscreenBlocksFocus() { + return (mGroupFlags & FLAG_TOUCHSCREEN_BLOCKS_FOCUS) != 0; + } + + boolean shouldBlockFocusForTouchscreen() { + return getTouchscreenBlocksFocus() && + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); + } + @Override public void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) { super.findViewsWithText(outViews, text, flags); @@ -2440,6 +2485,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } int descendantFocusability = getDescendantFocusability(); + if (shouldBlockFocusForTouchscreen()) { + return false; + } + switch (descendantFocusability) { case FOCUS_BLOCK_DESCENDANTS: return super.requestFocus(direction, previouslyFocusedRect); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5def940..9405299 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1713,7 +1713,8 @@ public final class ViewRootImpl implements ViewParent, if (hwInitialized || mWidth != mAttachInfo.mHardwareRenderer.getWidth() || mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { - mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, + final Rect shadowInsets = params != null ? params.shadowInsets : null; + mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets, mAttachInfo.mRootView.getResources().getDisplayMetrics()); if (!hwInitialized) { mAttachInfo.mHardwareRenderer.invalidate(mSurface); @@ -2211,20 +2212,22 @@ public final class ViewRootImpl implements ViewParent, return measureSpec; } + int mHardwareXOffset; int mHardwareYOffset; int mResizeAlpha; final Paint mResizePaint = new Paint(); @Override public void onHardwarePreDraw(HardwareCanvas canvas) { - canvas.translate(0, -mHardwareYOffset); + canvas.translate(-mHardwareXOffset, -mHardwareYOffset); } @Override public void onHardwarePostDraw(HardwareCanvas canvas) { if (mResizeBuffer != null) { mResizePaint.setAlpha(mResizeAlpha); - canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint); + canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset, + mResizePaint); } drawAccessibilityFocusedDrawableIfNeeded(canvas); } @@ -2368,15 +2371,17 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mTreeObserver.dispatchOnScrollChanged(); } - int yoff; + final WindowManager.LayoutParams params = mWindowAttributes; + final Rect surfaceInsets = params != null ? params.shadowInsets : null; boolean animating = mScroller != null && mScroller.computeScrollOffset(); + final int curScrollY; if (animating) { - yoff = mScroller.getCurrY(); + curScrollY = mScroller.getCurrY(); } else { - yoff = mScrollY; + curScrollY = mScrollY; } - if (mCurScrollY != yoff) { - mCurScrollY = yoff; + if (mCurScrollY != curScrollY) { + mCurScrollY = curScrollY; fullRedrawNeeded = true; } @@ -2425,11 +2430,14 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mTreeObserver.dispatchOnDraw(); + final int xOffset = surfaceInsets != null ? -surfaceInsets.left : 0; + final int yOffset = curScrollY + (surfaceInsets != null ? -surfaceInsets.top : 0); if (!dirty.isEmpty() || mIsAnimating) { if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; - mHardwareYOffset = yoff; + mHardwareYOffset = yOffset; + mHardwareXOffset = xOffset; mResizeAlpha = resizeAlpha; dirty.setEmpty(); @@ -2450,8 +2458,9 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mHardwareRenderer.isRequested()) { try { - attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight, - mSurface, attachInfo.mRootView.getResources().getDisplayMetrics()); + attachInfo.mHardwareRenderer.initializeIfNeeded( + mWidth, mHeight, mSurface, surfaceInsets, + attachInfo.mRootView.getResources().getDisplayMetrics()); } catch (OutOfResourcesException e) { handleOutOfResourcesException(e); return; @@ -2462,7 +2471,7 @@ public final class ViewRootImpl implements ViewParent, return; } - if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { + if (!drawSoftware(surface, attachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } @@ -2475,9 +2484,9 @@ public final class ViewRootImpl implements ViewParent, } /** - * @return true if drawing was succesfull, false if an error occurred + * @return true if drawing was successful, false if an error occurred */ - private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, + private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. @@ -2526,7 +2535,7 @@ public final class ViewRootImpl implements ViewParent, // If we are applying an offset, we need to clear the area // where the offset doesn't appear to avoid having garbage // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { + if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } @@ -2542,7 +2551,7 @@ public final class ViewRootImpl implements ViewParent, ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } try { - canvas.translate(0, -yoff); + canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } @@ -3147,8 +3156,10 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){ mFullRedrawNeeded = true; try { + final WindowManager.LayoutParams lp = mWindowAttributes; + final Rect surfaceInsets = lp != null ? lp.shadowInsets : null; mAttachInfo.mHardwareRenderer.initializeIfNeeded( - mWidth, mHeight, mSurface, + mWidth, mHeight, mSurface, surfaceInsets, mAttachInfo.mRootView.getResources().getDisplayMetrics()); } catch (OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 4eecc6a..c06b5d8 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -19,7 +19,9 @@ package android.view; import android.app.Presentation; import android.content.Context; import android.content.pm.ActivityInfo; +import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -1290,6 +1292,13 @@ public interface WindowManager extends ViewManager { * field is added with {@link #y} to supply the <var>yAdj</var> parameter. */ public float verticalMargin; + + /** + * Positive insets between the drawing surface and window content. + * + * @hide + */ + public Rect shadowInsets = new Rect(); /** * The desired bitmap format. May be one of the constants in @@ -1571,6 +1580,10 @@ public interface WindowManager extends ViewManager { out.writeInt(hasSystemUiListeners ? 1 : 0); out.writeInt(inputFeatures); out.writeLong(userActivityTimeout); + out.writeInt(shadowInsets.left); + out.writeInt(shadowInsets.top); + out.writeInt(shadowInsets.right); + out.writeInt(shadowInsets.bottom); } public static final Parcelable.Creator<LayoutParams> CREATOR @@ -1613,6 +1626,7 @@ public interface WindowManager extends ViewManager { hasSystemUiListeners = in.readInt() != 0; inputFeatures = in.readInt(); userActivityTimeout = in.readLong(); + shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt()); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -1644,6 +1658,8 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19; /** {@hide} */ + public static final int SHADOW_INSETS_CHANGED = 1<<20; + /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; // internal buffer to backup/restore parameters under compatibility mode. @@ -1778,6 +1794,11 @@ public interface WindowManager extends ViewManager { changes |= USER_ACTIVITY_TIMEOUT_CHANGED; } + if (!shadowInsets.equals(o.shadowInsets)) { + shadowInsets.set(o.shadowInsets); + changes |= SHADOW_INSETS_CHANGED; + } + return changes; } @@ -1877,6 +1898,9 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } + if (!shadowInsets.equals(Insets.NONE)) { + sb.append(" shadowInsets=").append(shadowInsets); + } sb.append('}'); return sb.toString(); } diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index bf5c84e..38e3723 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -164,6 +164,11 @@ public abstract class WindowManagerInternal { public abstract void getWindowFrame(IBinder token, Rect outBounds); /** + * Opens the global actions dialog. + */ + public abstract void showGlobalActions(); + + /** * Invalidate all visible windows. Then report back on the callback once all windows have * redrawn. */ diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index ee542a1..5f0fa18 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -1162,6 +1162,12 @@ public interface WindowManagerPolicy { public void showRecentApps(); /** + * Show the global actions dialog. + * @hide + */ + public void showGlobalActions(); + + /** * @return The current height of the input method window. */ public int getInputMethodWindowVisibleHeightLw(); diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index e1f40b7..338f3d2 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -429,6 +429,21 @@ public class BaseInputConnection implements InputConnection { } /** + * The default implementation is responsible for handling + * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}. In fact, for derived classes, calling + * {@code super.requestCursorAnchorInfo(request)} is the only way to handle + * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}. + */ + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) { + if (request != null && mIMM != null && + request.getRequestType() == CursorAnchorInfoRequest.TYPE_CURSOR_RECT) { + mIMM.setCursorRectMonitorMode(request.getRequestFlags()); + return CursorAnchorInfoRequest.RESULT_SCHEDULED; + } + return CursorAnchorInfoRequest.RESULT_NOT_HANDLED; + } + + /** * The default implementation places the given text into the editable, * replacing any existing composing text. The new text is marked as * in a composing state with the composing style. diff --git a/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl new file mode 100644 index 0000000..41ef7cc6 --- /dev/null +++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable CursorAnchorInfoRequest; diff --git a/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java new file mode 100644 index 0000000..e4c94f2 --- /dev/null +++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java @@ -0,0 +1,203 @@ +/* + * 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.view.inputmethod; + +import android.inputmethodservice.InputMethodService; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.View; + +/** + * Used to enable or disable event notification for + * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}. This class is also used to + * enable {@link InputMethodService#onUpdateCursor(android.graphics.Rect)} for existing editors + * that have not supported {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}. + */ +public final class CursorAnchorInfoRequest implements Parcelable { + private final int mRequestType; + private final int mRequestFlags; + + /** + * Not handled by the editor. + */ + public static final int RESULT_NOT_HANDLED = 0x00; + /** + * Request is scheduled in the editor task queue. + */ + public static final int RESULT_SCHEDULED = 0x01; + + /** + * The request is for {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}. + * This mechanism is powerful enough to retrieve fine-grained positional information of + * characters in the editor. + */ + public static final int TYPE_CURSOR_ANCHOR_INFO = 0x01; + /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} + * whenever cursor/anchor position is changed. To disable monitoring, call + * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with + * {@link #TYPE_CURSOR_ANCHOR_INFO} and this flag off. + * <p> + * This flag can be used together with {@link #FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE}. + * </p> + */ + public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 0x01; + /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at + * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be + * used together with {@link #FLAG_CURSOR_ANCHOR_INFO_MONITOR}. + */ + public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 0x02; + + /** + * The request is for {@link InputMethodService#onUpdateCursor(android.graphics.Rect)}. This + * mechanism has been available since API Level 3 (CUPCAKE) but only the cursor rectangle can + * be retrieved with this mechanism. + */ + public static final int TYPE_CURSOR_RECT = 0x02; + /** + * The editor is requested to call + * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} + * whenever the cursor position is changed. To disable monitoring, call + * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with + * {@link #TYPE_CURSOR_RECT} and this flag off. + * <p> + * This flag can be used together with {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}. + * </p> + */ + public static final int FLAG_CURSOR_RECT_MONITOR = 0x01; + /** + * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be + * called back in screen coordinates. To receive cursor position in local coordinates, call + * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with + * {@link #TYPE_CURSOR_RECT} and this flag off. + */ + public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 0x02; + /** + * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be + * called back in screen coordinates after coordinate conversion with {@link View#getMatrix()}. + * To disable coordinate conversion with {@link View#getMatrix()} again, call + * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with + * {@link #TYPE_CURSOR_RECT} and this flag off. + * + * <p> + * The flag is ignored if {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES} is off. + * </p> + */ + public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 0x04; + + /** + * Constructs the object with request type and type-specific flags. + * + * @param requestType the type of this request. Currently {@link #TYPE_CURSOR_ANCHOR_INFO} or + * {@link #TYPE_CURSOR_RECT} is supported. + * @param requestFlags the flags for the given request type. + */ + public CursorAnchorInfoRequest(int requestType, int requestFlags) { + mRequestType = requestType; + mRequestFlags = requestFlags; + } + + /** + * Used to make this class parcelable. + * + * @param source the parcel from which the object is unmarshalled. + */ + public CursorAnchorInfoRequest(Parcel source) { + mRequestType = source.readInt(); + mRequestFlags = source.readInt(); + } + + /** + * @return the type of this request. + */ + public int getRequestType() { + return mRequestType; + } + + /** + * @return the flags that are specific to the type of this request. + */ + public int getRequestFlags() { + return mRequestFlags; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRequestType); + dest.writeInt(mRequestFlags); + } + + @Override + public int hashCode(){ + return mRequestType * 31 + mRequestFlags; + } + + @Override + public boolean equals(Object obj){ + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof CursorAnchorInfoRequest)) { + return false; + } + final CursorAnchorInfoRequest that = (CursorAnchorInfoRequest) obj; + if (hashCode() != that.hashCode()) { + return false; + } + return mRequestType != that.mRequestType && mRequestFlags == that.mRequestFlags; + } + + @Override + public String toString() { + return "CursorAnchorInfoRequest{mRequestType=" + mRequestType + + " mRequestFlags=" + mRequestFlags + + "}"; + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<CursorAnchorInfoRequest> CREATOR = + new Parcelable.Creator<CursorAnchorInfoRequest>() { + @Override + public CursorAnchorInfoRequest createFromParcel(Parcel source) { + return new CursorAnchorInfoRequest(source); + } + + @Override + public CursorAnchorInfoRequest[] newArray(int size) { + return new CursorAnchorInfoRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 3537aec..dff91dc 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -723,4 +723,15 @@ public interface InputConnection { * valid. */ public boolean performPrivateCommand(String action, Bundle data); + + /** + * Called by the IME to ask the editor for calling back + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to + * notify cursor/anchor locations. + * + * @param request the details of the request. + * @return a result code that depends on {@link CursorAnchorInfoRequest#getRequestType()}. See + * {@link CursorAnchorInfoRequest} for details. + */ + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request); } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index a48473e..c831d7c 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -125,4 +125,8 @@ public class InputConnectionWrapper implements InputConnection { public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } -} + + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) { + return mTarget.requestCursorAnchorInfo(request); + } + } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index ace8808..623b5f9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -25,8 +25,9 @@ import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; import android.content.Context; +import android.graphics.Matrix; import android.graphics.Rect; -import android.inputmethodservice.InputMethodService; +import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -312,8 +313,9 @@ public final class InputMethodManager { CompletionInfo[] mCompletions; // Cursor position on the screen. - Rect mTmpCursorRect = new Rect(); + Rect mNextCursorRect = new Rect(); Rect mCursorRect = new Rect(); + RectF mTempRectF = new RectF(); int mCursorSelStart; int mCursorSelEnd; int mCursorCandStart; @@ -348,8 +350,13 @@ public final class InputMethodManager { */ private final int[] mViewTopLeft = new int[2]; + /** + * The matrix to convert the view location into screen coordinates in {@link #updateCursor}. + */ + private final Matrix mViewToScreenMatrix = new Matrix(); + // ----------------------------------------------------------- - + /** * Sequence number of this binding, as returned by the server. */ @@ -365,10 +372,28 @@ public final class InputMethodManager { InputChannel mCurChannel; ImeInputEventSender mCurSender; + private static final int CURSOR_RECT_MONITOR_MODE_NONE = 0x0; + + private static final int CURSOR_RECT_MONITOR_FLAG_MASK = + CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR | + CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES | + CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX; + + private static final int CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE = 0x0; + + private static final int CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK = + CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR | + CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE; + + /** + * The monitor mode for {@link #updateCursor(View, int, int, int, int)}. + */ + private int mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE; + /** - * The current cursor/anchor monitor mode. + * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. */ - int mCursorAnchorMonitorMode = InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_NONE; + private int mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE; final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); @@ -382,7 +407,6 @@ public final class InputMethodManager { static final int MSG_SEND_INPUT_EVENT = 5; static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; - static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8; static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9; class H extends Handler { @@ -422,6 +446,9 @@ public final class InputMethodManager { return; } + mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE; + mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE; + setInputChannelLocked(res.channel); mCurMethod = res.method; mCurId = res.id; @@ -514,15 +541,6 @@ public final class InputMethodManager { finishedInputEvent(msg.arg1, false, false); return; } - case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: { - synchronized (mH) { - mCursorAnchorMonitorMode = msg.arg1; - // Clear the cache. - mCursorRect.setEmpty(); - mCursorAnchorInfo = null; - } - return; - } case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: { synchronized (mH) { mNextUserActionNotificationSequenceNumber = msg.arg1; @@ -594,11 +612,6 @@ public final class InputMethodManager { } @Override - public void setCursorAnchorMonitorMode(int monitorMode) { - mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0)); - } - - @Override public void setUserActionNotificationSequenceNumber(int sequenceNumber) { mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER, sequenceNumber, 0)); @@ -1535,37 +1548,45 @@ public final class InputMethodManager { return false; } synchronized (mH) { - return (mCursorAnchorMonitorMode & - InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0; + return (mCursorRectMonitorMode & CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR) != 0; } } /** - * Returns true if the current input method wants to receive the cursor rectangle in - * screen coordinates rather than local coordinates in the attached view. + * Updates the result of {@link #isWatchingCursor(View)}. * * @hide */ - public boolean usesScreenCoordinatesForCursorLocked() { - // {@link InputMethodService#CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT} also means - // that {@link InputMethodService#onUpdateCursor} should provide the cursor rectangle - // in screen coordinates rather than local coordinates. - return (mCursorAnchorMonitorMode & - InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0; + public void setCursorRectMonitorMode(int flags) { + synchronized (mH) { + mCursorRectMonitorMode = (CURSOR_RECT_MONITOR_FLAG_MASK & flags); + } } /** - * Set cursor/anchor monitor mode via {@link com.android.server.InputMethodManagerService}. - * This is an internal method for {@link android.inputmethodservice.InputMethodService} and - * should never be used from IMEs and applications. + * Returns true if the current input method wants to be notified when cursor/anchor location + * is changed. * * @hide */ - public void setCursorAnchorMonitorMode(IBinder imeToken, int monitorMode) { - try { - mService.setCursorAnchorMonitorMode(imeToken, monitorMode); - } catch (RemoteException e) { - throw new RuntimeException(e); + public boolean isCursorAnchorInfoEnabled() { + synchronized (mH) { + final boolean isImmediate = (mCursorAnchorInfoMonitorMode & + CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0; + final boolean isMonitoring = (mCursorAnchorInfoMonitorMode & + CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR) != 0; + return isImmediate || isMonitoring; + } + } + + /** + * Updates the result of {@link #isWatchingCursor(View)}. + * + * @hide + */ + public void setCursorAnchorInfoMonitorMode(int flags) { + synchronized (mH) { + mCursorAnchorInfoMonitorMode = (CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK & flags); } } @@ -1581,16 +1602,32 @@ public final class InputMethodManager { return; } if (DEBUG) Log.d(TAG, "updateCursor"); - mTmpCursorRect.set(left, top, right, bottom); - if (!Objects.equals(mCursorRect, mTmpCursorRect)) { + final boolean usesScreenCoordinates = (mCursorRectMonitorMode & + CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES) != 0; + if (usesScreenCoordinates) { + view.getLocationOnScreen(mViewTopLeft); + final Matrix viewMatrix = view.getMatrix(); + final boolean usesViewMatrix = (viewMatrix != null) && ((mCursorRectMonitorMode & + CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX) != 0); + if (usesViewMatrix) { + mTempRectF.set(left, top, right, bottom); + mViewToScreenMatrix.set(viewMatrix); + mViewToScreenMatrix.postTranslate(mViewTopLeft[0], mViewTopLeft[1]); + mViewToScreenMatrix.mapRect(mTempRectF); + mNextCursorRect.set((int)mTempRectF.left, (int)mTempRectF.top, + (int)mTempRectF.right, (int)mTempRectF.bottom); + } else { + mNextCursorRect.set(left + mViewTopLeft[0], top + mViewTopLeft[1], + right + mViewTopLeft[0], bottom + mViewTopLeft[1]); + } + } else { + mNextCursorRect.set(left, top, right, bottom); + } + if (!Objects.equals(mCursorRect, mNextCursorRect)) { + if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mNextCursorRect); try { - if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod); - mCursorRect.set(mTmpCursorRect); - if (usesScreenCoordinatesForCursorLocked()) { - view.getLocationOnScreen(mViewTopLeft); - mTmpCursorRect.offset(mViewTopLeft[0], mViewTopLeft[1]); - } - mCurMethod.updateCursor(mTmpCursorRect); + mCurMethod.updateCursor(mNextCursorRect); + mCursorRect.set(mNextCursorRect); } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } @@ -1613,7 +1650,11 @@ public final class InputMethodManager { || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } - if (Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) { + // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has + // not been changed from the previous call. + final boolean isImmediate = (mCursorAnchorInfoMonitorMode & + CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0; + if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) { Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info=" + cursorAnchorInfo); return; } @@ -1621,6 +1662,9 @@ public final class InputMethodManager { try { mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo); mCursorAnchorInfo = cursorAnchorInfo; + // Clear immediate bit (if any). + mCursorAnchorInfoMonitorMode &= + ~CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3a1f6ed..9701c6f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -68,6 +68,7 @@ import android.view.animation.LinearInterpolator; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -5696,6 +5697,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public boolean performPrivateCommand(String action, Bundle data) { return getTarget().performPrivateCommand(action, data); } + + @Override + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) { + return getTarget().requestCursorAnchorInfo(request); + } } /** diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index acee592..96abf51 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.content.res.Configuration; import android.util.AttributeSet; +import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; @@ -28,7 +29,6 @@ import android.view.accessibility.AccessibilityEvent; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; -import com.android.internal.view.menu.MenuPresenter; import com.android.internal.view.menu.MenuView; /** @@ -45,6 +45,12 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo private MenuBuilder mMenu; + /** Context against which to inflate popup menus. */ + private Context mPopupContext; + + /** Theme resource against which to inflate popup menus. */ + private int mPopupTheme; + private boolean mReserveOverflow; private ActionMenuPresenter mPresenter; private boolean mFormatItems; @@ -64,9 +70,41 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo final float density = context.getResources().getDisplayMetrics().density; mMinCellSize = (int) (MIN_CELL_SIZE * density); mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density); + mPopupContext = context; + mPopupTheme = 0; } - /** @hide */ + /** + * Specifies the theme to use when inflating popup menus. By default, uses + * the same theme as the action menu view itself. + * + * @param resId theme used to inflate popup menus + * @see #getPopupTheme() + */ + public void setPopupTheme(int resId) { + if (mPopupTheme != resId) { + mPopupTheme = resId; + if (resId == 0) { + mPopupContext = mContext; + } else { + mPopupContext = new ContextThemeWrapper(mContext, resId); + } + } + } + + /** + * @return resource identifier of the theme used to inflate popup menus, or + * 0 if menus are inflated against the action menu view theme + * @see #setPopupTheme(int) + */ + public int getPopupTheme() { + return mPopupTheme; + } + + /** + * @param presenter Menu presenter used to display popup menu + * @hide + */ public void setPresenter(ActionMenuPresenter presenter) { mPresenter = presenter; mPresenter.setMenuView(this); @@ -571,7 +609,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo mMenu.setCallback(new MenuBuilderCallback()); mPresenter = new ActionMenuPresenter(context); mPresenter.setCallback(new ActionMenuPresenterCallback()); - mMenu.addMenuPresenter(mPresenter); + mMenu.addMenuPresenter(mPresenter, mPopupContext); mPresenter.setMenuView(this); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 170a316..66c4b81 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3027,8 +3027,11 @@ public class Editor { if (null == imm) { return; } + if (!imm.isActive(mTextView)) { + return; + } // Skip if the IME has not requested the cursor/anchor position. - if (!imm.isWatchingCursor(mTextView)) { + if (!imm.isCursorAnchorInfoEnabled()) { return; } Layout layout = mTextView.getLayout(); diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 01632ae..a35d447 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -109,6 +110,8 @@ public class PopupWindow { private int mPopupWidth; private int mPopupHeight; + private float mElevation; + private int[] mDrawingLocation = new int[2]; private int[] mScreenLocation = new int[2]; private Rect mTempRect = new Rect(); @@ -196,6 +199,7 @@ public class PopupWindow { attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes); mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground); + mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); @@ -319,25 +323,49 @@ public class PopupWindow { } /** - * <p>Return the drawable used as the popup window's background.</p> + * Return the drawable used as the popup window's background. * - * @return the background drawable or null + * @return the background drawable or {@code null} if not set + * @see #setBackgroundDrawable(Drawable) + * @attr ref android.R.styleable#PopupWindow_popupBackground */ public Drawable getBackground() { return mBackground; } /** - * <p>Change the background drawable for this popup window. The background - * can be set to null.</p> + * Specifies the background drawable for this popup window. The background + * can be set to {@code null}. * * @param background the popup's background + * @see #getBackground() + * @attr ref android.R.styleable#PopupWindow_popupBackground */ public void setBackgroundDrawable(Drawable background) { mBackground = background; } /** + * @return the elevation for this popup window in pixels + * @see #setElevation(float) + * @attr ref android.R.styleable#PopupWindow_popupElevation + */ + public float getElevation() { + return mElevation; + } + + /** + * Specifies the elevation for this popup window. + * + * @param elevation the popup's elevation in pixels + * @see #getElevation() + * @attr ref android.R.styleable#PopupWindow_popupElevation + */ + public void setElevation(float elevation) { + mElevation = elevation; + } + + /** * <p>Return the animation style to use the popup appears and disappears</p> * * @return the animation style to use the popup appears and disappears @@ -973,7 +1001,7 @@ public class PopupWindow { /** * <p>Prepare the popup by embedding in into a new ViewGroup if the * background drawable is not null. If embedding is required, the layout - * parameters' height is mnodified to take into account the background's + * parameters' height is modified to take into account the background's * padding.</p> * * @param p the layout parameters of the popup's content view @@ -998,13 +1026,15 @@ public class PopupWindow { PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); - popupViewContainer.setBackgroundDrawable(mBackground); + popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } + + mPopupView.setElevation(mElevation); mPopupViewInitialLayoutDirectionInherited = (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; @@ -1066,6 +1096,10 @@ public class PopupWindow { p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); + // TODO: Use real shadow insets once that algorithm is finalized. + final int shadowInset = (int) Math.ceil(mElevation * 2); + p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset); + return p; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index dd58ba6..fac0eb2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -279,6 +279,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private ColorStateList mTextColor; private ColorStateList mHintTextColor; private ColorStateList mLinkTextColor; + @ViewDebug.ExportedProperty(category = "text") private int mCurTextColor; private int mCurHintTextColor; private boolean mFreezesText; @@ -2519,6 +2520,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * @return the size (in scaled pixels) of thee default text size in this TextView. + * @hide + */ + @ViewDebug.ExportedProperty(category = "text") + public float getScaledTextSize() { + return mTextPaint.getTextSize() / mTextPaint.density; + } + + /** @hide */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), + @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), + @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), + @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") + }) + public int getTypefaceStyle() { + return mTextPaint.getTypeface().getStyle(); + } + + /** * Set the default text size to the given value, interpreted as "scaled * pixel" units. This size is adjusted based on the current density and * user font size preference. diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index 24950f6..712e6d0 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -14,7 +14,6 @@ * limitations under the License. */ - package android.widget; import android.annotation.NonNull; @@ -27,16 +26,15 @@ import android.os.Parcelable; import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.CollapsibleActionView; +import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewDebug; import android.view.ViewGroup; -import android.view.Window; + import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuItemImpl; @@ -104,6 +102,12 @@ public class Toolbar extends ViewGroup { private ImageButton mCollapseButtonView; View mExpandedActionView; + /** Context against which to inflate popup menus. */ + private Context mPopupContext; + + /** Theme resource against which to inflate popup menus. */ + private int mPopupTheme; + private int mTitleTextAppearance; private int mSubtitleTextAppearance; private int mNavButtonStyle; @@ -230,6 +234,36 @@ public class Toolbar extends ViewGroup { setSubtitle(subtitle); } a.recycle(); + + mPopupContext = context; + mPopupTheme = 0; + } + + /** + * Specifies the theme to use when inflating popup menus. By default, uses + * the same theme as the toolbar itself. + * + * @param resId theme used to inflate popup menus + * @see #getPopupTheme() + */ + public void setPopupTheme(int resId) { + if (mPopupTheme != resId) { + mPopupTheme = resId; + if (resId == 0) { + mPopupContext = mContext; + } else { + mPopupContext = new ContextThemeWrapper(mContext, resId); + } + } + } + + /** + * @return resource identifier of the theme used to inflate popup menus, or + * 0 if menus are inflated against the toolbar theme + * @see #setPopupTheme(int) + */ + public int getPopupTheme() { + return mPopupTheme; } @Override @@ -306,22 +340,21 @@ public class Toolbar extends ViewGroup { oldMenu.removeMenuPresenter(mExpandedMenuPresenter); } - final Context context = getContext(); - if (mExpandedMenuPresenter == null) { mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); } outerPresenter.setExpandedActionViewsExclusive(true); if (menu != null) { - menu.addMenuPresenter(outerPresenter); - menu.addMenuPresenter(mExpandedMenuPresenter); + menu.addMenuPresenter(outerPresenter, mPopupContext); + menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); } else { - outerPresenter.initForMenu(context, null); - mExpandedMenuPresenter.initForMenu(context, null); + outerPresenter.initForMenu(mPopupContext, null); + mExpandedMenuPresenter.initForMenu(mPopupContext, null); outerPresenter.updateMenuView(true); mExpandedMenuPresenter.updateMenuView(true); } + mMenuView.setPopupTheme(mPopupTheme); mMenuView.setPresenter(outerPresenter); mOuterActionMenuPresenter = outerPresenter; } @@ -768,13 +801,14 @@ public class Toolbar extends ViewGroup { mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); } mMenuView.setExpandedActionViewsExclusive(true); - menu.addMenuPresenter(mExpandedMenuPresenter); + menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); } } private void ensureMenuView() { if (mMenuView == null) { mMenuView = new ActionMenuView(getContext()); + mMenuView.setPopupTheme(mPopupTheme); mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener); final LayoutParams lp = generateDefaultLayoutParams(); lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 98e35dd..c78f770 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -20,6 +20,7 @@ import android.content.Intent; import android.os.Bundle; import com.android.internal.app.IVoiceInteractor; +import android.hardware.soundtrigger.KeyphraseSoundModel; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -29,4 +30,16 @@ interface IVoiceInteractionManagerService { IVoiceInteractor interactor); int startVoiceActivity(IBinder token, in Intent intent, String resolvedType); void finish(IBinder token); + + /** + * Lists the registered Sound models for keyphrase detection. + * May be null if no matching sound models exist. + * + * @param service The current voice interaction service. + */ + List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels(in IVoiceInteractionService service); + /** + * Updates the given keyphrase sound model. Adds the model if it doesn't exist currently. + */ + int updateKeyphraseSoundModel(in KeyphraseSoundModel model); } diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index e8a3f0a..7af52f3 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -122,6 +122,16 @@ public class ToolbarActionBar extends ActionBar { } @Override + public void setElevation(float elevation) { + mToolbar.setElevation(elevation); + } + + @Override + public float getElevation() { + return mToolbar.getElevation(); + } + + @Override public Context getThemedContext() { return mToolbar.getContext(); } diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 7bd316f..b5ff0cc 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -222,6 +222,10 @@ public class WindowDecorActionBar extends ActionBar implements if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) { setHideOnContentScrollEnabled(true); } + final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0); + if (elevation != 0) { + setElevation(elevation); + } a.recycle(); } @@ -236,6 +240,19 @@ public class WindowDecorActionBar extends ActionBar implements } } + @Override + public void setElevation(float elevation) { + mContainerView.setElevation(elevation); + if (mSplitView != null) { + mSplitView.setElevation(elevation); + } + } + + @Override + public float getElevation() { + return mContainerView.getElevation(); + } + public void onConfigurationChanged(Configuration newConfig) { setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); } diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 273de61..023ba03 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -48,6 +48,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Arrays; /** * A helper class for retrieving the power usage information for all applications and services. @@ -82,7 +83,6 @@ public class BatteryStatsHelper { private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>(); private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; - private int mAsUser = 0; long mRawRealtime; long mRawUptime; @@ -176,11 +176,34 @@ public class BatteryStatsHelper { * Refreshes the power usage list. */ public void refreshStats(int statsType, int asUser) { - refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000, + SparseArray<UserHandle> users = new SparseArray<UserHandle>(1); + users.put(asUser, new UserHandle(asUser)); + refreshStats(statsType, users); + } + + /** + * Refreshes the power usage list. + */ + public void refreshStats(int statsType, List<UserHandle> asUsers) { + final int n = asUsers.size(); + SparseArray<UserHandle> users = new SparseArray<UserHandle>(n); + for (int i = 0; i < n; ++i) { + UserHandle userHandle = asUsers.get(i); + users.put(userHandle.getIdentifier(), userHandle); + } + refreshStats(statsType, users); + } + + /** + * Refreshes the power usage list. + */ + public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) { + refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000, SystemClock.uptimeMillis() * 1000); } - public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) { + public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs, + long rawUptimeUs) { // Initialize mStats if necessary. getStats(); @@ -204,7 +227,6 @@ public class BatteryStatsHelper { } mStatsType = statsType; - mAsUser = asUser; mRawUptime = rawUptimeUs; mRawRealtime = rawRealtimeUs; mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs); @@ -227,7 +249,7 @@ public class BatteryStatsHelper { mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() * mPowerProfile.getBatteryCapacity()) / 100; - processAppUsage(); + processAppUsage(asUsers); // Before aggregating apps in to users, collect all apps to sort by their ms per packet. for (int i=0; i<mUsageList.size(); i++) { @@ -280,7 +302,8 @@ public class BatteryStatsHelper { Collections.sort(mUsageList); } - private void processAppUsage() { + private void processAppUsage(SparseArray<UserHandle> asUsers) { + final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); SensorManager sensorManager = (SensorManager) mContext.getSystemService( Context.SENSOR_SERVICE); final int which = mStatsType; @@ -499,7 +522,7 @@ public class BatteryStatsHelper { } else if (u.getUid() == Process.BLUETOOTH_UID) { mBluetoothSippers.add(app); mBluetoothPower += power; - } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser + } else if (!forAllUsers && asUsers.get(userId) == null && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { List<BatterySipper> list = mUserSippers.get(userId); if (list == null) { diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index c792d78..897381d 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -25,6 +25,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -54,6 +55,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_REPORT_FULLSCREEN_MODE = 100; private static final int DO_PERFORM_PRIVATE_COMMAND = 120; private static final int DO_CLEAR_META_KEY_STATES = 130; + private static final int DO_REQUEST_CURSOR_ANCHOR_INFO = 140; private WeakReference<InputConnection> mInputConnection; @@ -175,6 +177,11 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data)); } + public void requestCursorAnchorInfo(CursorAnchorInfoRequest request, int seq, + IInputContextCallback callback) { + dispatchMessage(obtainMessageOSC(DO_REQUEST_CURSOR_ANCHOR_INFO, request, seq, callback)); + } + void dispatchMessage(Message msg) { // If we are calling this from the main thread, then we can call // right through. Otherwise, we need to send the message to the @@ -420,6 +427,23 @@ public class IInputConnectionWrapper extends IInputContext.Stub { (Bundle)args.arg2); return; } + case DO_REQUEST_CURSOR_ANCHOR_INFO: { + SomeArgs args = (SomeArgs)msg.obj; + try { + InputConnection ic = mInputConnection.get(); + if (ic == null || !isActive()) { + Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); + args.callback.setRequestCursorAnchorInfoResult(0, args.seq); + return; + } + args.callback.setRequestCursorAnchorInfoResult( + ic.requestCursorAnchorInfo((CursorAnchorInfoRequest)args.arg1), + args.seq); + } catch (RemoteException e) { + Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e); + } + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -449,7 +473,15 @@ public class IInputConnectionWrapper extends IInputContext.Stub { args.seq = seq; return mH.obtainMessage(what, arg1, arg2, args); } - + + Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) { + SomeArgs args = new SomeArgs(); + args.arg1 = arg1; + args.callback = callback; + args.seq = seq; + return mH.obtainMessage(what, 0, 0, args); + } + Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq, IInputContextCallback callback) { SomeArgs args = new SomeArgs(); diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 719a24f..c06596a 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.ExtractedTextRequest; import com.android.internal.view.IInputContextCallback; @@ -73,4 +74,6 @@ import com.android.internal.view.IInputContextCallback; void getSelectedText(int flags, int seq, IInputContextCallback callback); + void requestCursorAnchorInfo(in CursorAnchorInfoRequest request, int seq, + IInputContextCallback callback); } diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl index 661066b..ab2fbdc 100644 --- a/core/java/com/android/internal/view/IInputContextCallback.aidl +++ b/core/java/com/android/internal/view/IInputContextCallback.aidl @@ -27,4 +27,5 @@ oneway interface IInputContextCallback { void setCursorCapsMode(int capsMode, int seq); void setExtractedText(in ExtractedText extractedText, int seq); void setSelectedText(CharSequence selectedText, int seq); + void setRequestCursorAnchorInfoResult(int result, int seq); } diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index b100d27..89d36ff 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -27,6 +27,5 @@ oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); void onUnbindMethod(int sequence); void setActive(boolean active); - void setCursorAnchorMonitorMode(int monitorMode); void setUserActionNotificationSequenceNumber(int sequenceNumber); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b84c359..6f104dd 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -78,5 +78,4 @@ interface IInputMethodManager { void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); int getInputMethodWindowVisibleHeight(); oneway void notifyUserAction(int sequenceNumber); - void setCursorAnchorMonitorMode(in IBinder token, int monitorMode); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 9024d8d..8535a98 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -23,6 +23,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -40,6 +41,7 @@ public class InputConnectionWrapper implements InputConnection { public CharSequence mSelectedText; public ExtractedText mExtractedText; public int mCursorCapsMode; + public int mCursorAnchorInfoRequestResult; // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain // exclusive access to this object. @@ -152,7 +154,20 @@ public class InputConnectionWrapper implements InputConnection { } } } - + + public void setRequestCursorAnchorInfoResult(int result, int seq) { + synchronized (this) { + if (seq == mSeq) { + mCursorAnchorInfoRequestResult = result; + mHaveValue = true; + notifyAll(); + } else { + Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq + + ") in setCursorAnchorInfoRequestResult, ignoring."); + } + } + } + /** * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds. * @@ -413,4 +428,22 @@ public class InputConnectionWrapper implements InputConnection { return false; } } + + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) { + int value = CursorAnchorInfoRequest.RESULT_NOT_HANDLED; + try { + InputContextCallback callback = InputContextCallback.getInstance(); + mIInputContext.requestCursorAnchorInfo(request, callback.mSeq, callback); + synchronized (callback) { + callback.waitForResultLocked(); + if (callback.mHaveValue) { + value = callback.mCursorAnchorInfoRequestResult; + } + } + callback.dispose(); + } catch (RemoteException e) { + return CursorAnchorInfoRequest.RESULT_NOT_HANDLED; + } + return value; + } } diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 5d7d322..e8d1ead 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -212,8 +212,21 @@ public class MenuBuilder implements Menu { * @param presenter The presenter to add */ public void addMenuPresenter(MenuPresenter presenter) { + addMenuPresenter(presenter, mContext); + } + + /** + * Add a presenter to this menu that uses an alternate context for + * inflating menu items. This will only hold a WeakReference; you do not + * need to explicitly remove a presenter, but you can using + * {@link #removeMenuPresenter(MenuPresenter)}. + * + * @param presenter The presenter to add + * @param menuContext The context used to inflate menu items + */ + public void addMenuPresenter(MenuPresenter presenter, Context menuContext) { mPresenters.add(new WeakReference<MenuPresenter>(presenter)); - presenter.initForMenu(mContext, this); + presenter.initForMenu(menuContext, this); mIsActionItemsStale = true; } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 5a12893..40f58e9 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -95,7 +95,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On mAnchorView = anchorView; - menu.addMenuPresenter(this); + // Present the menu using our context, not the menu builder's context. + menu.addMenuPresenter(this, context); } public void setAnchorView(View anchor) { diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java index d913a39..f207b98 100644 --- a/core/java/com/android/internal/view/menu/MenuPresenter.java +++ b/core/java/com/android/internal/view/menu/MenuPresenter.java @@ -18,7 +18,6 @@ package com.android.internal.view.menu; import android.content.Context; import android.os.Parcelable; -import android.view.Menu; import android.view.ViewGroup; /** @@ -61,7 +60,7 @@ public interface MenuPresenter { /** * Retrieve a MenuView to display the menu specified in - * {@link #initForMenu(Context, Menu)}. + * {@link #initForMenu(Context, MenuBuilder)}. * * @param root Intended parent of the MenuView. * @return A freshly created MenuView. diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index 9e7ff93..850ea23 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -16,6 +16,9 @@ package com.android.internal.widget; import com.android.internal.R; + +import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.widget.ActionMenuPresenter; import android.widget.ActionMenuView; @@ -32,6 +35,15 @@ import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; public abstract class AbsActionBarView extends ViewGroup { + private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); + + private static final int FADE_DURATION = 200; + + protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); + + /** Context against which to inflate popup menus. */ + protected final Context mPopupContext; + protected ActionMenuView mMenuView; protected ActionMenuPresenter mActionMenuPresenter; protected ViewGroup mSplitView; @@ -40,11 +52,6 @@ public abstract class AbsActionBarView extends ViewGroup { protected int mContentHeight; protected Animator mVisibilityAnim; - protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener(); - - private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); - - private static final int FADE_DURATION = 200; public AbsActionBarView(Context context) { this(context, null); @@ -61,6 +68,14 @@ public abstract class AbsActionBarView extends ViewGroup { public AbsActionBarView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + + final TypedValue tv = new TypedValue(); + if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true) + && tv.resourceId != 0) { + mPopupContext = new ContextThemeWrapper(context, tv.resourceId); + } else { + mPopupContext = context; + } } @Override diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 6ff77a0..c7ac815 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -16,6 +16,9 @@ package com.android.internal.widget; import com.android.internal.R; + +import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.widget.ActionMenuPresenter; import android.widget.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; @@ -231,7 +234,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); if (!mSplitActionBar) { - menu.addMenuPresenter(mActionMenuPresenter); + menu.addMenuPresenter(mActionMenuPresenter, mPopupContext); mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); mMenuView.setBackgroundDrawable(null); addView(mMenuView, layoutParams); @@ -244,7 +247,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi // Span the whole width layoutParams.width = LayoutParams.MATCH_PARENT; layoutParams.height = mContentHeight; - menu.addMenuPresenter(mActionMenuPresenter); + menu.addMenuPresenter(mActionMenuPresenter, mPopupContext); mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); mMenuView.setBackgroundDrawable(mSplitBackground); mSplitView.addView(mMenuView, layoutParams); diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 77559c0..e53af69 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -29,7 +29,9 @@ import android.os.Parcelable; import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.CollapsibleActionView; +import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -435,11 +437,11 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar { private void configPresenters(MenuBuilder builder) { if (builder != null) { - builder.addMenuPresenter(mActionMenuPresenter); - builder.addMenuPresenter(mExpandedMenuPresenter); + builder.addMenuPresenter(mActionMenuPresenter, mPopupContext); + builder.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); } else { - mActionMenuPresenter.initForMenu(mContext, null); - mExpandedMenuPresenter.initForMenu(mContext, null); + mActionMenuPresenter.initForMenu(mPopupContext, null); + mExpandedMenuPresenter.initForMenu(mPopupContext, null); mActionMenuPresenter.updateMenuView(true); mExpandedMenuPresenter.updateMenuView(true); } diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 4cbdf78..10a7794 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -25,6 +25,7 @@ import android.util.Log; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfoRequest; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.widget.TextView; @@ -185,4 +186,18 @@ public class EditableInputConnection extends BaseInputConnection { return success; } + + @Override + public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) { + if (DEBUG) Log.v(TAG, "requestCursorAnchorInfo " + request); + final int result = super.requestCursorAnchorInfo(request); + if (mIMM != null && request != null && (request.getRequestType() == + CursorAnchorInfoRequest.TYPE_CURSOR_ANCHOR_INFO)) { + mIMM.setCursorAnchorInfoMonitorMode(request.getRequestFlags()); + // One-shot event is not yet fully supported. + // TODO: Support one-shot event correctly. + return CursorAnchorInfoRequest.RESULT_SCHEDULED; + } + return result; + } } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 869a91b..ef7ef0a 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -139,6 +139,7 @@ LOCAL_SRC_FILES:= \ android_hardware_Camera.cpp \ android_hardware_camera2_CameraMetadata.cpp \ android_hardware_camera2_legacy_LegacyCameraDevice.cpp \ + android_hardware_camera2_legacy_PerfMeasurement.cpp \ android_hardware_camera2_DngCreator.cpp \ android_hardware_SensorManager.cpp \ android_hardware_SerialPort.cpp \ @@ -242,7 +243,8 @@ LOCAL_SHARED_LIBRARIES := \ libnetd_client \ libsoundtrigger \ libminikin \ - libstlport + libstlport \ + libprocessgroup \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f7cd8c3..9b66734 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -79,6 +79,7 @@ extern int register_android_opengl_jni_GLES31Ext(JNIEnv* env); extern int register_android_hardware_Camera(JNIEnv *env); extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env); extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env); +extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env); extern int register_android_hardware_camera2_DngCreator(JNIEnv *env); extern int register_android_hardware_SensorManager(JNIEnv *env); extern int register_android_hardware_SerialPort(JNIEnv *env); @@ -1314,6 +1315,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice), + REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement), REG_JNI(register_android_hardware_camera2_DngCreator), REG_JNI(register_android_hardware_SensorManager), REG_JNI(register_android_hardware_SerialPort), diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp index 33100bf..3a3328f 100644 --- a/core/jni/android_hardware_camera2_DngCreator.cpp +++ b/core/jni/android_hardware_camera2_DngCreator.cpp @@ -24,11 +24,14 @@ #include <img_utils/TiffIfd.h> #include <img_utils/TiffWriter.h> #include <img_utils/Output.h> +#include <img_utils/Input.h> +#include <img_utils/StripSource.h> #include <utils/Log.h> #include <utils/Errors.h> #include <utils/StrongPointer.h> #include <utils/RefBase.h> +#include <utils/Vector.h> #include <cutils/properties.h> #include <string.h> @@ -42,17 +45,17 @@ using namespace android; using namespace img_utils; -#define BAIL_IF_INVALID(expr, jnienv, tagId) \ +#define BAIL_IF_INVALID(expr, jnienv, tagId, writer) \ if ((expr) != OK) { \ jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ - "Invalid metadata for tag %x", tagId); \ + "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \ return; \ } -#define BAIL_IF_EMPTY(entry, jnienv, tagId) \ +#define BAIL_IF_EMPTY(entry, jnienv, tagId, writer) \ if (entry.count == 0) { \ jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ - "Missing metadata fields for tag %x", tagId); \ + "Missing metadata fields for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \ return; \ } @@ -66,15 +69,98 @@ static struct { jmethodID mWriteMethod; } gOutputStreamClassInfo; +static struct { + jmethodID mReadMethod; + jmethodID mSkipMethod; +} gInputStreamClassInfo; + +static struct { + jmethodID mGetMethod; +} gInputByteBufferClassInfo; + enum { BITS_PER_SAMPLE = 16, BYTES_PER_SAMPLE = 2, - TIFF_IFD_0 = 0 + BYTES_PER_RGB_PIXEL = 3, + BITS_PER_RGB_SAMPLE = 8, + BYTES_PER_RGB_SAMPLE = 1, + SAMPLES_PER_RGB_PIXEL = 3, + SAMPLES_PER_RAW_PIXEL = 1, + TIFF_IFD_0 = 0, + TIFF_IFD_SUB1 = 1, + TIFF_IFD_GPSINFO = 2, }; // ---------------------------------------------------------------------------- -// This class is not intended to be used across JNI calls. +/** + * Container class for the persistent native context. + */ + +class NativeContext : public LightRefBase<NativeContext> { + +public: + NativeContext(); + virtual ~NativeContext(); + + TiffWriter* getWriter(); + + uint32_t getThumbnailWidth(); + uint32_t getThumbnailHeight(); + const uint8_t* getThumbnail(); + + bool setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height); + +private: + Vector<uint8_t> mCurrentThumbnail; + TiffWriter mWriter; + uint32_t mThumbnailWidth; + uint32_t mThumbnailHeight; +}; + +NativeContext::NativeContext() : mThumbnailWidth(0), mThumbnailHeight(0) {} + +NativeContext::~NativeContext() {} + +TiffWriter* NativeContext::getWriter() { + return &mWriter; +} + +uint32_t NativeContext::getThumbnailWidth() { + return mThumbnailWidth; +} + +uint32_t NativeContext::getThumbnailHeight() { + return mThumbnailHeight; +} + +const uint8_t* NativeContext::getThumbnail() { + return mCurrentThumbnail.array(); +} + +bool NativeContext::setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height) { + mThumbnailWidth = width; + mThumbnailHeight = height; + + size_t size = BYTES_PER_RGB_PIXEL * width * height; + if (mCurrentThumbnail.resize(size) < 0) { + ALOGE("%s: Could not resize thumbnail buffer.", __FUNCTION__); + return false; + } + + uint8_t* thumb = mCurrentThumbnail.editArray(); + memcpy(thumb, buffer, size); + return true; +} + +// End of NativeContext +// ---------------------------------------------------------------------------- + +/** + * Wrapper class for a Java OutputStream. + * + * This class is not intended to be used across JNI calls. + */ class JniOutputStream : public Output, public LightRefBase<JniOutputStream> { public: JniOutputStream(JNIEnv* env, jobject outStream); @@ -82,11 +168,13 @@ public: virtual ~JniOutputStream(); status_t open(); + status_t write(const uint8_t* buf, size_t offset, size_t count); + status_t close(); private: enum { - BYTE_ARRAY_LENGTH = 1024 + BYTE_ARRAY_LENGTH = 4096 }; jobject mOutputStream; JNIEnv* mEnv; @@ -138,27 +226,465 @@ status_t JniOutputStream::close() { return OK; } +// End of JniOutputStream // ---------------------------------------------------------------------------- +/** + * Wrapper class for a Java InputStream. + * + * This class is not intended to be used across JNI calls. + */ +class JniInputStream : public Input, public LightRefBase<JniInputStream> { +public: + JniInputStream(JNIEnv* env, jobject inStream); + + status_t open(); + + status_t close(); + + ssize_t read(uint8_t* buf, size_t offset, size_t count); + + ssize_t skip(size_t count); + + virtual ~JniInputStream(); +private: + enum { + BYTE_ARRAY_LENGTH = 4096 + }; + jobject mInStream; + JNIEnv* mEnv; + jbyteArray mByteArray; + +}; + +JniInputStream::JniInputStream(JNIEnv* env, jobject inStream) : mInStream(inStream), mEnv(env) { + mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH); + if (mByteArray == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array."); + } +} + +JniInputStream::~JniInputStream() { + mEnv->DeleteLocalRef(mByteArray); +} + +ssize_t JniInputStream::read(uint8_t* buf, size_t offset, size_t count) { + + jint realCount = BYTE_ARRAY_LENGTH; + if (count < BYTE_ARRAY_LENGTH) { + realCount = count; + } + jint actual = mEnv->CallIntMethod(mInStream, gInputStreamClassInfo.mReadMethod, mByteArray, 0, + realCount); + + if (actual < 0) { + return NOT_ENOUGH_DATA; + } + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + mEnv->GetByteArrayRegion(mByteArray, 0, actual, reinterpret_cast<jbyte*>(buf + offset)); + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + return actual; +} + +ssize_t JniInputStream::skip(size_t count) { + jlong actual = mEnv->CallLongMethod(mInStream, gInputStreamClassInfo.mSkipMethod, + static_cast<jlong>(count)); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + if (actual < 0) { + return NOT_ENOUGH_DATA; + } + return actual; +} + +status_t JniInputStream::open() { + // Do nothing + return OK; +} + +status_t JniInputStream::close() { + // Do nothing + return OK; +} + +// End of JniInputStream +// ---------------------------------------------------------------------------- + +/** + * Wrapper class for a non-direct Java ByteBuffer. + * + * This class is not intended to be used across JNI calls. + */ +class JniInputByteBuffer : public Input, public LightRefBase<JniInputByteBuffer> { +public: + JniInputByteBuffer(JNIEnv* env, jobject inBuf); + + status_t open(); + + status_t close(); + + ssize_t read(uint8_t* buf, size_t offset, size_t count); + + virtual ~JniInputByteBuffer(); +private: + enum { + BYTE_ARRAY_LENGTH = 4096 + }; + jobject mInBuf; + JNIEnv* mEnv; + jbyteArray mByteArray; +}; + +JniInputByteBuffer::JniInputByteBuffer(JNIEnv* env, jobject inBuf) : mInBuf(inBuf), mEnv(env) { + mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH); + if (mByteArray == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array."); + } +} + +JniInputByteBuffer::~JniInputByteBuffer() { + mEnv->DeleteLocalRef(mByteArray); +} + +ssize_t JniInputByteBuffer::read(uint8_t* buf, size_t offset, size_t count) { + jint realCount = BYTE_ARRAY_LENGTH; + if (count < BYTE_ARRAY_LENGTH) { + realCount = count; + } + + mEnv->CallObjectMethod(mInBuf, gInputByteBufferClassInfo.mGetMethod, mByteArray, 0, + realCount); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + mEnv->GetByteArrayRegion(mByteArray, 0, realCount, reinterpret_cast<jbyte*>(buf + offset)); + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + return realCount; +} + +status_t JniInputByteBuffer::open() { + // Do nothing + return OK; +} + +status_t JniInputByteBuffer::close() { + // Do nothing + return OK; +} + +// End of JniInputByteBuffer +// ---------------------------------------------------------------------------- + +/** + * StripSource subclass for Input types. + * + * This class is not intended to be used across JNI calls. + */ + +class InputStripSource : public StripSource, public LightRefBase<InputStripSource> { +public: + InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width, uint32_t height, + uint32_t pixStride, uint32_t rowStride, uint64_t offset, uint32_t bytesPerSample, + uint32_t samplesPerPixel); + + virtual ~InputStripSource(); + + virtual status_t writeToStream(Output& stream, uint32_t count); + + virtual uint32_t getIfd() const; +protected: + uint32_t mIfd; + Input* mInput; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mPixStride; + uint32_t mRowStride; + uint64_t mOffset; + JNIEnv* mEnv; + uint32_t mBytesPerSample; + uint32_t mSamplesPerPixel; +}; + +InputStripSource::InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width, + uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset, + uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), mInput(&input), + mWidth(width), mHeight(height), mPixStride(pixStride), mRowStride(rowStride), + mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample), + mSamplesPerPixel(samplesPerPixel) {} + +InputStripSource::~InputStripSource() {} + +status_t InputStripSource::writeToStream(Output& stream, uint32_t count) { + status_t err = OK; + uint32_t fullSize = mRowStride * mHeight; + jlong offset = mOffset; + + if (fullSize != count) { + ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count, + fullSize); + jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write"); + return BAD_VALUE; + } + + // Skip offset + while (offset > 0) { + ssize_t skipped = mInput->skip(offset); + if (skipped <= 0) { + if (skipped == NOT_ENOUGH_DATA || skipped == 0) { + jniThrowExceptionFmt(mEnv, "java/io/IOException", + "Early EOF encountered in skip, not enough pixel data for image of size %u", + fullSize); + skipped = NOT_ENOUGH_DATA; + } else { + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", + "Error encountered while skip bytes in input stream."); + } + } + + return skipped; + } + offset -= skipped; + } + + Vector<uint8_t> row; + if (row.resize(mRowStride) < 0) { + jniThrowException(mEnv, "java/lang/OutOfMemoryError", "Could not allocate row vector."); + return BAD_VALUE; + } + + uint8_t* rowBytes = row.editArray(); + + for (uint32_t i = 0; i < mHeight; ++i) { + size_t rowFillAmt = 0; + size_t rowSize = mPixStride; + + while (rowFillAmt < mRowStride) { + ssize_t bytesRead = mInput->read(rowBytes, rowFillAmt, rowSize); + if (bytesRead <= 0) { + if (bytesRead == NOT_ENOUGH_DATA || bytesRead == 0) { + jniThrowExceptionFmt(mEnv, "java/io/IOException", + "Early EOF encountered, not enough pixel data for image of size %u", + fullSize); + bytesRead = NOT_ENOUGH_DATA; + } else { + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", + "Error encountered while reading"); + } + } + return bytesRead; + } + rowFillAmt += bytesRead; + rowSize -= bytesRead; + } + + if (mPixStride == mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using stream per-row write for strip.", __FUNCTION__); + + if (stream.write(rowBytes, 0, mBytesPerSample * mSamplesPerPixel * mWidth) != OK || + mEnv->ExceptionCheck()) { + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } + return BAD_VALUE; + } + } else { + ALOGV("%s: Using stream per-pixel write for strip.", __FUNCTION__); + jniThrowException(mEnv, "java/lang/IllegalStateException", + "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous"); + return BAD_VALUE; + + // TODO: Add support for non-contiguous pixels if needed. + } + } + return OK; +} + +uint32_t InputStripSource::getIfd() const { + return mIfd; +} + +// End of InputStripSource +// ---------------------------------------------------------------------------- + +/** + * StripSource subclass for direct buffer types. + * + * This class is not intended to be used across JNI calls. + */ + +class DirectStripSource : public StripSource, public LightRefBase<DirectStripSource> { +public: + DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd, uint32_t width, + uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset, + uint32_t bytesPerSample, uint32_t samplesPerPixel); + + virtual ~DirectStripSource(); + + virtual status_t writeToStream(Output& stream, uint32_t count); + + virtual uint32_t getIfd() const; +protected: + uint32_t mIfd; + const uint8_t* mPixelBytes; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mPixStride; + uint32_t mRowStride; + uint16_t mOffset; + JNIEnv* mEnv; + uint32_t mBytesPerSample; + uint32_t mSamplesPerPixel; +}; + +DirectStripSource::DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd, + uint32_t width, uint32_t height, uint32_t pixStride, uint32_t rowStride, + uint64_t offset, uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), + mPixelBytes(pixelBytes), mWidth(width), mHeight(height), mPixStride(pixStride), + mRowStride(rowStride), mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample), + mSamplesPerPixel(samplesPerPixel) {} + +DirectStripSource::~DirectStripSource() {} + +status_t DirectStripSource::writeToStream(Output& stream, uint32_t count) { + uint32_t fullSize = mRowStride * mHeight; + + if (fullSize != count) { + ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count, + fullSize); + jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write"); + return BAD_VALUE; + } + + if (mPixStride == mBytesPerSample * mSamplesPerPixel + && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__); + + if (stream.write(mPixelBytes, mOffset, fullSize) != OK || mEnv->ExceptionCheck()) { + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } + return BAD_VALUE; + } + } else if (mPixStride == mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using direct per-row write for strip.", __FUNCTION__); + + for (size_t i = 0; i < mHeight; ++i) { + if (stream.write(mPixelBytes, mOffset + i * mRowStride, mPixStride * mWidth) != OK || + mEnv->ExceptionCheck()) { + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } + return BAD_VALUE; + } + } + } else { + ALOGV("%s: Using direct per-pixel write for strip.", __FUNCTION__); + + jniThrowException(mEnv, "java/lang/IllegalStateException", + "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous"); + return BAD_VALUE; + + // TODO: Add support for non-contiguous pixels if needed. + } + return OK; + +} + +uint32_t DirectStripSource::getIfd() const { + return mIfd; +} + +// End of DirectStripSource +// ---------------------------------------------------------------------------- + +static bool validateDngHeader(JNIEnv* env, TiffWriter* writer, jint width, jint height) { + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + + // TODO: handle lens shading map, etc. conversions for other raw buffer sizes. + uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>()); + uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>()); + + if (width < 0 || metadataWidth != static_cast<uint32_t>(width)) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \ + "Metadata width %d doesn't match image width %d", metadataWidth, width); + return false; + } + + if (height < 0 || metadataHeight != static_cast<uint32_t>(height)) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \ + "Metadata height %d doesn't match image height %d", metadataHeight, height); + return false; + } + + return true; +} + +static status_t moveEntries(TiffWriter* writer, uint32_t ifdFrom, uint32_t ifdTo, + const Vector<uint16_t>& entries) { + for (size_t i = 0; i < entries.size(); ++i) { + uint16_t tagId = entries[i]; + sp<TiffEntry> entry = writer->getEntry(tagId, ifdFrom); + if (entry == NULL) { + ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId, + ifdFrom); + return BAD_VALUE; + } + if (writer->addEntry(entry, ifdTo) != OK) { + ALOGE("%s: moveEntries failed, could not add entry %u to IFD %u", __FUNCTION__, tagId, + ifdFrom); + return BAD_VALUE; + } + writer->removeEntry(tagId, ifdFrom); + } + return OK; +} + +// ---------------------------------------------------------------------------- extern "C" { -static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) { +static NativeContext* DngCreator_getNativeContext(JNIEnv* env, jobject thiz) { ALOGV("%s:", __FUNCTION__); - return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz, + return reinterpret_cast<NativeContext*>(env->GetLongField(thiz, gDngCreatorClassInfo.mNativeContext)); } -static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) { +static void DngCreator_setNativeContext(JNIEnv* env, jobject thiz, sp<NativeContext> context) { ALOGV("%s:", __FUNCTION__); - TiffWriter* current = DngCreator_getCreator(env, thiz); - if (writer != NULL) { - writer->incStrong((void*) DngCreator_setCreator); + NativeContext* current = DngCreator_getNativeContext(env, thiz); + + if (context != NULL) { + context->incStrong((void*) DngCreator_setNativeContext); } + if (current) { - current->decStrong((void*) DngCreator_setCreator); + current->decStrong((void*) DngCreator_setNativeContext); } + env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext, - reinterpret_cast<jlong>(writer.get())); + reinterpret_cast<jlong>(context.get())); +} + +static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + NativeContext* current = DngCreator_getNativeContext(env, thiz); + if (current) { + return current->getWriter(); + } + return NULL; } static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { @@ -174,6 +700,19 @@ static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class"); gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V"); LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method"); + + jclass inputStreamClazz = env->FindClass("java/io/InputStream"); + LOG_ALWAYS_FATAL_IF(inputStreamClazz == NULL, "Can't find java/io/InputStream class"); + gInputStreamClassInfo.mReadMethod = env->GetMethodID(inputStreamClazz, "read", "([BII)I"); + LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mReadMethod == NULL, "Can't find read method"); + gInputStreamClassInfo.mSkipMethod = env->GetMethodID(inputStreamClazz, "skip", "(J)J"); + LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mSkipMethod == NULL, "Can't find skip method"); + + jclass inputBufferClazz = env->FindClass("java/nio/ByteBuffer"); + LOG_ALWAYS_FATAL_IF(inputBufferClazz == NULL, "Can't find java/nio/ByteBuffer class"); + gInputByteBufferClassInfo.mGetMethod = env->GetMethodID(inputBufferClazz, "get", + "([BII)Ljava/nio/ByteBuffer;"); + LOG_ALWAYS_FATAL_IF(gInputByteBufferClassInfo.mGetMethod == NULL, "Can't find get method"); } static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr, @@ -192,7 +731,8 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt return; } - sp<TiffWriter> writer = new TiffWriter(); + sp<NativeContext> nativeContext = new NativeContext(); + TiffWriter* writer = nativeContext->getWriter(); writer->addIfd(TIFF_IFD_0); @@ -208,96 +748,99 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // TODO: Greensplit. // TODO: Add remaining non-essential tags + + // Setup main image tags + { // Set orientation uint16_t orientation = 1; // Normal BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env, - TAG_ORIENTATION); + TAG_ORIENTATION, writer); } { // Set subfiletype uint32_t subfileType = 0; // Main image BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env, - TAG_NEWSUBFILETYPE); + TAG_NEWSUBFILETYPE, writer); } { // Set bits per sample uint16_t bits = static_cast<uint16_t>(bitsPerSample); BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env, - TAG_BITSPERSAMPLE); + TAG_BITSPERSAMPLE, writer); } { // Set compression uint16_t compression = 1; // None BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env, - TAG_COMPRESSION); + TAG_COMPRESSION, writer); } { // Set dimensions camera_metadata_entry entry = characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); - BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH); + BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH, writer); uint32_t width = static_cast<uint32_t>(entry.data.i32[2]); uint32_t height = static_cast<uint32_t>(entry.data.i32[3]); BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env, - TAG_IMAGEWIDTH); + TAG_IMAGEWIDTH, writer); BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env, - TAG_IMAGELENGTH); + TAG_IMAGELENGTH, writer); imageWidth = width; imageHeight = height; } { // Set photometric interpretation - uint16_t interpretation = 32803; + uint16_t interpretation = 32803; // CFA BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation, - TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION); + TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer); } { // Set blacklevel tags camera_metadata_entry entry = characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN); - BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL); + BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL, writer); const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32); BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env, - TAG_BLACKLEVEL); + TAG_BLACKLEVEL, writer); uint16_t repeatDim[2] = {2, 2}; BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env, - TAG_BLACKLEVELREPEATDIM); + TAG_BLACKLEVELREPEATDIM, writer); } { // Set samples per pixel uint16_t samples = static_cast<uint16_t>(samplesPerPixel); BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0), - env, TAG_SAMPLESPERPIXEL); + env, TAG_SAMPLESPERPIXEL, writer); } { // Set planar configuration uint16_t config = 1; // Chunky BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0), - env, TAG_PLANARCONFIGURATION); + env, TAG_PLANARCONFIGURATION, writer); } { // Set CFA pattern dimensions uint16_t repeatDim[2] = {2, 2}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0), - env, TAG_CFAREPEATPATTERNDIM); + env, TAG_CFAREPEATPATTERNDIM, writer); } { // Set CFA pattern camera_metadata_entry entry = characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); - BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN); + BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN, writer); camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa = static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>( entry.data.u8[0]); @@ -305,28 +848,28 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: { uint8_t cfa[4] = {0, 1, 1, 2}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), - env, TAG_CFAPATTERN); + env, TAG_CFAPATTERN, writer); opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; break; } case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: { uint8_t cfa[4] = {1, 0, 2, 1}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), - env, TAG_CFAPATTERN); + env, TAG_CFAPATTERN, writer); opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG; break; } case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: { uint8_t cfa[4] = {1, 2, 0, 1}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), - env, TAG_CFAPATTERN); + env, TAG_CFAPATTERN, writer); opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG; break; } case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: { uint8_t cfa[4] = {2, 1, 1, 0}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), - env, TAG_CFAPATTERN); + env, TAG_CFAPATTERN, writer); opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR; break; } @@ -342,21 +885,21 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set CFA plane color uint8_t cfaPlaneColor[3] = {0, 1, 2}; BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0), - env, TAG_CFAPLANECOLOR); + env, TAG_CFAPLANECOLOR, writer); } { // Set CFA layout uint16_t cfaLayout = 1; BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0), - env, TAG_CFALAYOUT); + env, TAG_CFALAYOUT, writer); } { // image description uint8_t imageDescription = '\0'; // empty BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, TIFF_IFD_0), - env, TAG_IMAGEDESCRIPTION); + env, TAG_IMAGEDESCRIPTION, writer); } { @@ -368,7 +911,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1; BAIL_IF_INVALID(writer->addEntry(TAG_MAKE, count, reinterpret_cast<uint8_t*>(manufacturer), - TIFF_IFD_0), env, TAG_MAKE); + TIFF_IFD_0), env, TAG_MAKE, writer); } { @@ -380,23 +923,23 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint32_t count = static_cast<uint32_t>(strlen(model)) + 1; BAIL_IF_INVALID(writer->addEntry(TAG_MODEL, count, reinterpret_cast<uint8_t*>(model), - TIFF_IFD_0), env, TAG_MODEL); + TIFF_IFD_0), env, TAG_MODEL, writer); } { // x resolution uint32_t xres[] = { 72, 1 }; // default 72 ppi BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0), - env, TAG_XRESOLUTION); + env, TAG_XRESOLUTION, writer); // y resolution uint32_t yres[] = { 72, 1 }; // default 72 ppi BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0), - env, TAG_YRESOLUTION); + env, TAG_YRESOLUTION, writer); uint16_t unit = 2; // inches BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0), - env, TAG_RESOLUTIONUNIT); + env, TAG_RESOLUTIONUNIT, writer); } { @@ -405,7 +948,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt property_get("ro.build.fingerprint", software, ""); uint32_t count = static_cast<uint32_t>(strlen(software)) + 1; BAIL_IF_INVALID(writer->addEntry(TAG_SOFTWARE, count, reinterpret_cast<uint8_t*>(software), - TIFF_IFD_0), env, TAG_SOFTWARE); + TIFF_IFD_0), env, TAG_SOFTWARE, writer); } { @@ -420,12 +963,22 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt return; } - BAIL_IF_INVALID(writer->addEntry(TAG_DATETIME, DATETIME_COUNT, - reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL); + if (writer->addEntry(TAG_DATETIME, DATETIME_COUNT, + reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) { + env->ReleaseStringUTFChars(formattedCaptureTime, captureTime); + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_DATETIME); + return; + } // datetime original - BAIL_IF_INVALID(writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT, - reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL); + if (writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT, + reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) { + env->ReleaseStringUTFChars(formattedCaptureTime, captureTime); + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL); + return; + } env->ReleaseStringUTFChars(formattedCaptureTime, captureTime); } @@ -433,21 +986,21 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // TIFF/EP standard id uint8_t standardId[] = { 1, 0, 0, 0 }; BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId, - TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID); + TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer); } { // copyright uint8_t copyright = '\0'; // empty BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, ©right, - TIFF_IFD_0), env, TAG_COPYRIGHT); + TIFF_IFD_0), env, TAG_COPYRIGHT, writer); } { // exposure time camera_metadata_entry entry = results.find(ANDROID_SENSOR_EXPOSURE_TIME); - BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME); + BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME, writer); int64_t exposureTime = *(entry.data.i64); @@ -473,7 +1026,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator }; BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure, - TIFF_IFD_0), env, TAG_EXPOSURETIME); + TIFF_IFD_0), env, TAG_EXPOSURETIME, writer); } @@ -481,7 +1034,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // ISO speed ratings camera_metadata_entry entry = results.find(ANDROID_SENSOR_SENSITIVITY); - BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS); + BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS, writer); int32_t tempIso = *(entry.data.i32); if (tempIso < 0) { @@ -497,57 +1050,57 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint16_t iso = static_cast<uint16_t>(tempIso); BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso, - TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS); + TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer); } { // focal length camera_metadata_entry entry = results.find(ANDROID_LENS_FOCAL_LENGTH); - BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH); + BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH, writer); uint32_t focalLength[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 }; BAIL_IF_INVALID(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength, - TIFF_IFD_0), env, TAG_FOCALLENGTH); + TIFF_IFD_0), env, TAG_FOCALLENGTH, writer); } { // f number camera_metadata_entry entry = results.find(ANDROID_LENS_APERTURE); - BAIL_IF_EMPTY(entry, env, TAG_FNUMBER); + BAIL_IF_EMPTY(entry, env, TAG_FNUMBER, writer); uint32_t fnum[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 }; BAIL_IF_INVALID(writer->addEntry(TAG_FNUMBER, 1, fnum, - TIFF_IFD_0), env, TAG_FNUMBER); + TIFF_IFD_0), env, TAG_FNUMBER, writer); } { // Set DNG version information uint8_t version[4] = {1, 4, 0, 0}; BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0), - env, TAG_DNGVERSION); + env, TAG_DNGVERSION, writer); uint8_t backwardVersion[4] = {1, 1, 0, 0}; BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0), - env, TAG_DNGBACKWARDVERSION); + env, TAG_DNGBACKWARDVERSION, writer); } { // Set whitelevel camera_metadata_entry entry = characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL); - BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL); + BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL, writer); uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]); BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env, - TAG_WHITELEVEL); + TAG_WHITELEVEL, writer); } { // Set default scale uint32_t defaultScale[4] = {1, 1, 1, 1}; BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0), - env, TAG_DEFAULTSCALE); + env, TAG_DEFAULTSCALE, writer); } bool singleIlluminant = false; @@ -555,7 +1108,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set calibration illuminants camera_metadata_entry entry1 = characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1); - BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1); + BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer); camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2); if (entry2.count == 0) { @@ -564,12 +1117,12 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint16_t ref1 = entry1.data.u8[0]; BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1, - TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1); + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1, writer); if (!singleIlluminant) { uint16_t ref2 = entry2.data.u8[0]; BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2, - TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2); + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer); } } @@ -577,7 +1130,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set color transforms camera_metadata_entry entry1 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1); - BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1); + BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1, writer); int32_t colorTransform1[entry1.count * 2]; @@ -587,12 +1140,12 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt colorTransform1[ctr++] = entry1.data.r[i].denominator; } - BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0), - env, TAG_COLORMATRIX1); + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, + TIFF_IFD_0), env, TAG_COLORMATRIX1, writer); if (!singleIlluminant) { camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2); - BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2); + BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2, writer); int32_t colorTransform2[entry2.count * 2]; ctr = 0; @@ -601,8 +1154,8 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt colorTransform2[ctr++] = entry2.data.r[i].denominator; } - BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0), - env, TAG_COLORMATRIX2); + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, + TIFF_IFD_0), env, TAG_COLORMATRIX2, writer); } } @@ -610,7 +1163,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set calibration transforms camera_metadata_entry entry1 = characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1); - BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1); + BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1, writer); int32_t calibrationTransform1[entry1.count * 2]; @@ -621,12 +1174,12 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt } BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1, - TIFF_IFD_0), env, TAG_CAMERACALIBRATION1); + TIFF_IFD_0), env, TAG_CAMERACALIBRATION1, writer); if (!singleIlluminant) { camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2); - BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2); + BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2, writer); int32_t calibrationTransform2[entry2.count * 2]; ctr = 0; @@ -636,7 +1189,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt } BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1, - TIFF_IFD_0), env, TAG_CAMERACALIBRATION2); + TIFF_IFD_0), env, TAG_CAMERACALIBRATION2, writer); } } @@ -644,7 +1197,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set forward transforms camera_metadata_entry entry1 = characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1); - BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1); + BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1, writer); int32_t forwardTransform1[entry1.count * 2]; @@ -655,12 +1208,12 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt } BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1, - TIFF_IFD_0), env, TAG_FORWARDMATRIX1); + TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer); if (!singleIlluminant) { camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2); - BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2); + BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2, writer); int32_t forwardTransform2[entry2.count * 2]; ctr = 0; @@ -670,7 +1223,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt } BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2, - TIFF_IFD_0), env, TAG_FORWARDMATRIX2); + TIFF_IFD_0), env, TAG_FORWARDMATRIX2, writer); } } @@ -678,7 +1231,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt // Set camera neutral camera_metadata_entry entry = results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT); - BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL); + BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL, writer); uint32_t cameraNeutral[entry.count * 2]; size_t ctr = 0; @@ -690,23 +1243,18 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt } BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral, - TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL); + TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer); } { // Setup data strips // TODO: Switch to tiled implementation. - uint32_t offset = 0; - BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env, - TAG_STRIPOFFSETS); - - BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env, - TAG_ROWSPERSTRIP); - - uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel / - bitsPerByte; - BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env, - TAG_STRIPBYTECOUNTS); + if (writer->addStrip(TIFF_IFD_0) != OK) { + ALOGE("%s: Could not setup strip tags.", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to setup strip tags."); + return; + } } { @@ -717,9 +1265,9 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt uint32_t defaultCropOrigin[] = {margin, margin}; uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin}; BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin, - TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN); + TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer); BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize, - TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE); + TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE, writer); } } @@ -742,21 +1290,26 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1, reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env, - TAG_UNIQUECAMERAMODEL); + TAG_UNIQUECAMERAMODEL, writer); } { // Setup opcode List 2 camera_metadata_entry entry1 = characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE); - BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2); - uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]); - uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]); + + uint32_t lsmWidth = 0; + uint32_t lsmHeight = 0; + + if (entry1.count != 0) { + lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]); + lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]); + } camera_metadata_entry entry2 = results.find(ANDROID_STATISTICS_LENS_SHADING_MAP); - BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2); - if (entry2.count == lsmWidth * lsmHeight * 4) { + + if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) { OpcodeListBuilder builder; status_t err = builder.addGainMapsForMetadata(lsmWidth, @@ -773,7 +1326,7 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt err = builder.buildOpList(opcodeListBuf); if (err == OK) { BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf, - TIFF_IFD_0), env, TAG_OPCODELIST2); + TIFF_IFD_0), env, TAG_OPCODELIST2, writer); } else { ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__); jniThrowRuntimeException(env, "failed to construct lens shading map opcode."); @@ -783,138 +1336,505 @@ static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPt jniThrowRuntimeException(env, "failed to add lens shading map."); } } else { - ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__); + ALOGW("%s: No lens shading map found in result metadata. Image quality may be reduced.", + __FUNCTION__); } } - DngCreator_setCreator(env, thiz, writer); + DngCreator_setNativeContext(env, thiz, nativeContext); } static void DngCreator_destroy(JNIEnv* env, jobject thiz) { ALOGV("%s:", __FUNCTION__); - DngCreator_setCreator(env, thiz, NULL); + DngCreator_setNativeContext(env, thiz, NULL); } -static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) { +static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) { ALOGV("%s:", __FUNCTION__); - jniThrowRuntimeException(env, "nativeSetOrientation is not implemented"); -} -static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) { - ALOGV("%s:", __FUNCTION__); - jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented"); + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "setOrientation called with uninitialized DngCreator"); + return; + } + + uint16_t orientation = static_cast<uint16_t>(orient); + BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env, + TAG_ORIENTATION, writer); + + // Set main image orientation also if in a separate IFD + if (writer->hasIfd(TIFF_IFD_SUB1)) { + BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_SUB1), env, + TAG_ORIENTATION, writer); + } } -static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height, - jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride, - jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) { +static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) { ALOGV("%s:", __FUNCTION__); - jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented"); + + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "setDescription called with uninitialized DngCreator"); + return; + } + + const char* desc = env->GetStringUTFChars(description, NULL); + size_t len = strlen(desc) + 1; + + if (writer->addEntry(TAG_IMAGEDESCRIPTION, len, + reinterpret_cast<const uint8_t*>(desc), TIFF_IFD_0) != OK) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION); + } + + env->ReleaseStringUTFChars(description, desc); } -static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width, - jint height, jobject inBuffer, jint rowStride, jint pixStride) { +static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag, jstring latRef, + jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) { ALOGV("%s:", __FUNCTION__); - sp<JniOutputStream> out = new JniOutputStream(env, outStream); - if(env->ExceptionCheck()) { - ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "setGpsTags called with uninitialized DngCreator"); return; } - uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer)); - if (pixelBytes == NULL) { - ALOGE("%s: Could not get native byte buffer", __FUNCTION__); - jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer"); + if (!writer->hasIfd(TIFF_IFD_GPSINFO)) { + if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) { + ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO, + TIFF_IFD_0); + jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO"); + return; + } + } + + const jsize GPS_VALUE_LENGTH = 6; + jsize latLen = env->GetArrayLength(latTag); + jsize longLen = env->GetArrayLength(longTag); + jsize timeLen = env->GetArrayLength(timeTag); + if (latLen != GPS_VALUE_LENGTH) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid latitude tag length"); + return; + } else if (longLen != GPS_VALUE_LENGTH) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid longitude tag length"); return; + } else if (timeLen != GPS_VALUE_LENGTH) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid time tag length"); + return; + } + + uint32_t latitude[GPS_VALUE_LENGTH]; + uint32_t longitude[GPS_VALUE_LENGTH]; + uint32_t timestamp[GPS_VALUE_LENGTH]; + + env->GetIntArrayRegion(latTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH), + reinterpret_cast<jint*>(&latitude)); + env->GetIntArrayRegion(longTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH), + reinterpret_cast<jint*>(&longitude)); + env->GetIntArrayRegion(timeTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH), + reinterpret_cast<jint*>(×tamp)); + + const jsize GPS_REF_LENGTH = 2; + const jsize GPS_DATE_LENGTH = 11; + uint8_t latitudeRef[GPS_REF_LENGTH]; + uint8_t longitudeRef[GPS_REF_LENGTH]; + uint8_t date[GPS_DATE_LENGTH]; + + env->GetStringUTFRegion(latRef, 0, 1, reinterpret_cast<char*>(&latitudeRef)); + latitudeRef[GPS_REF_LENGTH - 1] = '\0'; + env->GetStringUTFRegion(longRef, 0, 1, reinterpret_cast<char*>(&longitudeRef)); + longitudeRef[GPS_REF_LENGTH - 1] = '\0'; + + env->GetStringUTFRegion(dateTag, 0, GPS_DATE_LENGTH - 1, reinterpret_cast<char*>(&date)); + date[GPS_DATE_LENGTH - 1] = '\0'; + + { + uint8_t version[] = {2, 3, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_GPSVERSIONID, 4, version, + TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer); + } + + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDEREF, GPS_REF_LENGTH, latitudeRef, + TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDEREF, writer); + } + + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDEREF, GPS_REF_LENGTH, longitudeRef, + TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDEREF, writer); } + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDE, 3, latitude, + TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer); + } + + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDE, 3, longitude, + TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer); + } + + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSTIMESTAMP, 3, timestamp, + TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer); + } + + { + BAIL_IF_INVALID(writer->addEntry(TAG_GPSDATESTAMP, GPS_DATE_LENGTH, date, + TIFF_IFD_GPSINFO), env, TAG_GPSDATESTAMP, writer); + } +} + +static void DngCreator_nativeSetThumbnail(JNIEnv* env, jobject thiz, jobject buffer, jint width, + jint height) { + ALOGV("%s:", __FUNCTION__); + + NativeContext* context = DngCreator_getNativeContext(env, thiz); TiffWriter* writer = DngCreator_getCreator(env, thiz); - if (writer == NULL) { + if (writer == NULL || context == NULL) { ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); jniThrowException(env, "java/lang/AssertionError", - "Write called with uninitialized DngCreator"); + "setThumbnail called with uninitialized DngCreator"); return; } - // TODO: handle lens shading map, etc. conversions for other raw buffer sizes. - uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>()); - uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>()); - if (metadataWidth != width) { - jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ - "Metadata width %d doesn't match image width %d", metadataWidth, width); + + size_t fullSize = width * height * BYTES_PER_RGB_PIXEL; + jlong capacity = env->GetDirectBufferCapacity(buffer); + if (capacity != fullSize) { + jniThrowExceptionFmt(env, "java/lang/AssertionError", + "Invalid size %d for thumbnail, expected size was %d", + capacity, fullSize); return; } - if (metadataHeight != height) { - jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ - "Metadata height %d doesn't match image height %d", metadataHeight, height); + uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer)); + if (pixelBytes == NULL) { + ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer"); return; } - uint32_t stripOffset = writer->getTotalSize(); + if (!writer->hasIfd(TIFF_IFD_SUB1)) { + if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) { + ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1, + TIFF_IFD_0); + jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD"); + return; + } - BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env, - TAG_STRIPOFFSETS); + Vector<uint16_t> tagsToMove; + tagsToMove.add(TAG_ORIENTATION); + tagsToMove.add(TAG_NEWSUBFILETYPE); + tagsToMove.add(TAG_BITSPERSAMPLE); + tagsToMove.add(TAG_COMPRESSION); + tagsToMove.add(TAG_IMAGEWIDTH); + tagsToMove.add(TAG_IMAGELENGTH); + tagsToMove.add(TAG_PHOTOMETRICINTERPRETATION); + tagsToMove.add(TAG_BLACKLEVEL); + tagsToMove.add(TAG_BLACKLEVELREPEATDIM); + tagsToMove.add(TAG_SAMPLESPERPIXEL); + tagsToMove.add(TAG_PLANARCONFIGURATION); + tagsToMove.add(TAG_CFAREPEATPATTERNDIM); + tagsToMove.add(TAG_CFAPATTERN); + tagsToMove.add(TAG_CFAPLANECOLOR); + tagsToMove.add(TAG_CFALAYOUT); + tagsToMove.add(TAG_XRESOLUTION); + tagsToMove.add(TAG_YRESOLUTION); + tagsToMove.add(TAG_RESOLUTIONUNIT); + tagsToMove.add(TAG_WHITELEVEL); + tagsToMove.add(TAG_DEFAULTSCALE); + tagsToMove.add(TAG_ROWSPERSTRIP); + tagsToMove.add(TAG_STRIPBYTECOUNTS); + tagsToMove.add(TAG_STRIPOFFSETS); + tagsToMove.add(TAG_DEFAULTCROPORIGIN); + tagsToMove.add(TAG_DEFAULTCROPSIZE); + tagsToMove.add(TAG_OPCODELIST2); + + if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) { + jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries"); + return; + } - if (writer->write(out.get()) != OK) { - if (!env->ExceptionCheck()) { - jniThrowException(env, "java/io/IOException", "Failed to write metadata"); + // Make sure both IFDs get the same orientation tag + sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1); + if (orientEntry != NULL) { + writer->addEntry(orientEntry, TIFF_IFD_0); } + } + + // Setup thumbnail tags + + { + // Set photometric interpretation + uint16_t interpretation = 2; // RGB + BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation, + TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0), + env, TAG_PLANARCONFIGURATION, writer); + } + + { + // Set samples per pixel + uint16_t samples = SAMPLES_PER_RGB_PIXEL; + BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0), + env, TAG_SAMPLESPERPIXEL, writer); + } + + { + // Set bits per sample + uint16_t bits = BITS_PER_RGB_SAMPLE; + BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env, + TAG_BITSPERSAMPLE, writer); + } + + { + // Set subfiletype + uint32_t subfileType = 1; // Thumbnail image + BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env, + TAG_NEWSUBFILETYPE, writer); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env, + TAG_COMPRESSION, writer); + } + + { + // Set dimensions + uint32_t uWidth = static_cast<uint32_t>(width); + uint32_t uHeight = static_cast<uint32_t>(height); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0), env, + TAG_IMAGEWIDTH, writer); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0), env, + TAG_IMAGELENGTH, writer); + } + + { + // x resolution + uint32_t xres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0), + env, TAG_XRESOLUTION, writer); + + // y resolution + uint32_t yres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0), + env, TAG_YRESOLUTION, writer); + + uint16_t unit = 2; // inches + BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0), + env, TAG_RESOLUTIONUNIT, writer); + } + + { + // Setup data strips + if (writer->addStrip(TIFF_IFD_0) != OK) { + ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to setup thumbnail strip tags."); + return; + } + if (writer->addStrip(TIFF_IFD_SUB1) != OK) { + ALOGE("%s: Could not main image strip tags.", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to setup main image strip tags."); + return; + } + } + + if (!context->setThumbnail(pixelBytes, width, height)) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set thumbnail."); return; } +} - size_t fullSize = rowStride * height; - jlong capacity = env->GetDirectBufferCapacity(inBuffer); - if (capacity < 0 || fullSize > capacity) { - jniThrowExceptionFmt(env, "java/lang/IllegalStateException", - "Invalid size %d for Image, size given in metadata is %d at current stride", - capacity, fullSize); +// TODO: Refactor out common preamble for the two nativeWrite methods. +static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width, + jint height, jobject inBuffer, jint rowStride, jint pixStride, jlong offset, + jboolean isDirect) { + ALOGV("%s:", __FUNCTION__); + ALOGV("%s: nativeWriteImage called with: width=%d, height=%d, rowStride=%d, pixStride=%d," + " offset=%lld", __FUNCTION__, width, height, rowStride, pixStride, offset); + uint32_t rStride = static_cast<uint32_t>(rowStride); + uint32_t pStride = static_cast<uint32_t>(pixStride); + uint32_t uWidth = static_cast<uint32_t>(width); + uint32_t uHeight = static_cast<uint32_t>(height); + uint64_t uOffset = static_cast<uint64_t>(offset); + + sp<JniOutputStream> out = new JniOutputStream(env, outStream); + if(env->ExceptionCheck()) { + ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); return; } - if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) { - if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) { - if (!env->ExceptionCheck()) { - jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); - } + TiffWriter* writer = DngCreator_getCreator(env, thiz); + NativeContext* context = DngCreator_getNativeContext(env, thiz); + if (writer == NULL || context == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "Write called with uninitialized DngCreator"); + return; + } + + // Validate DNG header + if (!validateDngHeader(env, writer, width, height)) { + return; + } + + sp<JniInputByteBuffer> inBuf; + Vector<StripSource*> sources; + sp<DirectStripSource> thumbnailSource; + uint32_t targetIfd = TIFF_IFD_0; + + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + + if (hasThumbnail) { + ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__); + uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE; + uint32_t thumbWidth = context->getThumbnailWidth(); + thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0, + thumbWidth, context->getThumbnailHeight(), bytesPerPixel, + bytesPerPixel * thumbWidth, /*offset*/0, BYTES_PER_RGB_SAMPLE, + SAMPLES_PER_RGB_PIXEL); + sources.add(thumbnailSource.get()); + targetIfd = TIFF_IFD_SUB1; + } + + if (isDirect) { + size_t fullSize = rStride * uHeight; + jlong capacity = env->GetDirectBufferCapacity(inBuffer); + if (capacity < 0 || fullSize + uOffset > static_cast<uint64_t>(capacity)) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid size %d for Image, size given in metadata is %d at current stride", + capacity, fullSize); return; } - } else if (pixStride == BYTES_PER_SAMPLE) { - for (size_t i = 0; i < height; ++i) { - if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK || - env->ExceptionCheck()) { - if (!env->ExceptionCheck()) { - jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); - } - return; + + uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer)); + if (pixelBytes == NULL) { + ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer"); + return; + } + + ALOGV("%s: Using direct-type strip source.", __FUNCTION__); + DirectStripSource stripSource(env, pixelBytes, targetIfd, uWidth, uHeight, pStride, + rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.add(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); } + return; } } else { - for (size_t i = 0; i < height; ++i) { - for (size_t j = 0; j < width; ++j) { - if (out->write(pixelBytes, i * rowStride + j * pixStride, - BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) { - if (env->ExceptionCheck()) { - jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); - } - return; - } + inBuf = new JniInputByteBuffer(env, inBuffer); + + ALOGV("%s: Using input-type strip source.", __FUNCTION__); + InputStripSource stripSource(env, *inBuf, targetIfd, uWidth, uHeight, pStride, + rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.add(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); } + return; } } - -} - -static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream, - jobject rawBuffer, jlong offset) { - ALOGV("%s:", __FUNCTION__); - jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented."); } static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream, - jobject inStream, jlong offset) { + jobject inStream, jint width, jint height, jlong offset) { ALOGV("%s:", __FUNCTION__); - jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented."); + + uint32_t rowStride = width * BYTES_PER_SAMPLE; + uint32_t pixStride = BYTES_PER_SAMPLE; + uint32_t uWidth = static_cast<uint32_t>(width); + uint32_t uHeight = static_cast<uint32_t>(height); + uint64_t uOffset = static_cast<uint32_t>(offset); + + ALOGV("%s: nativeWriteInputStream called with: width=%d, height=%d, rowStride=%u," + "pixStride=%u, offset=%lld", __FUNCTION__, width, height, rowStride, pixStride, + offset); + + sp<JniOutputStream> out = new JniOutputStream(env, outStream); + if(env->ExceptionCheck()) { + ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + return; + } + + TiffWriter* writer = DngCreator_getCreator(env, thiz); + NativeContext* context = DngCreator_getNativeContext(env, thiz); + if (writer == NULL || context == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "Write called with uninitialized DngCreator"); + return; + } + + // Validate DNG header + if (!validateDngHeader(env, writer, width, height)) { + return; + } + + sp<DirectStripSource> thumbnailSource; + uint32_t targetIfd = TIFF_IFD_0; + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + Vector<StripSource*> sources; + + if (hasThumbnail) { + ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__); + uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE; + uint32_t width = context->getThumbnailWidth(); + thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0, + width, context->getThumbnailHeight(), bytesPerPixel, + bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE, + SAMPLES_PER_RGB_PIXEL); + sources.add(thumbnailSource.get()); + targetIfd = TIFF_IFD_SUB1; + } + + sp<JniInputStream> in = new JniInputStream(env, inStream); + + ALOGV("%s: Using input-type strip source.", __FUNCTION__); + InputStripSource stripSource(env, *in, targetIfd, uWidth, uHeight, pixStride, + rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.add(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); + } + return; + } } } /*extern "C" */ @@ -926,16 +1846,13 @@ static JNINativeMethod gDngCreatorMethods[] = { (void*) DngCreator_init}, {"nativeDestroy", "()V", (void*) DngCreator_destroy}, {"nativeSetOrientation", "(I)V", (void*) DngCreator_nativeSetOrientation}, - {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V", - (void*) DngCreator_nativeSetThumbnailBitmap}, - {"nativeSetThumbnailImage", - "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V", - (void*) DngCreator_nativeSetThumbnailImage}, - {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V", + {"nativeSetDescription", "(Ljava/lang/String;)V", (void*) DngCreator_nativeSetDescription}, + {"nativeSetGpsTags", "([ILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;[I)V", + (void*) DngCreator_nativeSetGpsTags}, + {"nativeSetThumbnail","(Ljava/nio/ByteBuffer;II)V", (void*) DngCreator_nativeSetThumbnail}, + {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;IIJZ)V", (void*) DngCreator_nativeWriteImage}, - {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V", - (void*) DngCreator_nativeWriteByteBuffer}, - {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V", + {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;IIJ)V", (void*) DngCreator_nativeWriteInputStream}, }; diff --git a/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp new file mode 100644 index 0000000..93473a5 --- /dev/null +++ b/core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp @@ -0,0 +1,335 @@ +/* + * 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. + */ + +#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI" +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/Trace.h> +#include <utils/Vector.h> + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + +#include <ui/GraphicBuffer.h> +#include <system/window.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +using namespace android; + +// fully-qualified class name +#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement" + +/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */ + +// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is +// terminated by either 0 or space, while pExtension is terminated by 0. + +static bool +extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) { + while (true) { + char a = *pExtensions++; + char b = *pExtension++; + bool aEnd = a == '\0' || a == ' '; + bool bEnd = b == '\0'; + if (aEnd || bEnd) { + return aEnd == bEnd; + } + if (a != b) { + return false; + } + } +} + +static const GLubyte* +nextExtension(const GLubyte* pExtensions) { + while (true) { + char a = *pExtensions++; + if (a == '\0') { + return pExtensions-1; + } else if ( a == ' ') { + return pExtensions; + } + } +} + +static bool +checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) { + for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) { + if (extensionEqual(pExtensions, pExtension)) { + return true; + } + } + return false; +} + +/** End copied GL utility methods */ + +bool checkGlError(JNIEnv* env) { + int error; + if ((error = glGetError()) != GL_NO_ERROR) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "GLES20 error: 0x%d", error); + return true; + } + return false; +} + +/** + * Asynchronous low-overhead GL performance measurement using + * http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt + * + * Measures the duration of GPU processing for a set of GL commands, delivering + * the measurement asynchronously once processing completes. + * + * All calls must come from a single thread with a valid GL context active. + **/ +class PerfMeasurementContext { + private: + Vector<GLuint> mTimingQueries; + size_t mTimingStartIndex; + size_t mTimingEndIndex; + size_t mTimingQueryIndex; + size_t mFreeQueries; + + bool mInitDone; + public: + + /** + * maxQueryCount should be a conservative estimate of how many query objects + * will be active at once, which is a function of the GPU's level of + * pipelining and the frequency of queries. + */ + PerfMeasurementContext(size_t maxQueryCount): + mTimingStartIndex(0), + mTimingEndIndex(0), + mTimingQueryIndex(0) { + mTimingQueries.resize(maxQueryCount); + mFreeQueries = maxQueryCount; + mInitDone = false; + } + + int getMaxQueryCount() { + return mTimingQueries.size(); + } + + /** + * Start a measurement period using the next available query object. + * Returns INVALID_OPERATION if called multiple times in a row, + * and BAD_VALUE if no more query objects are available. + */ + int startGlTimer() { + // Lazy init of queries to avoid needing GL context during construction + if (!mInitDone) { + glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray()); + mInitDone = true; + } + + if (mTimingEndIndex != mTimingStartIndex) { + return INVALID_OPERATION; + } + + if (mFreeQueries == 0) { + return BAD_VALUE; + } + + glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]); + + mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size(); + mFreeQueries--; + + return OK; + } + + /** + * Finish the current measurement period + * Returns INVALID_OPERATION if called before any startGLTimer calls + * or if called multiple times in a row. + */ + int stopGlTimer() { + size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size(); + if (nextEndIndex != mTimingStartIndex) { + return INVALID_OPERATION; + } + glEndQueryEXT(GL_TIME_ELAPSED_EXT); + + mTimingEndIndex = nextEndIndex; + + return OK; + } + + static const nsecs_t NO_DURATION_YET = -1L; + static const nsecs_t FAILED_MEASUREMENT = -2L; + + /** + * Get the next available duration measurement. + * + * Returns NO_DURATION_YET if no new measurement is available, + * and FAILED_MEASUREMENT if an error occurred during the next + * measurement period. + * + * Otherwise returns a positive number of nanoseconds measuring the + * duration of the oldest completed query. + */ + nsecs_t getNextGlDuration() { + if (!mInitDone) { + // No start/stop called yet + return NO_DURATION_YET; + } + + GLint available; + glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex], + GL_QUERY_RESULT_AVAILABLE_EXT, &available); + if (!available) { + return NO_DURATION_YET; + } + + GLint64 duration = FAILED_MEASUREMENT; + GLint disjointOccurred; + glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred); + + if (!disjointOccurred) { + glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex], + GL_QUERY_RESULT_EXT, + &duration); + } + + mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size(); + mFreeQueries++; + + return static_cast<nsecs_t>(duration); + } + + static bool isMeasurementSupported() { + const GLubyte* extensions = glGetString(GL_EXTENSIONS); + return checkForExtension(extensions, + reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query")); + } + +}; + +PerfMeasurementContext* getContext(jlong context) { + return reinterpret_cast<PerfMeasurementContext*>(context); +} + +extern "C" { + +static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz, + jint maxQueryCount) { + PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount); + return reinterpret_cast<jlong>(context); +} + +static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz, + jlong contextHandle) { + PerfMeasurementContext *context = getContext(contextHandle); + delete(context); +} + +static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) { + bool supported = PerfMeasurementContext::isMeasurementSupported(); + checkGlError(env); + return static_cast<jboolean>(supported); +} + +static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz, + jlong contextHandle) { + + PerfMeasurementContext *context = getContext(contextHandle); + status_t err = context->startGlTimer(); + if (err != OK) { + switch (err) { + case INVALID_OPERATION: + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Mismatched start/end GL timing calls"); + return; + case BAD_VALUE: + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Too many timing queries in progress, max %d", + context->getMaxQueryCount()); + return; + default: + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Unknown error starting GL timing"); + return; + } + } + checkGlError(env); +} + +static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz, + jlong contextHandle) { + + PerfMeasurementContext *context = getContext(contextHandle); + status_t err = context->stopGlTimer(); + if (err != OK) { + switch (err) { + case INVALID_OPERATION: + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Mismatched start/end GL timing calls"); + return; + default: + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Unknown error ending GL timing"); + return; + } + } + checkGlError(env); +} + +static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env, + jobject thiz, jlong contextHandle) { + PerfMeasurementContext *context = getContext(contextHandle); + nsecs_t duration = context->getNextGlDuration(); + + checkGlError(env); + return static_cast<jlong>(duration); +} + +} // extern "C" + +static JNINativeMethod gPerfMeasurementMethods[] = { + { "nativeCreateContext", + "(I)J", + (jlong *)PerfMeasurement_nativeCreateContext }, + { "nativeDeleteContext", + "(J)V", + (void *)PerfMeasurement_nativeDeleteContext }, + { "nativeQuerySupport", + "()Z", + (jboolean *)PerfMeasurement_nativeQuerySupport }, + { "nativeStartGlTimer", + "(J)V", + (void *)PerfMeasurement_nativeStartGlTimer }, + { "nativeStopGlTimer", + "(J)V", + (void *)PerfMeasurement_nativeStopGlTimer }, + { "nativeGetNextGlDuration", + "(J)J", + (jlong *)PerfMeasurement_nativeGetNextGlDuration } +}; + + +// Get all the required offsets in java class and register native functions +int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env) +{ + // Register native functions + return AndroidRuntime::registerNativeMethods(env, + PERF_MEASUREMENT_CLASS_NAME, + gPerfMeasurementMethods, + NELEM(gPerfMeasurementMethods)); +} diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 6f89800..a75d547 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -285,6 +285,11 @@ static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, return (jboolean) !setNetworkForSocket(netId, socket); } +static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) +{ + return (jboolean) !protectFromVpn(socket); +} + // ---------------------------------------------------------------------------- /* @@ -308,6 +313,7 @@ static JNINativeMethod gNetworkUtilMethods[] = { { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution }, { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork }, + { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index a6b65cc..aaa680f 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -24,6 +24,7 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <utils/Vector.h> +#include <processgroup/processgroup.h> #include <android_runtime/AndroidRuntime.h> @@ -1002,6 +1003,16 @@ jintArray android_os_Process_getPidsForCommands(JNIEnv* env, jobject clazz, return pidArray; } +jint android_os_Process_killProcessGroup(JNIEnv* env, jobject clazz, jint uid, jint pid) +{ + return killProcessGroup(uid, pid, SIGKILL); +} + +void android_os_Process_removeAllProcessGroups(JNIEnv* env, jobject clazz) +{ + return removeAllProcessGroups(); +} + static const JNINativeMethod methods[] = { {"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName}, {"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName}, @@ -1029,6 +1040,8 @@ static const JNINativeMethod methods[] = { {"getPss", "(I)J", (void*)android_os_Process_getPss}, {"getPidsForCommands", "([Ljava/lang/String;)[I", (void*)android_os_Process_getPidsForCommands}, //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject}, + {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup}, + {"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups}, }; const char* const kProcessPathName = "android/os/Process"; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 0cdddba..989b60e 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -41,6 +41,7 @@ #include <cutils/sched_policy.h> #include <utils/String8.h> #include <selinux/android.h> +#include <processgroup/processgroup.h> #include "android_runtime/AndroidRuntime.h" #include "JNIHelp.h" @@ -435,6 +436,14 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra } } + if (!is_system_server) { + int rc = createProcessGroup(uid, getpid()); + if (rc != 0) { + ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc)); + RuntimeAbort(env); + } + } + SetGids(env, javaGids); SetRLimits(env, javaRlimits); diff --git a/core/res/res/drawable/popup_background_material.xml b/core/res/res/drawable/popup_background_material.xml index 9e50790..b1f0cf5 100644 --- a/core/res/res/drawable/popup_background_material.xml +++ b/core/res/res/drawable/popup_background_material.xml @@ -14,7 +14,12 @@ limitations under the License. --> -<nine-patch xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/popup_background_mtrl_mult" - android:tint="?attr/colorBackground" - android:tintMode="multiply" /> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:radius="2dp" /> + <solid + android:color="?attr/colorBackground" /> + +</shape> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index ea9e189..9438fcc 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -730,6 +730,9 @@ <attr name="actionBarTabTextStyle" format="reference" /> <attr name="actionOverflowButtonStyle" format="reference" /> <attr name="actionOverflowMenuStyle" format="reference" /> + <!-- Reference to a theme that should be used to inflate popups + shown by widgets in the action bar. --> + <attr name="actionBarPopupTheme" format="reference" /> <!-- Reference to a style for the Action Bar --> <attr name="actionBarStyle" format="reference" /> <!-- Reference to a style for the split Action Bar. This style @@ -2517,6 +2520,9 @@ <enum name="blocksDescendants" value="2" /> </attr> + <!-- Set to true if this ViewGroup blocks focus in the presence of a touchscreen. --> + <attr name="touchscreenBlocksFocus" format="boolean" /> + <!-- Sets whether this ViewGroup should split MotionEvents to separate child views during touch event dispatch. If false (default), touch events will be dispatched to @@ -4057,6 +4063,8 @@ <declare-styleable name="PopupWindow"> <!-- The background to use for the popup window. --> <attr name="popupBackground" format="reference|color" /> + <!-- Window elevation to use for the popup window. --> + <attr name="popupElevation" format="dimension" /> <!-- The animation style to use for the popup window. --> <attr name="popupAnimationStyle" format="reference" /> <!-- Whether the popup window should overlap its anchor view. --> @@ -6778,10 +6786,20 @@ <attr name="itemPadding" format="dimension" /> <!-- Set true to hide the action bar on a vertical nested scroll of content. --> <attr name="hideOnContentScroll" format="boolean" /> + <!-- Minimum inset for content views within a bar. Navigation buttons and + menu views are excepted. Only valid for some themes and configurations. --> <attr name="contentInsetStart" format="dimension" /> + <!-- Minimum inset for content views within a bar. Navigation buttons and + menu views are excepted. Only valid for some themes and configurations. --> <attr name="contentInsetEnd" format="dimension" /> + <!-- Minimum inset for content views within a bar. Navigation buttons and + menu views are excepted. Only valid for some themes and configurations. --> <attr name="contentInsetLeft" format="dimension" /> + <!-- Minimum inset for content views within a bar. Navigation buttons and + menu views are excepted. Only valid for some themes and configurations. --> <attr name="contentInsetRight" format="dimension" /> + <!-- Elevation for the action bar itself --> + <attr name="elevation" /> </declare-styleable> <declare-styleable name="ActionMode"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 9789eee..226e30a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2025,10 +2025,11 @@ <!-- Groups signing keys into a {@code KeySet} for easier reference in other APIs. However, currently no APIs use this. --> <attr name="keySet" /> - <declare-styleable name="PublicKey"> + <declare-styleable name="AndroidManifestPublicKey"> + <attr name="name" /> <attr name="value" /> </declare-styleable> - <declare-styleable name="KeySet"> + <declare-styleable name="AndroidManifestKeySet"> <attr name="name" /> </declare-styleable> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 1eb8946..a0ca06e 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2231,6 +2231,8 @@ <public type="attr" name="buttonBarPositiveButtonStyle" /> <public type="attr" name="buttonBarNeutralButtonStyle" /> <public type="attr" name="buttonBarNegativeButtonStyle" /> + <public type="attr" name="popupElevation" /> + <public type="attr" name="actionBarPopupTheme" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2508,4 +2510,5 @@ a view visibility changes. --> <public type="transition" name="slide_left"/> <public type="attr" name="multiArch" /> + <public type="attr" name="touchscreenBlocksFocus" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e8bd99a..7c60c6e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2152,8 +2152,8 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string> - <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string> - <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string> + <string name="permlab_handoverStatus">Receive Android Beam transfer status</string> + <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string> <!-- Policy administration --> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index c1eb999..4623258 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -733,6 +733,7 @@ please see styles_device_defaults.xml. <style name="Widget.Material.ListPopupWindow" parent="Widget.ListPopupWindow"> <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_material</item> + <item name="popupElevation">@dimen/floating_window_z</item> <item name="popupAnimationStyle">@style/Animation.Material.Popup</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> @@ -803,6 +804,7 @@ please see styles_device_defaults.xml. <item name="homeLayout">@layout/action_bar_home_material</item> <item name="gravity">center_vertical</item> <item name="contentInsetStart">16dp</item> + <item name="elevation">8dp</item> </style> <style name="Widget.Material.ActionBar.Solid"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2065088..cf0f7b7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1904,4 +1904,5 @@ <java-symbol type="bool" name="skipHoldBeforeMerge" /> <java-symbol type="bool" name="useImsAlwaysForEmergencyCall" /> + <java-symbol type="attr" name="touchscreenBlocksFocus" /> </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index b63a8c1..d61253f 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -362,6 +362,7 @@ please see themes_device_defaults.xml. <item name="actionMenuTextAppearance">@style/TextAppearance.Holo.Widget.ActionBar.Menu</item> <item name="actionMenuTextColor">?attr/textColorPrimary</item> <item name="actionBarWidgetTheme">@null</item> + <item name="actionBarPopupTheme">@null</item> <item name="actionBarTheme">@null</item> <item name="actionBarDivider">?attr/dividerVertical</item> <item name="actionBarItemBackground">?attr/selectableItemBackground</item> diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml index 78405e3..a9150c6 100644 --- a/core/res/res/values/themes_holo.xml +++ b/core/res/res/values/themes_holo.xml @@ -341,6 +341,7 @@ please see themes_device_defaults.xml. <item name="actionBarSize">@dimen/action_bar_default_height</item> <item name="actionModePopupWindowStyle">@style/Widget.Holo.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@null</item> + <item name="actionBarPopupTheme">@null</item> <item name="actionBarTheme">@null</item> <item name="actionModeCutDrawable">@drawable/ic_menu_cut_holo_dark</item> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 472177f..47f3778 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -315,6 +315,7 @@ please see themes_device_defaults.xml. <item name="actionBarSize">@dimen/action_bar_default_height_material</item> <item name="actionModePopupWindowStyle">@style/Widget.Material.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@null</item> + <item name="actionBarPopupTheme">@null</item> <item name="actionBarTheme">@style/ThemeOverlay.Material.ActionBar</item> <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item> @@ -498,7 +499,7 @@ please see themes_device_defaults.xml. <item name="windowFullscreen">false</item> <item name="windowOverscan">false</item> <item name="windowIsFloating">false</item> - <item name="windowContentOverlay">@drawable/ab_solid_shadow_material</item> + <item name="windowContentOverlay">@null</item> <item name="windowShowWallpaper">false</item> <item name="windowTitleStyle">@style/WindowTitle.Material</item> <item name="windowTitleSize">@dimen/action_bar_default_height_material</item> @@ -653,6 +654,7 @@ please see themes_device_defaults.xml. <item name="actionButtonStyle">@style/Widget.Material.Light.ActionButton</item> <item name="actionOverflowButtonStyle">@style/Widget.Material.Light.ActionButton.Overflow</item> <item name="actionOverflowMenuStyle">@style/Widget.Material.Light.PopupMenu.Overflow</item> + <item name="actionBarPopupTheme">@null</item> <item name="actionModeBackground">@drawable/cab_background_top_holo_light</item> <item name="actionModeSplitBackground">@drawable/cab_background_bottom_holo_light</item> <item name="actionModeCloseDrawable">@drawable/ic_cab_done_material</item> @@ -737,6 +739,7 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Light.DarkActionBar"> <item name="actionBarWidgetTheme">@null</item> <item name="actionBarTheme">@style/ThemeOverlay.Material.Dark.ActionBar</item> + <item name="actionBarPopupTheme">@style/ThemeOverlay.Material.Light</item> <item name="colorPrimaryDark">@color/material_blue_grey_900</item> <item name="colorPrimary">@color/material_blue_grey_800</item> @@ -778,6 +781,7 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item> + <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlHighlight">@color/ripple_material_light</item> <item name="colorButtonNormal">@color/btn_default_material_light</item> </style> @@ -814,6 +818,7 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item> + <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlHighlight">@color/ripple_material_dark</item> <item name="colorButtonNormal">@color/btn_default_material_dark</item> </style> diff --git a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml b/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml index 41a2974..b7645b0 100644 --- a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml +++ b/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml @@ -18,14 +18,16 @@ <application android:hasCode="false"> </application> <uses-permission android:name="com.android.frameworks.coretests.keysets_permdef.keyset_perm" /> - <keys> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="> - <keyset android:name="A" /> - </publicKey> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ=="> - <keyset android:name="B" /> - </publicKey> - </keys> - <upgrade-keyset android:name="A"/> - <upgrade-keyset android:name="B"/> + <key-sets> + <key-set android:name="A"> + <public-key android:name="keyA" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/> + </key-set> + <key-set android:name="B"> + <public-key android:name="keyB" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ==" /> + </key-set> + <upgrade-key-set android:name="A"/> + <upgrade-key-set android:name="B"/> + </key-sets> </manifest> diff --git a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml b/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml index 87c420e..f31b75f 100644 --- a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml +++ b/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml @@ -17,10 +17,11 @@ package="com.android.frameworks.coretests.keysets"> <application android:hasCode="false"> </application> - <keys> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="> - <keyset android:name="A" /> - </publicKey> - </keys> - <upgrade-keyset android:name="A"/> + <key-sets> + <key-set android:name="A" > + <public-key android:name="keyA" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/> + </key-set> + <upgrade-key-set android:name="A"/> + </key-sets> </manifest> diff --git a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml b/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml index a65f085..8ad3471 100644 --- a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml +++ b/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml @@ -17,13 +17,13 @@ package="com.android.frameworks.coretests.keysets"> <application android:hasCode="false"> </application> - <keys> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="> - <keyset android:name="AB" /> - </publicKey> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ=="> - <keyset android:name="AB" /> - </publicKey> - </keys> - <upgrade-keyset android:name="AB"/> + <key-sets> + <key-set android:name="AB" > + <public-key android:name="keyA" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ==" /> + <public-key android:name="keyB" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ==" /> + </key-set> + <upgrade-key-set android:name="AB"/> + </key-sets> </manifest> diff --git a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml b/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml index 5b0b864..cdbd639 100644 --- a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml +++ b/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml @@ -17,14 +17,16 @@ package="com.android.frameworks.coretests.keysets"> <application android:hasCode="false"> </application> - <keys> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="> - <keyset android:name="A" /> - </publicKey> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ=="> - <keyset android:name="B" /> - </publicKey> - </keys> - <upgrade-keyset android:name="A"/> - <upgrade-keyset android:name="B"/> + <key-sets> + <key-set android:name="A" > + <public-key android:name="keyA" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ==" /> + </key-set> + <key-set android:name="B" > + <public-key android:name="keyB" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ==" /> + </key-set> + <upgrade-key-set android:name="A"/> + <upgrade-key-set android:name="B"/> + </key-sets> </manifest> diff --git a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml b/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml index 9b89961..61063c3 100644 --- a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml +++ b/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml @@ -17,10 +17,11 @@ package="com.android.frameworks.coretests.keysets"> <application android:hasCode="false"> </application> - <keys> - <publicKey android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ=="> - <keyset android:name="B" /> - </publicKey> - </keys> - <upgrade-keyset android:name="B"/> + <key-sets> + <key-set android:name="B" > + <public-key android:name="keyB" + android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMTfQsY8UuXiXmvw/y7Tpr7HoyfAC0nE/8Qdk3ZtEr9asa5qqP0F6xzCI1PGVFV+WLVRwm6FdB9StENL5EKyQFcCAwEAAQ==" /> + </key-set> + <upgrade-key-set android:name="B"/> + </key-sets> </manifest> diff --git a/core/tests/inputmethodtests/run_core_inputmethod_test.sh b/core/tests/inputmethodtests/run_core_inputmethod_test.sh index e0f4f6d..ed8b7f7 100755 --- a/core/tests/inputmethodtests/run_core_inputmethod_test.sh +++ b/core/tests/inputmethodtests/run_core_inputmethod_test.sh @@ -21,4 +21,4 @@ if [[ $rebuild == true ]]; then $COMMAND fi -adb shell am instrument -w -e class android.os.InputMethodTest,android.os.InputMethodSubtypeArrayTest,android.os.InputMethodSubtypeSwitchingControllerTest,android.os.CursorAnchorInfoTest,android.os.SparseRectFArrayTest com.android.frameworks.coretests.inputmethod/android.test.InstrumentationTestRunner +adb shell am instrument -w -e class android.os.InputMethodTest,android.os.InputMethodSubtypeTest,android.os.InputMethodSubtypeArrayTest,android.os.InputMethodSubtypeSwitchingControllerTest,android.os.CursorAnchorInfoTest,android.os.SparseRectFArrayTest com.android.frameworks.coretests.inputmethod/android.test.InstrumentationTestRunner diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java new file mode 100644 index 0000000..323a360 --- /dev/null +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.inputmethod.InputMethodSubtype; +import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; + +import java.util.Objects; + +public class InputMethodSubtypeTest extends InstrumentationTestCase { + + public void verifyLocale(final String localeString) { + // InputMethodSubtype#getLocale() returns exactly the same string that is passed to the + // constructor. + assertEquals(localeString, createDummySubtype(localeString).getLocale()); + + // InputMethodSubtype#getLocale() should be preserved via marshaling. + assertEquals(createDummySubtype(localeString).getLocale(), + cloneViaParcel(createDummySubtype(localeString)).getLocale()); + + // InputMethodSubtype#getLocale() should be preserved via marshaling. + assertEquals(createDummySubtype(localeString).getLocale(), + cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).getLocale()); + + // Make sure InputMethodSubtype#hashCode() returns the same hash code. + assertEquals(createDummySubtype(localeString).hashCode(), + createDummySubtype(localeString).hashCode()); + assertEquals(createDummySubtype(localeString).hashCode(), + cloneViaParcel(createDummySubtype(localeString)).hashCode()); + assertEquals(createDummySubtype(localeString).hashCode(), + cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).hashCode()); + } + + @SmallTest + public void testLocaleString() throws Exception { + // The locale string in InputMethodSubtype has accepted an arbitrary text actually, + // regardless of the validity of the text as a locale string. + verifyLocale("en_US"); + verifyLocale("apparently invalid locale string"); + verifyLocale("zz"); + verifyLocale("iw"); + verifyLocale("he"); + } + + @SmallTest + public void testDeprecatedLocaleString() throws Exception { + // Make sure "iw" is not automatically replaced with "he". + final InputMethodSubtype subtypeIw = createDummySubtype("iw"); + final InputMethodSubtype subtypeHe = createDummySubtype("he"); + assertEquals("iw", subtypeIw.getLocale()); + assertEquals("he", subtypeHe.getLocale()); + assertFalse(Objects.equals(subtypeIw, subtypeHe)); + assertFalse(Objects.equals(subtypeIw.hashCode(), subtypeHe.hashCode())); + + final InputMethodSubtype clonedSubtypeIw = cloneViaParcel(subtypeIw); + final InputMethodSubtype clonedSubtypeHe = cloneViaParcel(subtypeHe); + assertEquals(subtypeIw, clonedSubtypeIw); + assertEquals(subtypeHe, clonedSubtypeHe); + assertEquals("iw", clonedSubtypeIw.getLocale()); + assertEquals("he", clonedSubtypeHe.getLocale()); + } + + private static final InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return InputMethodSubtype.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } + + private static final InputMethodSubtype createDummySubtype(final String locale) { + final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); + return builder.setSubtypeNameResId(0) + .setSubtypeIconResId(0) + .setSubtypeLocale(locale) + .setIsAsciiCapable(true) + .build(); + } +} diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml index a7e159a..0f0281b 100644 --- a/data/fonts/fallback_fonts.xml +++ b/data/fonts/fallback_fonts.xml @@ -234,12 +234,12 @@ </family> <family> <fileset> - <file lang="zh-CN">NotoSansHans-Regular.otf</file> + <file lang="zh-Hans">NotoSansHans-Regular.otf</file> </fileset> </family> <family> <fileset> - <file lang="zh-TW">NotoSansHant-Regular.otf</file> + <file lang="zh-Hant">NotoSansHant-Regular.otf</file> </fileset> </family> <family> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b050a02..e7a073c 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1019,9 +1019,6 @@ public abstract class Drawable { drawable = new StateListDrawable(); } else if (name.equals("animated-selector")) { drawable = new AnimatedStateListDrawable(); - } else if (name.equals("material-progress")) { - // TODO: Replace this with something less ridiculous. - drawable = new MaterialProgressDrawable(); } else if (name.equals("level-list")) { drawable = new LevelListDrawable(); } else if (name.equals("layer-list")) { diff --git a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java b/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java deleted file mode 100644 index c484094..0000000 --- a/graphics/java/android/graphics/drawable/MaterialProgressDrawable.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.graphics.drawable; - -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.Resources.Theme; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Paint.Cap; -import android.graphics.Paint.Style; -import android.graphics.PorterDuff.Mode; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.util.ArrayList; - -/** - * Fancy progress indicator for Material theme. - * - * TODO: Replace this class with something less ridiculous. - */ -class MaterialProgressDrawable extends Drawable implements Animatable { - private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); - private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); - private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); - - /** The duration of a single progress spin in milliseconds. */ - private static final int ANIMATION_DURATION = 1000 * 80 / 60; - - /** The number of points in the progress "star". */ - private static final int NUM_POINTS = 5; - - /** The list of animators operating on this drawable. */ - private final ArrayList<Animator> mAnimators = new ArrayList<Animator>(); - - /** The indicator ring, used to manage animation state. */ - private final Ring mRing; - - private MaterialProgressState mState; - private PorterDuffColorFilter mTintFilter; - - /** Canvas rotation in degrees. */ - private float mRotation; - - private boolean mMutated; - - public MaterialProgressDrawable() { - this(new MaterialProgressState(null), null); - } - - private MaterialProgressDrawable(MaterialProgressState state, Theme theme) { - mState = state; - if (theme != null && state.canApplyTheme()) { - applyTheme(theme); - } - - mRing = new Ring(mCallback); - mMutated = false; - - initializeFromState(); - setupAnimators(); - } - - private void initializeFromState() { - final MaterialProgressState state = mState; - - final Ring ring = mRing; - ring.setStrokeWidth(state.mStrokeWidth); - - final int color = state.mColor.getColorForState(getState(), Color.TRANSPARENT); - ring.setColor(color); - - final float minEdge = Math.min(state.mWidth, state.mHeight); - if (state.mInnerRadius <= 0 || minEdge < 0) { - ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0f)); - } else { - float insets = minEdge / 2.0f - state.mInnerRadius; - ring.setInsets(insets); - } - - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); - } - - @Override - public Drawable mutate() { - if (!mMutated && super.mutate() == this) { - mState = new MaterialProgressState(mState); - mMutated = true; - } - return this; - } - - @Override - protected boolean onStateChange(int[] stateSet) { - boolean changed = super.onStateChange(stateSet); - - final MaterialProgressState state = mState; - final int color = state.mColor.getColorForState(stateSet, Color.TRANSPARENT); - if (color != mRing.getColor()) { - mRing.setColor(color); - changed = true; - } - - if (state.mTint != null && state.mTintMode != null) { - mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); - changed = true; - } - - return changed; - } - - @Override - public boolean isStateful() { - return super.isStateful() || mState.mColor.isStateful(); - } - - @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) - throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.MaterialProgressDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.MaterialProgressDrawable_visible); - updateStateFromTypedArray(a); - a.recycle(); - - initializeFromState(); - } - - @Override - public void applyTheme(Theme t) { - final TypedArray a = t.resolveAttributes(mState.mThemeAttrs, - R.styleable.MaterialProgressDrawable); - updateStateFromTypedArray(a); - a.recycle(); - } - - private void updateStateFromTypedArray(TypedArray a) { - final MaterialProgressState state = mState; - state.mThemeAttrs = a.extractThemeAttrs(); - state.mWidth = a.getDimensionPixelSize( - R.styleable.MaterialProgressDrawable_width, state.mWidth); - state.mHeight = a.getDimensionPixelSize( - R.styleable.MaterialProgressDrawable_height, state.mHeight); - state.mInnerRadius = a.getDimension( - R.styleable.MaterialProgressDrawable_innerRadius, state.mInnerRadius); - state.mStrokeWidth = a.getDimension( - R.styleable.MaterialProgressDrawable_thickness, state.mStrokeWidth); - - if (a.hasValue(R.styleable.MaterialProgressDrawable_color)) { - state.mColor = a.getColorStateList(R.styleable.MaterialProgressDrawable_color); - } - } - - @Override - public boolean setVisible(boolean visible, boolean restart) { - boolean changed = super.setVisible(visible, restart); - if (visible) { - if (changed || restart) { - start(); - } - } else { - stop(); - } - return changed; - } - - @Override - public int getIntrinsicHeight() { - return mState.mHeight; - } - - @Override - public int getIntrinsicWidth() { - return mState.mWidth; - } - - @Override - public void draw(Canvas c) { - final Rect bounds = getBounds(); - final int saveCount = c.save(); - c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); - mRing.draw(c, bounds); - c.restoreToCount(saveCount); - } - - @Override - public void setAlpha(int alpha) { - mRing.setAlpha(alpha); - } - - @Override - public int getAlpha() { - return mRing.getAlpha(); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - mRing.setColorFilter(colorFilter); - } - - @Override - public ColorFilter getColorFilter() { - return mRing.getColorFilter(); - } - - @Override - public void setTint(ColorStateList tint, Mode tintMode) { - if (mState.mTint != tint || mState.mTintMode != tintMode) { - mState.mTint = tint; - mState.mTintMode = tintMode; - - mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); - invalidateSelf(); - } - } - - @SuppressWarnings("unused") - private void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - private float getRotation() { - return mRotation; - } - - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } - - @Override - public boolean isRunning() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - if (animator.isRunning()) { - return true; - } - } - return false; - } - - @Override - public void start() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - if (animator.isPaused()) { - animator.resume(); - } else if (!animator.isRunning()){ - animator.start(); - } - } - } - - @Override - public void stop() { - final ArrayList<Animator> animators = mAnimators; - final int N = animators.size(); - for (int i = 0; i < N; i++) { - final Animator animator = animators.get(i); - animator.pause(); - } - } - - private void setupAnimators() { - final Ring ring = mRing; - - final ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", 0, 0.75f); - endTrim.setDuration(ANIMATION_DURATION); - endTrim.setInterpolator(START_CURVE_INTERPOLATOR); - endTrim.setRepeatCount(ObjectAnimator.INFINITE); - endTrim.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", 0.0f, 0.75f); - startTrim.setDuration(ANIMATION_DURATION); - startTrim.setInterpolator(END_CURVE_INTERPOLATOR); - startTrim.setRepeatCount(ObjectAnimator.INFINITE); - startTrim.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", 0.0f, 0.25f); - rotation.setDuration(ANIMATION_DURATION); - rotation.setInterpolator(LINEAR_INTERPOLATOR); - rotation.setRepeatCount(ObjectAnimator.INFINITE); - rotation.setRepeatMode(ObjectAnimator.RESTART); - - final ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", 0.0f, 360.0f); - groupRotation.setDuration(NUM_POINTS * ANIMATION_DURATION); - groupRotation.setInterpolator(LINEAR_INTERPOLATOR); - groupRotation.setRepeatCount(ObjectAnimator.INFINITE); - groupRotation.setRepeatMode(ObjectAnimator.RESTART); - - mAnimators.add(endTrim); - mAnimators.add(startTrim); - mAnimators.add(rotation); - mAnimators.add(groupRotation); - } - - private final Callback mCallback = new Callback() { - @Override - public void invalidateDrawable(Drawable d) { - invalidateSelf(); - } - - @Override - public void scheduleDrawable(Drawable d, Runnable what, long when) { - scheduleSelf(what, when); - } - - @Override - public void unscheduleDrawable(Drawable d, Runnable what) { - unscheduleSelf(what); - } - }; - - private static class MaterialProgressState extends ConstantState { - private int[] mThemeAttrs = null; - private float mStrokeWidth = 5.0f; - private float mInnerRadius = -1.0f; - private int mWidth = -1; - private int mHeight = -1; - private ColorStateList mColor = ColorStateList.valueOf(Color.TRANSPARENT); - private ColorStateList mTint = null; - private Mode mTintMode = null; - - public MaterialProgressState(MaterialProgressState orig) { - if (orig != null) { - mThemeAttrs = orig.mThemeAttrs; - mStrokeWidth = orig.mStrokeWidth; - mInnerRadius = orig.mInnerRadius; - mWidth = orig.mWidth; - mHeight = orig.mHeight; - mColor = orig.mColor; - mTint = orig.mTint; - mTintMode = orig.mTintMode; - } - } - - @Override - public boolean canApplyTheme() { - return mThemeAttrs != null; - } - - @Override - public Drawable newDrawable() { - return newDrawable(null, null); - } - - @Override - public Drawable newDrawable(Resources res) { - return newDrawable(res, null); - } - - @Override - public Drawable newDrawable(Resources res, Theme theme) { - return new MaterialProgressDrawable(this, theme); - } - - @Override - public int getChangingConfigurations() { - return 0; - } - } - - private static class Ring { - private final RectF mTempBounds = new RectF(); - private final Paint mPaint = new Paint(); - - private final Callback mCallback; - - private float mStartTrim = 0.0f; - private float mEndTrim = 0.0f; - private float mRotation = 0.0f; - private float mStrokeWidth = 5.0f; - private float mStrokeInset = 2.5f; - - private int mAlpha = 0xFF; - private int mColor = Color.BLACK; - - public Ring(Callback callback) { - mCallback = callback; - - mPaint.setStrokeCap(Cap.ROUND); - mPaint.setAntiAlias(true); - mPaint.setStyle(Style.STROKE); - } - - public void draw(Canvas c, Rect bounds) { - final RectF arcBounds = mTempBounds; - arcBounds.set(bounds); - arcBounds.inset(mStrokeInset, mStrokeInset); - - final float startAngle = (mStartTrim + mRotation) * 360; - final float endAngle = (mEndTrim + mRotation) * 360; - float sweepAngle = endAngle - startAngle; - - // Ensure the sweep angle isn't too small to draw. - final float diameter = Math.min(arcBounds.width(), arcBounds.height()); - final float minAngle = (float) (360.0 / (diameter * Math.PI)); - if (sweepAngle < minAngle && sweepAngle > -minAngle) { - sweepAngle = Math.signum(sweepAngle) * minAngle; - } - - c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); - } - - public void setColorFilter(ColorFilter filter) { - mPaint.setColorFilter(filter); - invalidateSelf(); - } - - public ColorFilter getColorFilter() { - return mPaint.getColorFilter(); - } - - public void setAlpha(int alpha) { - mAlpha = alpha; - mPaint.setColor(mColor & 0xFFFFFF | alpha << 24); - invalidateSelf(); - } - - public int getAlpha() { - return mAlpha; - } - - public void setColor(int color) { - mColor = color; - mPaint.setColor(color & 0xFFFFFF | mAlpha << 24); - invalidateSelf(); - } - - public int getColor() { - return mColor; - } - - public void setStrokeWidth(float strokeWidth) { - mStrokeWidth = strokeWidth; - mPaint.setStrokeWidth(strokeWidth); - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStrokeWidth() { - return mStrokeWidth; - } - - @SuppressWarnings("unused") - public void setStartTrim(float startTrim) { - mStartTrim = startTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getStartTrim() { - return mStartTrim; - } - - @SuppressWarnings("unused") - public void setEndTrim(float endTrim) { - mEndTrim = endTrim; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getEndTrim() { - return mEndTrim; - } - - @SuppressWarnings("unused") - public void setRotation(float rotation) { - mRotation = rotation; - invalidateSelf(); - } - - @SuppressWarnings("unused") - public float getRotation() { - return mRotation; - } - - public void setInsets(float insets) { - mStrokeInset = insets; - } - - @SuppressWarnings("unused") - public float getInsets() { - return mStrokeInset; - } - - private void invalidateSelf() { - mCallback.invalidateDrawable(null); - } - } - - /** - * Squishes the interpolation curve into the second half of the animation. - */ - private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator { - @Override - public float getInterpolation(float input) { - return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f)); - } - } - - /** - * Squishes the interpolation curve into the first half of the animation. - */ - private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator { - @Override - public float getInterpolation(float input) { - return super.getInterpolation(Math.min(1, input * 2.0f)); - } - } -} diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 4ea0046..8783994 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -299,11 +299,10 @@ public class VectorDrawable extends Drawable { return color; } - @Override public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - final TypedArray a = obtainAttributes(res, theme, attrs,R.styleable.VectorDrawable); + final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); updateStateFromTypedArray(a); a.recycle(); @@ -358,7 +357,7 @@ public class VectorDrawable extends Drawable { if (SHAPE_PATH.equals(tagName)) { final VPath path = new VPath(); path.inflate(res, attrs, theme); - currentGroup.add(path); + currentGroup.mChildren.add(path); if (path.getPathName() != null) { pathRenderer.mVGTargetsMap.put(path.getPathName(), path); } @@ -375,7 +374,7 @@ public class VectorDrawable extends Drawable { } else if (SHAPE_GROUP.equals(tagName)) { VGroup newChildGroup = new VGroup(); newChildGroup.inflate(res, attrs, theme); - currentGroup.mChildGroupList.add(newChildGroup); + currentGroup.mChildren.add(newChildGroup); groupStack.push(newChildGroup); if (newChildGroup.getGroupName() != null) { pathRenderer.mVGTargetsMap.put(newChildGroup.getGroupName(), @@ -424,16 +423,19 @@ public class VectorDrawable extends Drawable { private void printGroupTree(VGroup currentGroup, int level) { String indent = ""; - for (int i = 0 ; i < level ; i++) { + for (int i = 0; i < level; i++) { indent += " "; } // Print the current node - Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + Log.v(LOGTAG, indent + "current group is :" + currentGroup.getGroupName() + " rotation is " + currentGroup.mRotate); - Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); - // Then print all the children - for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { - printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); + Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); + // Then print all the children groups + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + printGroupTree((VGroup) child, level + 1); + } } } @@ -552,21 +554,21 @@ public class VectorDrawable extends Drawable { private boolean recursiveCanApplyTheme(VGroup currentGroup) { // We can do a tree traverse here, if there is one path return true, // then we return true for the whole tree. - final ArrayList<VPath> paths = currentGroup.mPathList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - return true; - } - } - - final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; - - for (int i = 0; i < childGroups.size(); i++) { - VGroup childGroup = childGroups.get(i); - if (childGroup.canApplyTheme() - || recursiveCanApplyTheme(childGroup)) { - return true; + final ArrayList<Object> children = currentGroup.mChildren; + + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme() + || recursiveCanApplyTheme(childGroup)) { + return true; + } + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + return true; + } } } return false; @@ -580,24 +582,22 @@ public class VectorDrawable extends Drawable { private void recursiveApplyTheme(VGroup currentGroup, Theme t) { // We can do a tree traverse here, apply theme to all paths which // can apply theme. - final ArrayList<VPath> paths = currentGroup.mPathList; - for (int j = paths.size() - 1; j >= 0; j--) { - final VPath path = paths.get(j); - if (path.canApplyTheme()) { - path.applyTheme(t); - } - } - - final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; - - for (int i = 0; i < childGroups.size(); i++) { - VGroup childGroup = childGroups.get(i); - if (childGroup.canApplyTheme()) { - childGroup.applyTheme(t); + final ArrayList<Object> children = currentGroup.mChildren; + for (int i = 0; i < children.size(); i++) { + Object child = children.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + if (childGroup.canApplyTheme()) { + childGroup.applyTheme(t); + } + recursiveApplyTheme(childGroup, t); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + if (childPath.canApplyTheme()) { + childPath.applyTheme(t); + } } - recursiveApplyTheme(childGroup, t); } - } public void setColorFilter(ColorFilter colorFilter) { @@ -624,11 +624,18 @@ public class VectorDrawable extends Drawable { currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha; - drawPath(currentGroup, stackedAlpha, canvas, w, h); - // Draw the group tree in post order. - for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { - drawGroupTree(currentGroup.mChildGroupList.get(i), - currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h); + + // Draw the group tree in the same order as the XML file. + for (int i = 0; i < currentGroup.mChildren.size(); i++) { + Object child = currentGroup.mChildren.get(i); + if (child instanceof VGroup) { + VGroup childGroup = (VGroup) child; + drawGroupTree(childGroup, currentGroup.mStackedMatrix, + stackedAlpha, canvas, w, h); + } else if (child instanceof VPath) { + VPath childPath = (VPath) child; + drawPath(currentGroup, childPath, stackedAlpha, canvas, w, h); + } } } @@ -637,7 +644,8 @@ public class VectorDrawable extends Drawable { drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h); } - private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) { + private void drawPath(VGroup vGroup, VPath vPath, float stackedAlpha, + Canvas canvas, int w, int h) { final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); mFinalPathMatrix.set(vGroup.mStackedMatrix); @@ -645,75 +653,71 @@ public class VectorDrawable extends Drawable { mFinalPathMatrix.postTranslate( w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); - ArrayList<VPath> paths = vGroup.getPaths(); - for (int i = 0; i < paths.size(); i++) { - VPath vPath = paths.get(i); - vPath.toPath(mPath); - final Path path = mPath; + vPath.toPath(mPath); + final Path path = mPath; - if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { - float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; - float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; + if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) { + float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f; + float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f; - if (mPathMeasure == null) { - mPathMeasure = new PathMeasure(); - } - mPathMeasure.setPath(mPath, false); - - float len = mPathMeasure.getLength(); - start = start * len; - end = end * len; - path.reset(); - if (start > end) { - mPathMeasure.getSegment(start, len, path, true); - mPathMeasure.getSegment(0f, end, path, true); - } else { - mPathMeasure.getSegment(start, end, path, true); - } - path.rLineTo(0, 0); // fix bug in measure + if (mPathMeasure == null) { + mPathMeasure = new PathMeasure(); } + mPathMeasure.setPath(mPath, false); + + float len = mPathMeasure.getLength(); + start = start * len; + end = end * len; + path.reset(); + if (start > end) { + mPathMeasure.getSegment(start, len, path, true); + mPathMeasure.getSegment(0f, end, path, true); + } else { + mPathMeasure.getSegment(start, end, path, true); + } + path.rLineTo(0, 0); // fix bug in measure + } - mRenderPath.reset(); + mRenderPath.reset(); - mRenderPath.addPath(path, mFinalPathMatrix); + mRenderPath.addPath(path, mFinalPathMatrix); - if (vPath.mClip) { - canvas.clipPath(mRenderPath, Region.Op.REPLACE); - } else { - if (vPath.mFillColor != 0) { - if (mFillPaint == null) { - mFillPaint = new Paint(); - mFillPaint.setColorFilter(mColorFilter); - mFillPaint.setStyle(Paint.Style.FILL); - mFillPaint.setAntiAlias(true); - } - mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); - canvas.drawPath(mRenderPath, mFillPaint); + if (vPath.mClip) { + canvas.clipPath(mRenderPath, Region.Op.REPLACE); + } else { + if (vPath.mFillColor != 0) { + if (mFillPaint == null) { + mFillPaint = new Paint(); + mFillPaint.setColorFilter(mColorFilter); + mFillPaint.setStyle(Paint.Style.FILL); + mFillPaint.setAntiAlias(true); } + mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha)); + canvas.drawPath(mRenderPath, mFillPaint); + } - if (vPath.mStrokeColor != 0) { - if (mStrokePaint == null) { - mStrokePaint = new Paint(); - mStrokePaint.setColorFilter(mColorFilter); - mStrokePaint.setStyle(Paint.Style.STROKE); - mStrokePaint.setAntiAlias(true); - } + if (vPath.mStrokeColor != 0) { + if (mStrokePaint == null) { + mStrokePaint = new Paint(); + mStrokePaint.setColorFilter(mColorFilter); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setAntiAlias(true); + } - final Paint strokePaint = mStrokePaint; - if (vPath.mStrokeLineJoin != null) { - strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); - } + final Paint strokePaint = mStrokePaint; + if (vPath.mStrokeLineJoin != null) { + strokePaint.setStrokeJoin(vPath.mStrokeLineJoin); + } - if (vPath.mStrokeLineCap != null) { - strokePaint.setStrokeCap(vPath.mStrokeLineCap); - } + if (vPath.mStrokeLineCap != null) { + strokePaint.setStrokeCap(vPath.mStrokeLineCap); + } - strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); + strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale); - strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); - strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); - canvas.drawPath(mRenderPath, strokePaint); - } + strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha)); + strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale); + canvas.drawPath(mRenderPath, strokePaint); } } } @@ -742,7 +746,7 @@ public class VectorDrawable extends Drawable { } private void parseSize(Resources r, AttributeSet attrs) - throws XmlPullParserException { + throws XmlPullParserException { final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize); // Account for any configuration changes. @@ -771,8 +775,7 @@ public class VectorDrawable extends Drawable { ///////////////////////////////////////////////////// // Variables below need to be copied (deep copy if applicable) for mutation. - private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); - private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); + final ArrayList<Object> mChildren = new ArrayList<Object>(); private float mRotate = 0; private float mPivotX = 0; @@ -808,19 +811,21 @@ public class VectorDrawable extends Drawable { mLocalMatrix.set(copy.mLocalMatrix); - for (int i = 0; i < copy.mPathList.size(); i ++) { - VPath copyPath = copy.mPathList.get(i); - VPath newPath = new VPath(copyPath); - mPathList.add(newPath); - if (newPath.mPathName != null) { - targetsMap.put(copyPath.mPathName, newPath); + final ArrayList<Object> children = copy.mChildren; + for (int i = 0; i < children.size(); i++) { + Object copyChild = children.get(i); + if (copyChild instanceof VGroup) { + VGroup copyGroup = (VGroup) copyChild; + mChildren.add(new VGroup(copyGroup, targetsMap)); + } else if (copyChild instanceof VPath) { + VPath copyPath = (VPath) copyChild; + VPath newPath = new VPath(copyPath); + mChildren.add(newPath); + if (newPath.mPathName != null) { + targetsMap.put(newPath.mPathName, newPath); + } } } - - for (int i = 0; i < copy.mChildGroupList.size(); i ++) { - VGroup currentGroup = copy.mChildGroupList.get(i); - mChildGroupList.add(new VGroup(currentGroup, targetsMap)); - } } public VGroup() { @@ -922,10 +927,6 @@ public class VectorDrawable extends Drawable { return mLocalMatrix; } - public void add(VPath path) { - mPathList.add(path); - } - public boolean canApplyTheme() { return mThemeAttrs != null; } @@ -980,15 +981,6 @@ public class VectorDrawable extends Drawable { mLocalMatrix.postRotate(mRotate, 0, 0); mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); } - - /** - * Must return in order of adding - * @return ordered list of paths - */ - public ArrayList<VPath> getPaths() { - return mPathList; - } - } private static class VPath { @@ -1012,7 +1004,7 @@ public class VectorDrawable extends Drawable { float mStrokeMiterlimit = 4; private PathParser.PathDataNode[] mNodes = null; - private String mPathName; + String mPathName; private int mChangingConfigurations; public VPath() { diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index ee77897..cc72ae0 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -60,7 +60,6 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons GLint status; glGetProgramiv(mProgramId, GL_LINK_STATUS, &status); if (status != GL_TRUE) { - ALOGE("Error while linking shaders:"); GLint infoLen = 0; glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { @@ -68,14 +67,7 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons glGetProgramInfoLog(mProgramId, infoLen, 0, &log[0]); ALOGE("%s", log); } - - glDetachShader(mProgramId, mVertexShader); - glDetachShader(mProgramId, mFragmentShader); - - glDeleteShader(mVertexShader); - glDeleteShader(mFragmentShader); - - glDeleteProgram(mProgramId); + LOG_ALWAYS_FATAL("Error while linking shaders"); } else { mInitialized = true; } @@ -153,8 +145,7 @@ GLuint Program::buildShader(const char* source, GLenum type) { // use a fixed size instead GLchar log[512]; glGetShaderInfoLog(shader, sizeof(log), 0, &log[0]); - ALOGE("Error while compiling shader: %s", log); - glDeleteShader(shader); + LOG_ALWAYS_FATAL("Error while compiling shader: %s", log); return 0; } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5bcbc3c..c088b82 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2895,13 +2895,15 @@ public class AudioManager { * @param device out device type to be used for system audio mode. * Ignored if {@code on} is {@code false} * @param name name of system audio device + * @return output device type. 0 (DEVICE_NONE) if failed to set device. * @hide */ - public void setHdmiSystemAudioSupported(boolean on, int device, String name) { + public int setHdmiSystemAudioSupported(boolean on, int device, String name) { try { - getService().setHdmiSystemAudioSupported(on, device, name); + return getService().setHdmiSystemAudioSupported(on, device, name); } catch (RemoteException e) { Log.w(TAG, "Error setting system audio mode", e); + return AudioSystem.DEVICE_NONE; } } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index f262390..a58a20f 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -65,7 +65,6 @@ import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.System; - import android.telecomm.TelecommManager; import android.text.TextUtils; import android.util.Log; @@ -84,6 +83,7 @@ import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -850,11 +850,10 @@ public class AudioService extends IAudioService.Stub { streamType = getActiveStreamType(suggestedStreamType); } - // Play sounds on STREAM_RING only and if lock screen is not on. + // Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only. if ((streamType != STREAM_REMOTE_MUSIC) && (flags & AudioManager.FLAG_PLAY_SOUND) != 0 && - ((mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING) - || (mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()))) { + (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } @@ -3777,6 +3776,11 @@ public class AudioService extends IAudioService.Stub { AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE); } + if (mHdmiTvClient != null) { + setHdmiSystemAudioSupported(mHdmiSystemAudioSupported, + mHdmiSystemAudioOutputDevice, ""); + } + // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); break; @@ -4755,53 +4759,111 @@ public class AudioService extends IAudioService.Stub { private boolean mHdmiSystemAudioSupported = false; // Set only when device is tv. private HdmiTvClient mHdmiTvClient; + private int mHdmiSystemAudioOutputDevice = AudioSystem.DEVICE_NONE; + private int[] mSpeakerGains; @Override - public void setHdmiSystemAudioSupported(boolean on, int device, String name) { + public int setHdmiSystemAudioSupported(boolean on, int device, String name) { if (mHdmiTvClient == null) { Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode."); - return; + return AudioSystem.DEVICE_NONE; } - if ((device & AudioSystem.DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO) == 0) { - Log.w(TAG, "Unsupported Hdmi-Cec system audio output:" + device); - return; + if (on && !checkHdmiSystemAudioOutput(device)) { + return AudioSystem.DEVICE_NONE; } - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; - int oldStreamDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - int oldIndex = streamState.getIndex(oldStreamDevice); - synchronized (mHdmiTvClient) { + if (on) { + mHdmiSystemAudioOutputDevice = device; + } + if (mHdmiSystemAudioSupported == on) { + return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC); + } mHdmiSystemAudioSupported = on; + updateHdmiSystemAudioVolumeLocked(on); + } + return AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC); + } + + private boolean checkHdmiSystemAudioOutput(int device) { + if ((device & AudioSystem.DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO) == 0) { + Log.w(TAG, "Unsupported Hdmi-Cec system audio output:" + device); + return false; + } + + int streamDevice = AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC); + // If other devices except for system audio and speaker are available, + // fails to start system audio mode. + if ((streamDevice & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER) != 0) { + Log.w(TAG, "Should turn off other devices before starting system audio:" + + streamDevice); + return false; + } + if (AudioSystem.getDeviceConnectionState(device, "") != + AudioSystem.DEVICE_STATE_AVAILABLE) { + Log.w(TAG, "Output device is not connected:" + device); + return false; + } + return true; + } - // TODO: call AudioSystem.setForceUse(FORCE_FOR_MEDIA, - // on ? AudioSystem.FORCE_SYSTEM_AUDIO_XXX : AudioSystem.FORCE_NONE; + private void updateHdmiSystemAudioVolumeLocked(boolean on) { + AudioDevicePort speaker = findAudioDevicePort(AudioSystem.DEVICE_OUT_SPEAKER); + if (speaker == null) { + Log.w(TAG, "Has no speaker output."); + return; } - int newStreamDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC); - boolean updateSpeakerVolume = false; + AudioPortConfig portConfig = speaker.activeConfig(); + AudioGainConfig gainConfig = portConfig.gain(); + int[] newGains; + // When system audio is on, backup original gains and mute all channels of speaker by + // setting gains to 0; otherwise, restore gains of speaker. if (on) { - if ((oldStreamDevice & AudioSystem.DEVICE_OUT_SPEAKER) != 0) { - // Mute tv speaker. Note that set volume 0 instead of call mute() method because - // it's not possible to mute for a specific device. - streamState.setIndex(0, AudioSystem.DEVICE_OUT_SPEAKER); - updateSpeakerVolume = true; + if (gainConfig == null) { + Log.w(TAG, "Speaker has no gain control."); + return; } + // Back up original gains. + mSpeakerGains = Arrays.copyOf(gainConfig.values(), gainConfig.values().length); + // Set all gains to 0. + newGains = new int[gainConfig.values().length]; } else { - if ((newStreamDevice & AudioSystem.DEVICE_OUT_SPEAKER) != 0) { - // Restore speaker volume if exists. As there is no way to mute a device here, - // load system audio's volume and set it to speaker. - streamState.setIndex(oldIndex, AudioSystem.DEVICE_OUT_SPEAKER); - updateSpeakerVolume = true; + if (mSpeakerGains == null) { + Log.w(TAG, "mSpeakerGains should not be null."); + return; } + newGains = Arrays.copyOf(mSpeakerGains, mSpeakerGains.length); } - if (updateSpeakerVolume) { - sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_SPEAKER, 0, - streamState, 0); + gainConfig = gainConfig.mGain.buildConfig(gainConfig.mode(), + gainConfig.channelMask(), + newGains, + gainConfig.rampDurationMs()); + + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + if (AudioSystem.SUCCESS != audioManager.setAudioPortGain(speaker, gainConfig)) { + Log.w(TAG, "Failed to update audio port config."); + } + } + + private AudioDevicePort findAudioDevicePort(int type) { + ArrayList<AudioPort> devicePorts = new ArrayList<>(); + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + int status = audioManager.listAudioDevicePorts(devicePorts); + if (status != AudioSystem.SUCCESS) { + Log.w(TAG, "Failed to list up all audio ports"); + return null; + } + + for (AudioPort port : devicePorts) { + AudioDevicePort devicePort = (AudioDevicePort) port; + if (devicePort.type() == type) { + return devicePort; + } } + return null; } //========================================================================================== diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 392d953..72367c8 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -290,6 +290,9 @@ public class AudioSystem public static final int DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO = (DEVICE_OUT_LINE | DEVICE_OUT_HDMI_ARC | DEVICE_OUT_SPDIF); + public static final int DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER = + (DEVICE_OUT_ALL_HDMI_SYSTEM_AUDIO | + DEVICE_OUT_SPEAKER); // input devices public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1; @@ -437,10 +440,7 @@ public class AudioSystem public static final int FORCE_DIGITAL_DOCK = 9; public static final int FORCE_NO_BT_A2DP = 10; public static final int FORCE_SYSTEM_ENFORCED = 11; - public static final int FORCE_SYSTEM_AUDIO_HDMI_ARC = 12; - public static final int FORCE_SYSTEM_AUDIO_SPDIF = 13; - public static final int FORCE_SYSTEM_LINE = 14; - private static final int NUM_FORCE_CONFIG = 15; + private static final int NUM_FORCE_CONFIG = 12; public static final int FORCE_DEFAULT = FORCE_NONE; // usage for setForceUse, must match AudioSystem::force_use diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 277d41e..9bf7ace 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -198,5 +198,5 @@ interface IAudioService { void disableSafeMediaVolume(); - oneway void setHdmiSystemAudioSupported(boolean on, int device, String name); + int setHdmiSystemAudioSupported(boolean on, int device, String name); } diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 2854007..27360ed 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -19,6 +19,7 @@ package android.media.tv; import android.content.ComponentName; import android.media.tv.ITvInputSession; import android.net.Uri; +import android.media.tv.TvTrackInfo; import android.os.Bundle; import android.view.InputChannel; @@ -32,8 +33,6 @@ oneway interface ITvInputClient { void onAvailabilityChanged(in String inputId, boolean isAvailable); void onSessionReleased(int seq); void onSessionEvent(in String name, in Bundle args, int seq); - void onVideoStreamChanged(int width, int height, boolean interlaced, int seq); - void onAudioStreamChanged(int channelCount, int seq); - void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq); void onChannelRetuned(in Uri channelUri, int seq); + void onTrackInfoChanged(in List<TvTrackInfo> tracks, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 6db5a18..bdebd7c 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -23,6 +23,7 @@ import android.media.tv.ITvInputHardwareCallback; import android.media.tv.ITvInputClient; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.view.Surface; @@ -44,6 +45,8 @@ interface ITvInputManager { void setSurface(in IBinder sessionToken, in Surface surface, int userId); void setVolume(in IBinder sessionToken, float volume, int userId); void tune(in IBinder sessionToken, in Uri channelUri, int userId); + void selectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId); + void unselectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId); void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index fb2e251..f8590f1 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -17,6 +17,7 @@ package android.media.tv; import android.graphics.Rect; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.view.Surface; @@ -32,6 +33,8 @@ oneway interface ITvInputSession { // is to introduce some new concepts that will solve a number of problems in audio policy today. void setVolume(float volume); void tune(in Uri channelUri); + void selectTrack(in TvTrackInfo track); + void unselectTrack(in TvTrackInfo track); void createOverlayView(in IBinder windowToken, in Rect frame); void relayoutOverlayView(in Rect frame); diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 5a57ccd..4d72059 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -18,6 +18,7 @@ package android.media.tv; import android.media.tv.ITvInputSession; import android.net.Uri; +import android.media.tv.TvTrackInfo; import android.os.Bundle; /** @@ -28,8 +29,6 @@ import android.os.Bundle; oneway interface ITvInputSessionCallback { void onSessionCreated(ITvInputSession session); void onSessionEvent(in String name, in Bundle args); - void onVideoStreamChanged(int width, int height, boolean interlaced); - void onAudioStreamChanged(int channelCount); - void onClosedCaptionStreamChanged(boolean hasClosedCaption); void onChannelRetuned(in Uri channelUri); + void onTrackInfoChanged(in List<TvTrackInfo> tracks); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index d20ee0e..06ee4b5 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -44,9 +44,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand 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_CREATE_OVERLAY_VIEW = 5; - private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; - private static final int DO_REMOVE_OVERLAY_VIEW = 7; + private static final int DO_SELECT_TRACK = 5; + private static final int DO_UNSELECT_TRACK = 6; + private static final int DO_CREATE_OVERLAY_VIEW = 7; + private static final int DO_RELAYOUT_OVERLAY_VIEW = 8; + private static final int DO_REMOVE_OVERLAY_VIEW = 9; private final HandlerCaller mCaller; @@ -96,6 +98,14 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.tune((Uri) msg.obj); return; } + case DO_SELECT_TRACK: { + mTvInputSessionImpl.selectTrack((TvTrackInfo) msg.obj); + return; + } + case DO_UNSELECT_TRACK: { + mTvInputSessionImpl.unselectTrack((TvTrackInfo) msg.obj); + return; + } case DO_CREATE_OVERLAY_VIEW: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); @@ -138,6 +148,16 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override + public void selectTrack(TvTrackInfo track) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SELECT_TRACK, track)); + } + + @Override + public void unselectTrack(TvTrackInfo track) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UNSELECT_TRACK, track)); + } + + @Override public void createOverlayView(IBinder windowToken, Rect frame) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken, frame)); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 834ce64..521c809 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -35,6 +35,7 @@ import android.view.Surface; import android.view.View; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -89,49 +90,22 @@ public final class TvInputManager { } /** - * This is called at the beginning of the playback of a channel and later when the format of - * the video stream has been changed. - * - * @param session A {@link TvInputManager.Session} associated with this callback - * @param width The width of the video. - * @param height The height of the video. - * @param interlaced whether the video is interlaced mode or planer mode. - * @hide - */ - public void onVideoStreamChanged(Session session, int width, int height, - boolean interlaced) { - } - - /** - * This is called at the beginning of the playback of a channel and later when the format of - * the audio stream has been changed. - * - * @param session A {@link TvInputManager.Session} associated with this callback - * @param channelCount The number of channels in the audio stream. - * @hide - */ - public void onAudioStreamChanged(Session session, int channelCount) { - } - - /** - * This is called at the beginning of the playback of a channel and later when the closed - * caption stream has been changed. + * This is called when the channel of this session is changed by the underlying TV input + * with out any {@link TvInputManager.Session#tune(Uri)} request. * * @param session A {@link TvInputManager.Session} associated with this callback - * @param hasClosedCaption Whether the stream has closed caption or not. - * @hide + * @param channelUri The URI of a channel. */ - public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) { + public void onChannelRetuned(Session session, Uri channelUri) { } /** - * This is called when the channel of this session is changed by the underlying TV input - * with out any {@link TvInputManager.Session#tune(Uri)} request. + * This is called when the track information of the session has been changed. * * @param session A {@link TvInputManager.Session} associated with this callback - * @param channelUri The URI of a channel. + * @param tracks A list which includes track information. */ - public void onChannelRetuned(Session session, Uri channelUri) { + public void onTrackInfoChanged(Session session, List<TvTrackInfo> tracks) { } /** @@ -176,39 +150,21 @@ public final class TvInputManager { }); } - public void postVideoStreamChanged(final int width, final int height, - final boolean interlaced) { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onVideoStreamChanged(mSession, width, height, interlaced); - } - }); - } - - public void postAudioStreamChanged(final int channelCount) { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onAudioStreamChanged(mSession, channelCount); - } - }); - } - - public void postClosedCaptionStreamChanged(final boolean hasClosedCaption) { + public void postChannelRetuned(final Uri channelUri) { mHandler.post(new Runnable() { @Override public void run() { - mSessionCallback.onClosedCaptionStreamChanged(mSession, hasClosedCaption); + mSessionCallback.onChannelRetuned(mSession, channelUri); } }); } - public void postChannelRetuned(final Uri channelUri) { + public void postTrackInfoChanged(final List<TvTrackInfo> tracks) { mHandler.post(new Runnable() { @Override public void run() { - mSessionCallback.onChannelRetuned(mSession, channelUri); + mSession.setTracks(tracks); + mSessionCallback.onTrackInfoChanged(mSession, tracks); } }); } @@ -301,50 +257,26 @@ public final class TvInputManager { } @Override - public void onVideoStreamChanged(int width, int height, boolean interlaced, int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq " + seq); - return; - } - record.postVideoStreamChanged(width, height, interlaced); - } - } - - @Override - public void onAudioStreamChanged(int channelCount, int seq) { + public void onChannelRetuned(Uri channelUri, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for seq " + seq); return; } - record.postAudioStreamChanged(channelCount); + record.postChannelRetuned(channelUri); } } @Override - public void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq) { + public void onTrackInfoChanged(List<TvTrackInfo> tracks, int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { Log.e(TAG, "Callback not found for seq " + seq); return; } - record.postClosedCaptionStreamChanged(hasClosedCaption); - } - } - - @Override - public void onChannelRetuned(Uri channelUri, int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq " + seq); - return; - } - record.postChannelRetuned(channelUri); + record.postTrackInfoChanged(tracks); } } @@ -550,6 +482,7 @@ public final class TvInputManager { private IBinder mToken; private TvInputEventSender mSender; private InputChannel mChannel; + private List<TvTrackInfo> mTracks; /** @hide */ private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, @@ -633,6 +566,7 @@ public final class TvInputManager { Log.w(TAG, "The session has been already released"); return; } + mTracks = null; try { mService.tune(mToken, channelUri, mUserId); } catch (RemoteException e) { @@ -641,6 +575,65 @@ public final class TvInputManager { } /** + * Select a track. + * + * @param track the track to be selected. + * @see #getTracks() + */ + public void selectTrack(TvTrackInfo track) { + if (track == null) { + throw new IllegalArgumentException("track cannot be null"); + } + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.selectTrack(mToken, track, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Unselect a track. + * + * @param track the track to be selected. + * @see #getTracks() + */ + public void unselectTrack(TvTrackInfo track) { + if (track == null) { + throw new IllegalArgumentException("track cannot be null"); + } + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.unselectTrack(mToken, track, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns a list which includes track information. May return {@code null} if the + * information is not available. + * @see #selectTrack(TvTrackInfo) + * @see #unselectTrack(TvTrackInfo) + */ + public List<TvTrackInfo> getTracks() { + if (mTracks == null) { + return null; + } + return new ArrayList<TvTrackInfo>(mTracks); + } + + private void setTracks(List<TvTrackInfo> tracks) { + mTracks = tracks; + } + + /** * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} * should be called whenever the layout of its containing view is changed. * {@link #removeOverlayView()} should be called to remove the overlay view. diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 1e512cd..4614bda 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -44,6 +44,8 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; +import java.util.List; + /** * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which * provides pass-through video or broadcast TV programs. @@ -220,85 +222,43 @@ public abstract class TvInputService extends Service { } /** - * Sends the change on the format of the video stream. This is expected to be called at the - * beginning of the playback and later when the format has been changed. - * - * @param width The width of the video. - * @param height The height of the video. - * @param interlaced Whether the video is interlaced mode or planer mode. - * @hide - */ - public void dispatchVideoStreamChanged(final int width, final int height, - final boolean interlaced) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged"); - mSessionCallback.onVideoStreamChanged(width, height, interlaced); - } catch (RemoteException e) { - Log.w(TAG, "error in dispatchVideoSizeChanged"); - } - } - }); - } - - /** - * Sends the change on the format of the audio stream. This is expected to be called at the - * beginning of the playback and later when the format has been changed. - * - * @param channelNumber The number of channels in the audio stream. - * @hide - */ - public void dispatchAudioStreamChanged(final int channelNumber) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.d(TAG, "dispatchAudioStreamChanged"); - mSessionCallback.onAudioStreamChanged(channelNumber); - } catch (RemoteException e) { - Log.w(TAG, "error in dispatchAudioStreamChanged"); - } - } - }); - } - - /** - * Sends the change on the closed caption stream. This is expected to be called at the - * beginning of the playback and later when the stream has been changed. + * Notifies the channel of the session is retuned by TV input. * - * @param hasClosedCaption Whether the stream has closed caption or not. - * @hide + * @param channelUri The URI of a channel. */ - public void dispatchClosedCaptionStreamChanged(final boolean hasClosedCaption) { + public void dispatchChannelRetuned(final Uri channelUri) { mHandler.post(new Runnable() { @Override public void run() { try { - if (DEBUG) Log.d(TAG, "dispatchClosedCaptionStreamChanged"); - mSessionCallback.onClosedCaptionStreamChanged(hasClosedCaption); + if (DEBUG) Log.d(TAG, "dispatchChannelRetuned"); + mSessionCallback.onChannelRetuned(channelUri); } catch (RemoteException e) { - Log.w(TAG, "error in dispatchClosedCaptionStreamChanged"); + Log.w(TAG, "error in dispatchChannelRetuned"); } } }); } /** - * Notifies the channel of the session is retuned by TV input. + * Sends the change on the track information. This is expected to be called whenever a + * track is added/removed and the metadata of a track is modified. * - * @param channelUri The URI of a channel. + * @param tracks A list which includes track information. */ - public void dispatchChannelRetuned(final Uri channelUri) { + public void dispatchTrackInfoChanged(final List<TvTrackInfo> tracks) { + if (!TvTrackInfo.checkSanity(tracks)) { + throw new IllegalArgumentException( + "Two or more selected tracks for a track type."); + } mHandler.post(new Runnable() { @Override public void run() { try { - if (DEBUG) Log.d(TAG, "dispatchChannelRetuned"); - mSessionCallback.onChannelRetuned(channelUri); + if (DEBUG) Log.d(TAG, "dispatchTrackInfoChanged"); + mSessionCallback.onTrackInfoChanged(tracks); } catch (RemoteException e) { - Log.w(TAG, "error in dispatchChannelRetuned"); + Log.w(TAG, "error in dispatchTrackInfoChanged"); } } }); @@ -335,6 +295,38 @@ public abstract class TvInputService extends Service { public abstract boolean onTune(Uri channelUri); /** + * Selects a given track. + * <p> + * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the + * track selected previously should be unselected in the implementation of this method. + * Also, if the select operation was successful, the implementation should call + * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. + * </p> + * @param track The track to be selected. + * @return {@code true} if the select operation was successful, {@code false} otherwise. + * @see #dispatchTrackInfoChanged(TvTrackInfo[]) + * @see TvTrackInfo#KEY_IS_SELECTED + */ + public boolean onSelectTrack(TvTrackInfo track) { + return false; + } + + /** + * Unselects a given track. + * <p> + * If the unselect operation was successful, the implementation should call + * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. + * </p> + * @param track The track to be unselected. + * @return {@code true} if the unselect operation was successful, {@code false} otherwise. + * @see #dispatchTrackInfoChanged(TvTrackInfo[]) + * @see TvTrackInfo#KEY_IS_SELECTED + */ + public boolean onUnselectTrack(TvTrackInfo track) { + return false; + } + + /** * Called when an application requests to create an overlay view. Each session * implementation can override this method and return its own view. * @@ -500,6 +492,22 @@ public abstract class TvInputService extends Service { } /** + * Calls {@link #onSelectTrack}. + */ + void selectTrack(TvTrackInfo track) { + onSelectTrack(track); + // TODO: Handle failure. + } + + /** + * Calls {@link #onUnselectTrack}. + */ + void unselectTrack(TvTrackInfo track) { + onUnselectTrack(track); + // TODO: Handle failure. + } + + /** * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach * to the overlay window. * diff --git a/media/java/android/media/tv/TvTrackInfo.aidl b/media/java/android/media/tv/TvTrackInfo.aidl new file mode 100644 index 0000000..63b6b2a --- /dev/null +++ b/media/java/android/media/tv/TvTrackInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +parcelable TvTrackInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java new file mode 100644 index 0000000..de4f4b7 --- /dev/null +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -0,0 +1,292 @@ +/* + * 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.media.MediaFormat; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Encapsulates the format of tracks played in {@link TvInputService}. + */ +public final class TvTrackInfo implements Parcelable { + /** + * A key describing the type of this track. The associated value is an integer and it should be + * one of {@link #VALUE_TYPE_AUDIO}, {@link #VALUE_TYPE_VIDEO}, and {@link #VALUE_TYPE_SUBTITLE}. + * <p> + * This is a required key. + * </p> + */ + public static final String KEY_TYPE = "type"; + + /** + * A key describing the language of the track, using either ISO 639-1 or 639-2/T codes. + * If the language is unknown or could not be determined, the corresponding value will be "und". + * The associated value is a string. + * <p> + * This is a required key. + * </p> + */ + public static final String KEY_LANGUAGE = MediaFormat.KEY_LANGUAGE; + + /** + * A key describing whether this track is selected for the playback. + * The associated value is a boolean. + * <p> + * This is a required key. + * </p> + */ + public static final String KEY_IS_SELECTED = "is-selected"; + + /** + * A key describing the sample rate of an audio track. + * The associated value is an integer. + */ + public static final String KEY_SAMPLE_RATE = MediaFormat.KEY_SAMPLE_RATE; + + /** + * A key describing the number of channels in an audio track. + * The associated value is an integer. + */ + public static final String KEY_CHANNEL_COUNT = MediaFormat.KEY_CHANNEL_COUNT; + + /** + * A key describing the width of the content in a video track. + * The associated value is an integer. + */ + public static final String KEY_WIDTH = MediaFormat.KEY_WIDTH; + + /** + * A key describing the height of the content in a video track. + * The associated value is an integer. + */ + public static final String KEY_HEIGHT = MediaFormat.KEY_HEIGHT; + + /** + * A key describing a tag associated with this track. Expected to be used as an identifier with + * in a session. The associated value is a string. + */ + public static final String KEY_TAG = "tag"; + + /** + * The type value for audio track. + */ + public static final int VALUE_TYPE_AUDIO = 0; + + /** + * The type value for video track. + */ + public static final int VALUE_TYPE_VIDEO = 1; + + /** + * The type value for subtitle track. + */ + public static final int VALUE_TYPE_SUBTITLE = 2; + + private final Bundle mBundle; + + private TvTrackInfo(Bundle bundle) { + mBundle = new Bundle(bundle); + } + + private TvTrackInfo(Parcel in) { + mBundle = in.readBundle(); + } + + /** + * Checks if there is only one or zero selected track per track type. + * + * @param tracks a list including tracks which will be checked. + * @return true if there is only one or zero selected track per track type, false otherwise + * @hide + */ + public static boolean checkSanity(List<TvTrackInfo> tracks) { + int selectedAudioTracks = 0; + int selectedVideoTracks = 0; + int selectedSubtitleTracks = 0; + for (TvTrackInfo track : tracks) { + if (track.getBoolean(KEY_IS_SELECTED)) { + int type = track.getInt(KEY_TYPE); + if (type == VALUE_TYPE_AUDIO) { + selectedAudioTracks++; + } else if (type == VALUE_TYPE_VIDEO) { + selectedVideoTracks++; + } else if (type == VALUE_TYPE_SUBTITLE) { + selectedSubtitleTracks++; + } + } + } + if (selectedAudioTracks > 1 || selectedVideoTracks > 1 || selectedSubtitleTracks > 1) { + return false; + } + return true; + } + + /** + * Returns true if the given key is contained in the metadata + * + * @param key A String key + * @return true If the key exists in this metadata, false otherwise + */ + public boolean containsKey(String key) { + return mBundle.containsKey(key); + } + + /** + * Returns the value associated with the given key, or null if no mapping of + * the desired type exists for the given key or a null value is explicitly + * associated with the key. + * + * @param key The key the value is stored under + * @return A String value, or null + */ + public String getString(String key) { + return mBundle.getString(key); + } + + /** + * Returns the value associated with the given key, or 0L if no integer exists + * for the given key. + * + * @param key The key the value is stored under + * @return An integer value + */ + public int getInt(String key) { + return mBundle.getInt(key, 0); + } + + /** + * Returns the value associated with the given key, or false if no integer exists + * for the given key. + * + * @param key The key the value is stored under + * @return A boolean value + */ + public boolean getBoolean(String key) { + return mBundle.getBoolean(key, false); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBundle(mBundle); + } + + public static final Parcelable.Creator<TvTrackInfo> CREATOR + = new Parcelable.Creator<TvTrackInfo>() { + @Override + public TvTrackInfo createFromParcel(Parcel in) { + return new TvTrackInfo(in); + } + + @Override + public TvTrackInfo[] newArray(int size) { + return new TvTrackInfo[size]; + } + }; + + /** + * A builder class for creating {@link TvTrackInfo} objects. + */ + public static final class Builder { + private final Bundle mBundle; + + /** + * Create a {@link Builder}. Any field that should be included in the + * {@link TvTrackInfo} must be added. + * + * @param type The type of the track. + * @param language The language of the track, using either ISO 639-1 or 639-2/T codes. + * "und" if the language is unknown. + * @param isSelected Whether this track is selected for the playback or not. + */ + public Builder(int type, String language, boolean isSelected) { + if (type != VALUE_TYPE_AUDIO + && type != VALUE_TYPE_VIDEO + && type != VALUE_TYPE_SUBTITLE) { + throw new IllegalArgumentException("Unknown type: " + type); + } + mBundle = new Bundle(); + putInt(KEY_TYPE, type); + putString(KEY_LANGUAGE, language); + putBoolean(KEY_IS_SELECTED, isSelected); + } + + /** + * Create a Builder using a {@link TvTrackInfo} instance to set the + * initial values. All fields in the source metadata will be included in + * the new metadata. Fields can be overwritten by adding the same key. + * + * @param source The source {@link TvTrackInfo} instance + */ + public Builder(TvTrackInfo source) { + mBundle = new Bundle(source.mBundle); + } + + /** + * Put a String value into the track. + * + * @param key The key for referencing this value + * @param value The String value to store + * @return The Builder to allow chaining + */ + public Builder putString(String key, String value) { + mBundle.putString(key, value); + return this; + } + + /** + * Put an integer value into the track. + * + * @param key The key for referencing this value + * @param value The integer value to store + * @return The Builder to allow chaining + */ + public Builder putInt(String key, int value) { + mBundle.putInt(key, value); + return this; + } + + /** + * Put a boolean value into the track. + * + * @param key The key for referencing this value + * @param value The boolean value to store + * @return The Builder to allow chaining + */ + public Builder putBoolean(String key, boolean value) { + mBundle.putBoolean(key, value); + return this; + } + + /** + * Creates a {@link TvTrackInfo} instance with the specified fields. + * + * @return The new {@link TvTrackInfo} instance + */ + public TvTrackInfo build() { + return new TvTrackInfo(mBundle); + } + } +}
\ No newline at end of file diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 664a215..10f0c6b 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -18,6 +18,7 @@ package android.media.tv; import android.content.Context; import android.graphics.Rect; +import android.media.MediaPlayer.TrackInfo; import android.media.tv.TvInputManager.Session; import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; import android.media.tv.TvInputManager.SessionCallback; @@ -37,6 +38,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import java.util.List; + /** * View playing TV */ @@ -57,6 +60,8 @@ public class TvView extends ViewGroup { */ public static final int ERROR_TV_INPUT_DISCONNECTED = 1; + private static final int VIDEO_SIZE_VALUE_UNKNOWN = 0; + private final Handler mHandler = new Handler(); private TvInputManager.Session mSession; private final SurfaceView mSurfaceView; @@ -69,6 +74,8 @@ public class TvView extends ViewGroup { private OnUnhandledInputEventListener mOnUnhandledInputEventListener; private boolean mHasStreamVolume; private float mStreamVolume; + private int mVideoWidth = VIDEO_SIZE_VALUE_UNKNOWN; + private int mVideoHeight = VIDEO_SIZE_VALUE_UNKNOWN; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -209,6 +216,44 @@ public class TvView extends ViewGroup { } /** + * Select a track. + * <p> + * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the track + * selected in previous will be unselected. + * </p> + * @param track the track to be selected. + * @see #getTracks() + */ + public void selectTrack(TvTrackInfo track) { + if (mSession != null) { + mSession.selectTrack(track); + } + } + + /** + * Unselect a track. + * + * @param track the track to be unselected. + * @see #getTracks() + */ + public void unselectTrack(TvTrackInfo track) { + if (mSession != null) { + mSession.unselectTrack(track); + } + } + + /** + * Returns a list which includes of track information. May return {@code null} if the + * information is not available. + */ + public List<TvTrackInfo> getTracks() { + if (mSession == null) { + return null; + } + return mSession.getTracks(); + } + + /** * Dispatches an unhandled input event to the next receiver. * <p> * Except system keys, TvView always consumes input events in the normal flow. This is called @@ -401,6 +446,23 @@ public class TvView extends ViewGroup { location[0] + getWidth(), location[1] + getHeight()); } + private void updateVideoSize(List<TvTrackInfo> tracks) { + for (TvTrackInfo track : tracks) { + if (track.getBoolean(TvTrackInfo.KEY_IS_SELECTED) + && track.getInt(TvTrackInfo.KEY_TYPE) == TvTrackInfo.VALUE_TYPE_VIDEO) { + int width = track.getInt(TvTrackInfo.KEY_WIDTH); + int height = track.getInt(TvTrackInfo.KEY_HEIGHT); + if (width != mVideoWidth || height != mVideoHeight) { + mVideoWidth = width; + mVideoHeight = height; + if (mListener != null) { + mListener.onVideoSizeChanged(mSessionCallback.mInputId, width, height); + } + } + } + } + } + /** * Interface used to receive various status updates on the {@link TvView}. */ @@ -418,51 +480,32 @@ public class TvView extends ViewGroup { /** * This is invoked when the view is tuned to a specific channel and starts decoding video - * stream from there. It is also called later when the video format is changed. + * stream from there. It is also called later when the video size is changed. * * @param inputId The ID of the TV input bound to this view. * @param width The width of the video. * @param height The height of the video. - * @param interlaced {@code true} if the video is interlaced, {@code false} if the video is - * progressive. - * @hide - */ - public void onVideoStreamChanged(String inputId, int width, int height, - boolean interlaced) { - } - - /** - * This is invoked when the view is tuned to a specific channel and starts decoding audio - * stream from there. It is also called later when the audio format is changed. - * - * @param inputId The ID of the TV input bound to this view. - * @param channelCount The number of channels in the audio stream. - * @hide */ - public void onAudioStreamChanged(String inputId, int channelCount) { + public void onVideoSizeChanged(String inputId, int width, int height) { } /** - * This is invoked when the view is tuned to a specific channel and starts decoding data - * stream that includes subtitle information from the channel. It is also called later when - * the information disappears or appears. + * This is invoked when the channel of this TvView is changed by the underlying TV input + * with out any {@link TvView#tune(String, Uri)} request. * * @param inputId The ID of the TV input bound to this view. - * @param hasClosedCaption {@code true} if the stream contains closed caption, {@code false} - * otherwise. - * @hide + * @param channelUri The URI of a channel. */ - public void onClosedCaptionStreamChanged(String inputId, boolean hasClosedCaption) { + public void onChannelRetuned(String inputId, Uri channelUri) { } /** - * This is invoked when the channel of this TvView is changed by the underlying TV input - * with out any {@link TvView#tune(String, Uri)} request. + * This is called when the track information has been changed. * * @param inputId The ID of the TV input bound to this view. - * @param channelUri The URI of a channel. + * @param tracks A list which includes track information. */ - public void onChannelRetuned(String inputId, Uri channelUri) { + public void onTrackInfoChanged(String inputId, List<TvTrackInfo> tracks) { } /** @@ -533,57 +576,45 @@ public class TvView extends ViewGroup { @Override public void onSessionReleased(Session session) { + if (this == mSessionCallback) { + mSessionCallback = null; + } mSession = null; - mSessionCallback = null; if (mListener != null) { mListener.onError(mInputId, ERROR_TV_INPUT_DISCONNECTED); } } @Override - public void onVideoStreamChanged(Session session, int width, int height, - boolean interlaced) { - if (DEBUG) { - Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")"); - } - if (mListener != null) { - mListener.onVideoStreamChanged(mInputId, width, height, interlaced); - } - } - - @Override - public void onAudioStreamChanged(Session session, int channelCount) { + public void onChannelRetuned(Session session, Uri channelUri) { if (DEBUG) { - Log.d(TAG, "onAudioStreamChanged(" + channelCount + ")"); + Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")"); } if (mListener != null) { - mListener.onAudioStreamChanged(mInputId, channelCount); + mListener.onChannelRetuned(mInputId, channelUri); } } @Override - public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) { - if (DEBUG) { - Log.d(TAG, "onClosedCaptionStreamChanged(" + hasClosedCaption + ")"); - } - if (mListener != null) { - mListener.onClosedCaptionStreamChanged(mInputId, hasClosedCaption); + public void onTrackInfoChanged(Session session, List<TvTrackInfo> tracks) { + if (this != mSessionCallback) { + return; } - } - - @Override - public void onChannelRetuned(Session session, Uri channelUri) { if (DEBUG) { - Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")"); + Log.d(TAG, "onTrackInfoChanged()"); } + updateVideoSize(tracks); if (mListener != null) { - mListener.onChannelRetuned(mInputId, channelUri); + mListener.onTrackInfoChanged(mInputId, tracks); } } @Override public void onSessionEvent(TvInputManager.Session session, String eventType, Bundle eventArgs) { + if (this != mSessionCallback) { + return; + } if (mListener != null) { mListener.onEvent(mInputId, eventType, eventArgs); } diff --git a/packages/CaptivePortalLogin/Android.mk b/packages/CaptivePortalLogin/Android.mk new file mode 100644 index 0000000..576debc --- /dev/null +++ b/packages/CaptivePortalLogin/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := CaptivePortalLogin +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml new file mode 100644 index 0000000..5f78afe --- /dev/null +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.captiveportallogin" > + + <uses-permission android:name="android.permission.INTERNET" /> + + <application android:label="@string/app_name" > + <activity + android:name="com.android.captiveportallogin.CaptivePortalLoginActivity" + android:label="@string/action_bar_label" + android:theme="@android:style/Theme.Holo" > + <intent-filter> + <action android:name="android.intent.action.ACTION_SEND"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="text/plain"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml new file mode 100644 index 0000000..d8f2928 --- /dev/null +++ b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml @@ -0,0 +1,20 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" + tools:ignore="MergeRootFrame"> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <WebView + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="false" + android:layout_alignParentRight="false" /> + +</RelativeLayout> +</FrameLayout> diff --git a/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml new file mode 100644 index 0000000..1a88c5c --- /dev/null +++ b/packages/CaptivePortalLogin/res/menu/captive_portal_login.xml @@ -0,0 +1,15 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context="com.android.captiveportallogin.CaptivePortalLoginActivity" > + <item + android:id="@+id/action_do_not_use_network" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_do_not_use_network"/> + <item + android:id="@+id/action_use_network" + android:orderInCategory="200" + android:showAsAction="never" + android:title="@string/action_use_network"/> + +</menu> diff --git a/packages/CaptivePortalLogin/res/values/dimens.xml b/packages/CaptivePortalLogin/res/values/dimens.xml new file mode 100644 index 0000000..55c1e59 --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/dimens.xml @@ -0,0 +1,7 @@ +<resources> + + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + +</resources> diff --git a/packages/CaptivePortalLogin/res/values/strings.xml b/packages/CaptivePortalLogin/res/values/strings.xml new file mode 100644 index 0000000..1b0f0a4 --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/strings.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">CaptivePortalLogin</string> + <string name="action_use_network">Use this network as is</string> + <string name="action_do_not_use_network">Do not use this network</string> + <string name="action_bar_label">Sign-in to network</string> + +</resources> diff --git a/packages/CaptivePortalLogin/res/values/styles.xml b/packages/CaptivePortalLogin/res/values/styles.xml new file mode 100644 index 0000000..6ce89c7 --- /dev/null +++ b/packages/CaptivePortalLogin/res/values/styles.xml @@ -0,0 +1,20 @@ +<resources> + + <!-- + Base application theme, dependent on API level. This theme is replaced + by AppBaseTheme from res/values-vXX/styles.xml on newer devices. + --> + <style name="AppBaseTheme" parent="android:Theme.Light"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + </style> + +</resources> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java new file mode 100644 index 0000000..2c1db02 --- /dev/null +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.captiveportallogin; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.ConnectivityManager; +import android.net.Network; +import android.os.Bundle; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Window; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.lang.InterruptedException; + +public class CaptivePortalLoginActivity extends Activity { + private static final String DEFAULT_SERVER = "clients3.google.com"; + private static final int SOCKET_TIMEOUT_MS = 10000; + + // Keep this in sync with NetworkMonitor. + // Intent broadcast to ConnectivityService indicating sign-in is complete. + // Extras: + // EXTRA_TEXT = netId + // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. + private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = + "android.net.netmon.captive_portal_logged_in"; + private static final String LOGGED_IN_RESULT = "result"; + + private URL mURL; + private int mNetId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String server = Settings.Global.getString(getContentResolver(), "captive_portal_server"); + if (server == null) server = DEFAULT_SERVER; + try { + mURL = new URL("http://" + server + "/generate_204"); + } catch (MalformedURLException e) { + done(true); + } + + requestWindowFeature(Window.FEATURE_PROGRESS); + setContentView(R.layout.activity_captive_portal_login); + + getActionBar().setDisplayShowHomeEnabled(false); + + mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT)); + ConnectivityManager.setProcessDefaultNetwork(new Network(mNetId)); + + WebView myWebView = (WebView) findViewById(R.id.webview); + WebSettings webSettings = myWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + myWebView.setWebViewClient(new MyWebViewClient()); + myWebView.setWebChromeClient(new MyWebChromeClient()); + myWebView.loadUrl(mURL.toString()); + } + + private void done(boolean use_network) { + Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId)); + intent.putExtra(LOGGED_IN_RESULT, use_network ? "1" : "0"); + sendBroadcast(intent); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.captive_portal_login, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.action_use_network) { + done(true); + return true; + } + if (id == R.id.action_do_not_use_network) { + done(false); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void testForCaptivePortal() { + new Thread(new Runnable() { + public void run() { + // Give time for captive portal to open. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + HttpURLConnection urlConnection = null; + int httpResponseCode = 500; + try { + urlConnection = (HttpURLConnection) mURL.openConnection(); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } catch (IOException e) { + } finally { + if (urlConnection != null) urlConnection.disconnect(); + } + if (httpResponseCode == 204) { + done(true); + } + } + }).start(); + } + + private class MyWebViewClient extends WebViewClient { + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + testForCaptivePortal(); + } + + @Override + public void onPageFinished(WebView view, String url) { + testForCaptivePortal(); + } + } + + private class MyWebChromeClient extends WebChromeClient { + @Override + public void onProgressChanged(WebView view, int newProgress) { + setProgress(newProgress*100); + } + } +} diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index 2fd7bc4..8b79dbf 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -69,7 +69,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="8dp" - android:paddingEnd="4dp" + android:paddingEnd="@dimen/battery_level_padding_end" android:textColor="#ffffff" android:textSize="12sp"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml index 9d959fa..11f50ee 100644 --- a/packages/SystemUI/res/layout/zen_mode_panel.xml +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -56,7 +56,6 @@ android:id="@+id/zen_subhead_expanded" android:layout_width="wrap_content" android:layout_height="32dp" - android:clickable="true" android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.QS.Subhead" /> @@ -79,15 +78,4 @@ android:orientation="vertical" android:paddingTop="3dp" /> - <TextView - android:id="@+id/zen_alarm_warning" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="8dp" - android:paddingLeft="@dimen/qs_panel_padding" - android:paddingRight="@dimen/qs_panel_padding" - android:paddingTop="8dp" - android:text="@string/zen_alarm_warning" - android:textAppearance="@style/TextAppearance.QS.Warning" /> - </com.android.systemui.volume.ZenModePanel>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0bad2c3..e20947f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -232,8 +232,9 @@ <!-- Space reserved for the cards behind the top card in the bottom stack --> <dimen name="bottom_stack_peek_amount">12dp</dimen> - <!-- bottom_stack_peek_amount + notification_min_height --> - <dimen name="min_stack_height">76dp</dimen> + <!-- bottom_stack_peek_amount + notification_min_height + + notification_collapse_second_card_padding --> + <dimen name="min_stack_height">84dp</dimen> <!-- The height of the area before the bottom stack in which the notifications slow down --> <dimen name="bottom_stack_slow_down_length">12dp</dimen> @@ -354,4 +355,7 @@ <!-- The font size of the time when expanded in QS --> <dimen name="qs_time_expanded_size">20sp</dimen> + + <!-- Battery level padding end when in expanded QS (but not on Keyguard) --> + <dimen name="battery_level_padding_end">4dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ff60f1d..848fdf8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -595,8 +595,8 @@ <!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> - <!-- Zen mode: Alarm warning. [CHAR LIMIT=40] --> - <string name="zen_alarm_warning">You won\'t hear alarms or timers</string> + <!-- Zen mode: No interruptions title, with a warning about alarms and timers. [CHAR LIMIT=60] --> + <string name="zen_no_interruptions_with_warning">No interruptions, including alarms and timers</string> <!-- Zen mode: No interruptions. [CHAR LIMIT=40] --> <string name="zen_no_interruptions">No interruptions</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d4acb11..f99b68b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -185,11 +185,6 @@ <item name="android:textColor">@color/qs_subhead</item> </style> - <style name="TextAppearance.QS.Warning"> - <item name="android:textSize">12sp</item> - <item name="android:textColor">@color/qs_subhead</item> - </style> - <style name="TextAppearance.QS.SegmentedButton"> <item name="android:textSize">12sp</item> <item name="android:textAllCaps">true</item> @@ -259,11 +254,6 @@ <item name="android:layout_width">match_parent</item> </style> - <style name="QSWhiteTheme" parent="@android:style/Theme.DeviceDefault"> - <item name="android:colorControlNormal">#ffffffff</item> - <item name="android:colorControlActivated">#ffffffff</item> - </style> - <style name="QSBorderlessButton"> <item name="android:padding">12dp</item> <item name="android:background">@drawable/btn_borderless_rect</item> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 06cb318..597bb93 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -190,6 +190,7 @@ public class QSTileView extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int w = getMeasuredWidth(); + final int h = getMeasuredHeight(); int top = 0; top += mTileSpacingPx; @@ -199,8 +200,8 @@ public class QSTileView extends ViewGroup { if (mRipple != null) { // center the touch feedback on the center of the icon, and dial it down a bit final int cx = w / 2; - final int cy = mIcon.getTop() + mIcon.getHeight() / 2; - final int rad = (int)(mIcon.getHeight() * 1.5); + final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : h / 2; + final int rad = (int)(mIcon.getHeight() * 1.25); mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); } top = mIcon.getBottom(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 9e86a1e..2876607 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -90,6 +90,10 @@ public class WifiTile extends QSTile<QSTile.SignalState> { @Override protected void handleSecondaryClick() { + if (!mState.enabled) { + mController.setWifiEnabled(true); + mState.enabled = true; + } showDetail(true); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 57f4565..d1efb57 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -106,6 +106,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView public void run() { // Mark Recents as no longer visible AlternateRecentsComponent.notifyVisibilityChanged(false); + mVisible = false; // Finish Recents if (mLaunchIntent != null) { if (mLaunchOpts != null) { @@ -170,7 +171,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_OFF)) { + if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) { mFinishLaunchHomeRunnable.run(); } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) { // Refresh the search widget @@ -518,8 +519,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (mConfig.searchBarAppWidgetId >= 0) { mAppWidgetHost.stopListening(); } - - mVisible = false; } @Override @@ -641,6 +640,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView public void onTaskViewClicked() { // Mark recents as no longer visible AlternateRecentsComponent.notifyVisibilityChanged(false); + mVisible = false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 9921c55..ed3ebf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -75,16 +75,18 @@ public class NotificationData { } private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); - private RankingMap mRanking; + private RankingMap mRankingMap; + private final Ranking mTmpRanking = new Ranking(); private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { + private final Ranking mRankingA = new Ranking(); + private final Ranking mRankingB = new Ranking(); + @Override public int compare(Entry a, Entry b) { - if (mRanking != null) { - Ranking aRanking = mRanking.getRanking(a.key); - Ranking bRanking = mRanking.getRanking(b.key); - int aRank = aRanking != null ? aRanking.getRank() : -1; - int bRank = bRanking != null ? bRanking.getRank() : -1; - return aRank - bRank; + if (mRankingMap != null) { + mRankingMap.getRanking(a.key, mRankingA); + mRankingMap.getRanking(b.key, mRankingB); + return mRankingA.getRank() - mRankingB.getRank(); } final StatusBarNotification na = a.notification; @@ -138,7 +140,7 @@ public class NotificationData { public boolean isAmbient(String key) { // TODO: Remove when switching to NotificationListener. - if (mRanking == null) { + if (mRankingMap == null) { for (Entry entry : mEntries) { if (key.equals(entry.key)) { return entry.notification.getNotification().priority == @@ -146,15 +148,15 @@ public class NotificationData { } } } else { - Ranking ranking = mRanking.getRanking(key); - return ranking != null && ranking.isAmbient(); + mRankingMap.getRanking(key, mTmpRanking); + return mTmpRanking.isAmbient(); } return false; } private void updateRankingAndSort(RankingMap ranking) { if (ranking != null) { - mRanking = ranking; + mRankingMap = ranking; } Collections.sort(mEntries, mRankingComparator); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index e0a1ef1..55b3088 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -126,6 +126,8 @@ public class NotificationPanelView extends PanelView implements private boolean mBlockTouches; private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); private int mNotificationScrimWaitDistance; + private boolean mTwoFingerQsExpand; + private boolean mTwoFingerQsExpandPossible; /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still @@ -487,7 +489,7 @@ public class NotificationPanelView extends PanelView implements if (mExpandedHeight != 0) { handleQsDown(event); } - if (mQsTracking || mQsExpanded) { + if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) { onQsTouch(event); if (!mConflictingQsExpansionGesture) { return true; @@ -497,6 +499,15 @@ public class NotificationPanelView extends PanelView implements || event.getActionMasked() == MotionEvent.ACTION_UP) { mConflictingQsExpansionGesture = false; } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) { + mTwoFingerQsExpandPossible = true; + } + if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN + && event.getPointerCount() == 2 + && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { + mTwoFingerQsExpand = true; + requestPanelHeightUpdate(); + } super.onTouchEvent(event); return true; } @@ -842,7 +853,7 @@ public class NotificationPanelView extends PanelView implements min = Math.max(min, minHeight); } int maxHeight; - if (mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { + if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { maxHeight = (int) calculatePanelHeightQsExpanded(); } else { int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); @@ -863,7 +874,7 @@ public class NotificationPanelView extends PanelView implements if (!mQsExpanded) { positionClockAndNotifications(); } - if (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null + if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll) { float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() + mNotificationStackScroller.getMinStackHeight() @@ -1049,6 +1060,8 @@ public class NotificationPanelView extends PanelView implements mHeader.setListening(true); mQsPanel.setListening(true); } + mTwoFingerQsExpand = false; + mTwoFingerQsExpandPossible = false; } @Override @@ -1060,7 +1073,7 @@ public class NotificationPanelView extends PanelView implements @Override protected void setOverExpansion(float overExpansion, boolean isPixels) { - if (mConflictingQsExpansionGesture) { + if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { return; } if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index b95b88f..0fbdeeb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -46,6 +46,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private boolean mListening; private boolean mOverscrolled; private boolean mKeyguardShowing; + private boolean mCharging; private ViewGroup mSystemIconsContainer; private View mSystemIconsSuperContainer; @@ -80,6 +81,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private int mClockMarginBottomExpanded; private int mMultiUserSwitchWidthCollapsed; private int mMultiUserSwitchWidthExpanded; + private int mBatteryPaddingEnd; /** * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice @@ -164,6 +166,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mClockCollapsedScaleFactor = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size) / mClock.getTextSize(); + mBatteryPaddingEnd = + getResources().getDimensionPixelSize(R.dimen.battery_level_padding_end); } public void setActivityStarter(ActivityStarter activityStarter) { @@ -210,6 +214,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updateClockScale(); updateAvatarScale(); updateClockLp(); + updateBatteryLevelPaddingEnd(); } } @@ -273,12 +278,13 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL ? VISIBLE : GONE); mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing ? VISIBLE : GONE); - mBatteryLevel.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE); + mBatteryLevel.setVisibility(mKeyguardShowing && mCharging || mExpanded && !mOverscrolled + ? View.VISIBLE : View.GONE); } private void updateSystemIconsLayoutParams() { RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams(); - lp.addRule(RelativeLayout.START_OF, mExpanded + lp.addRule(RelativeLayout.START_OF, mExpanded && !mOverscrolled ? mSettingsButton.getId() : mMultiUserSwitch.getId()); lp.removeRule(ALIGN_PARENT_START); @@ -318,9 +324,19 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL } } + private void updateBatteryLevelPaddingEnd() { + mBatteryLevel.setPaddingRelative(0, 0, + mKeyguardShowing && !mExpanded ? 0 : mBatteryPaddingEnd, 0); + } + @Override public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { mBatteryLevel.setText(getResources().getString(R.string.battery_level_template, level)); + boolean changed = mCharging != charging; + mCharging = charging; + if (changed) { + updateVisibilities(); + } } private void updateClickTargets() { @@ -361,14 +377,16 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private void updateMultiUserSwitch() { int marginEnd; - if (mExpanded) { + if (mExpanded && !mOverscrolled) { marginEnd = mMultiUserExpandedMargin; } else if (mKeyguardShowing) { marginEnd = mMultiUserKeyguardMargin; } else { marginEnd = mMultiUserCollapsedMargin; } - int width = mExpanded ? mMultiUserSwitchWidthExpanded : mMultiUserSwitchWidthCollapsed; + int width = mExpanded && !mOverscrolled + ? mMultiUserSwitchWidthExpanded + : mMultiUserSwitchWidthCollapsed; MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); if (marginEnd != lp.getMarginEnd() || lp.width != width) { lp.setMarginEnd(marginEnd); @@ -421,6 +439,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updatePadding(); updateMultiUserSwitch(); updateClickTargets(); + updateBatteryLevelPaddingEnd(); } public void setUserInfoController(UserInfoController userInfoController) { @@ -484,6 +503,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL return !mKeyguardShowing || mExpanded; } + @Override + protected void dispatchSetPressed(boolean pressed) { + // We don't want that everything lights up when we click on the header, so block the request + // here. + } + private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { @Override public void onToggleStateChanged(final boolean state) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 799b41f..2b08902 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -34,6 +34,7 @@ import android.os.Message; import android.os.Messenger; import android.provider.Settings; import android.telephony.PhoneStateListener; +import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -474,8 +475,8 @@ public class NetworkControllerImpl extends BroadcastReceiver PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { - if (DEBUG) { - Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength + + if (true/*DEBUG*/) { + Rlog.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength + ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); } mSignalStrength = signalStrength; @@ -485,8 +486,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onServiceStateChanged(ServiceState state) { - if (DEBUG) { - Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + if (true/*DEBUG*/) { + Rlog.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + " dataState=" + state.getDataRegState()); } mServiceState = state; @@ -498,8 +499,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onCallStateChanged(int state, String incomingNumber) { - if (DEBUG) { - Log.d(TAG, "onCallStateChanged state=" + state); + if (true/*DEBUG*/) { + Rlog.d(TAG, "onCallStateChanged state=" + state); } // In cdma, if a voice call is made, RSSI should switch to 1x. if (isCdma()) { @@ -510,8 +511,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onDataConnectionStateChanged(int state, int networkType) { - if (DEBUG) { - Log.d(TAG, "onDataConnectionStateChanged: state=" + state + if (true/*DEBUG*/) { + Rlog.d(TAG, "onDataConnectionStateChanged: state=" + state + " type=" + networkType); } mDataState = state; @@ -523,8 +524,8 @@ public class NetworkControllerImpl extends BroadcastReceiver @Override public void onDataActivity(int direction) { - if (DEBUG) { - Log.d(TAG, "onDataActivity: direction=" + direction); + if (true/*DEBUG*/) { + Rlog.d(TAG, "onDataActivity: direction=" + direction); } mDataActivity = direction; updateDataIcon(); @@ -555,6 +556,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } else { mSimState = IccCardConstants.State.UNKNOWN; } + Rlog.d(TAG, "updateSimState: mSimState=" + mSimState); } private boolean isCdma() { @@ -562,6 +564,7 @@ public class NetworkControllerImpl extends BroadcastReceiver } private boolean hasService() { + boolean retVal; if (mServiceState != null) { // Consider the device to be in service if either voice or data service is available. // Some SIM cards are marketed as data-only and do not support voice service, and on @@ -569,16 +572,18 @@ public class NetworkControllerImpl extends BroadcastReceiver // service" or "emergency calls only" text that indicates that voice is not available. switch(mServiceState.getVoiceRegState()) { case ServiceState.STATE_POWER_OFF: - return false; + retVal = false; case ServiceState.STATE_OUT_OF_SERVICE: case ServiceState.STATE_EMERGENCY_ONLY: - return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; default: - return true; + retVal = true; } } else { - return false; + retVal = false; } + Rlog.d(TAG, "hasService: mServiceState=" + mServiceState + " retVal=" + retVal); + return retVal; } private void updateAirplaneMode() { @@ -591,14 +596,15 @@ public class NetworkControllerImpl extends BroadcastReceiver } private final void updateTelephonySignalStrength() { - if (!hasService()) { + Rlog.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() + " ss=" + mSignalStrength); + if (false/*!hasService()*/) { if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: !hasService()"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; } else { if (mSignalStrength == null) { - if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null"); + if (true/*CHATTY*/) Rlog.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; @@ -609,7 +615,7 @@ public class NetworkControllerImpl extends BroadcastReceiver int[] iconList; if (isCdma() && mAlwaysShowCdmaRssi) { mLastSignalLevel = iconLevel = mSignalStrength.getCdmaLevel(); - if(DEBUG) Log.d(TAG, "mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi + if(true/*DEBUG*/) Rlog.d(TAG, "updateTelephonySignalStrength: mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi + " set to cdmaLevel=" + mSignalStrength.getCdmaLevel() + " instead of level=" + mSignalStrength.getLevel()); } else { @@ -636,6 +642,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mContentDescriptionPhoneSignal = mContext.getString( AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]); mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel]; + Rlog.d(TAG, "updateTelephonySignalStrength: iconLevel=" + iconLevel); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 1721ec4..9bcffd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1317,7 +1317,8 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getPeekHeight() { - return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize; + return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize + + mCollapseSecondCardPadding; } private int clampPadding(int desiredPadding) { @@ -1795,11 +1796,11 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getEmptyBottomMargin() { - int emptyMargin = mMaxLayoutHeight - mContentHeight; + int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize; if (needsHeightAdaption()) { - emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize; + emptyMargin -= mBottomStackSlowDownHeight; } else { - emptyMargin = emptyMargin - mBottomStackPeekSize; + emptyMargin -= mCollapseSecondCardPadding; } return Math.max(emptyMargin, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index b9d07d5..6bb9765 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -85,7 +85,6 @@ public class ZenModePanel extends LinearLayout { private TextView mZenSubheadExpanded; private View mMoreSettings; private LinearLayout mZenConditions; - private View mAlarmWarning; private Callback mCallback; private ZenModeController mController; @@ -100,7 +99,7 @@ public class ZenModePanel extends LinearLayout { super(context, attrs); mContext = context; mFavorites = new Favorites(); - mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); + mInflater = LayoutInflater.from(mContext.getApplicationContext()); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); updateTag(); @@ -134,13 +133,6 @@ public class ZenModePanel extends LinearLayout { }); mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded); - mZenSubheadExpanded.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setExpanded(false); - fireInteraction(); - } - }); mMoreSettings = findViewById(R.id.zen_more_settings); mMoreSettings.setOnClickListener(new View.OnClickListener() { @@ -152,8 +144,6 @@ public class ZenModePanel extends LinearLayout { }); mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); - - mAlarmWarning = findViewById(R.id.zen_alarm_warning); } @Override @@ -273,16 +263,16 @@ public class ZenModePanel extends LinearLayout { final boolean zenOff = zen == Global.ZEN_MODE_OFF; final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; + final boolean foreverSelected = mExitConditionId == null; - mZenSubhead.setVisibility(!zenOff ? VISIBLE : GONE); + mZenSubhead.setVisibility(!zenOff && (mExpanded || !foreverSelected) ? VISIBLE : GONE); mZenSubheadExpanded.setVisibility(mExpanded ? VISIBLE : GONE); mZenSubheadCollapsed.setVisibility(!mExpanded ? VISIBLE : GONE); mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE); mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE); - mAlarmWarning.setVisibility(zenNone && mExpanded ? VISIBLE : GONE); if (zenNone) { - mZenSubheadExpanded.setText(R.string.zen_no_interruptions); + mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); mZenSubheadCollapsed.setText(mExitConditionText); } else if (zenImportant) { mZenSubheadExpanded.setText(R.string.zen_important_interruptions); diff --git a/phone/java/android/phone/PhoneManager.java b/phone/java/android/phone/PhoneManager.java index 360565e..f61b054 100644 --- a/phone/java/android/phone/PhoneManager.java +++ b/phone/java/android/phone/PhoneManager.java @@ -30,20 +30,17 @@ public final class PhoneManager { private static final String TAG = PhoneManager.class.getSimpleName(); private final Context mContext; - private final ITelecommService mService; /** * @hide */ - public PhoneManager(Context context, ITelecommService service) { + public PhoneManager(Context context) { Context appContext = context.getApplicationContext(); if (appContext != null) { mContext = appContext; } else { mContext = context; } - - mService = service; } /** @@ -56,10 +53,13 @@ public final class PhoneManager { * @return True if the digits were processed as an MMI code, false otherwise. */ public boolean handlePinMmi(String dialString) { - try { - return mService.handlePinMmi(dialString); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + return service.handlePinMmi(dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#handlePinMmi", e); + } } return false; } @@ -71,10 +71,13 @@ public final class PhoneManager { * </p> */ public void cancelMissedCallsNotification() { - try { - mService.cancelMissedCallsNotification(); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#cancelMissedCallNotification", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + service.cancelMissedCallsNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#cancelMissedCallsNotification", e); + } } } @@ -89,10 +92,13 @@ public final class PhoneManager { * @param showDialpad Brings up the in-call dialpad as part of showing the in-call screen. */ public void showCallScreen(boolean showDialpad) { - try { - mService.showCallScreen(showDialpad); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecommService#showCallScreen", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + service.showCallScreen(showDialpad); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#showCallScreen", e); + } } } @@ -103,11 +109,19 @@ public final class PhoneManager { * </p> */ public boolean isInAPhoneCall() { - try { - return mService.isInAPhoneCall(); - } catch (RemoteException e) { - Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e); + ITelecommService service = getTelecommService(); + if (service != null) { + try { + return service.isInAPhoneCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error caling ITelecommService#isInAPhoneCall", e); + } } return false; } + + private ITelecommService getTelecommService() { + return ITelecommService.Stub.asInterface( + ServiceManager.getService(Context.TELECOMM_SERVICE)); + } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 0b1252e..ef15a80 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -527,6 +527,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_WINDOW_MANAGER_DRAWN_COMPLETE = 7; private static final int MSG_WAKING_UP = 8; private static final int MSG_DISPATCH_SHOW_RECENTS = 9; + private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10; private class PolicyHandler extends Handler { @Override @@ -547,6 +548,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_DISPATCH_SHOW_RECENTS: showRecentApps(false); break; + case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS: + showGlobalActionsInternal(); + break; case MSG_KEYGUARD_DRAWN_COMPLETE: if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete"); mKeyguardDrawComplete = true; @@ -859,8 +863,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { performAuditoryFeedbackForAccessibilityIfNeed(); } - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); - showGlobalActionsDialog(); + showGlobalActionsInternal(); break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: @@ -880,7 +883,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; - void showGlobalActionsDialog() { + @Override + public void showGlobalActions() { + mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS); + mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS); + } + + void showGlobalActionsInternal() { + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); if (mGlobalActions == null) { mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); } @@ -5496,12 +5506,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { // prevent status bar interaction from clearing certain flags boolean statusBarHasFocus = win.getAttrs().type == TYPE_STATUS_BAR; - if (statusBarHasFocus) { + if (statusBarHasFocus && !isStatusBarKeyguard()) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - if (!isStatusBarKeyguard() || mHideLockScreen) { + if (mHideLockScreen) { flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT; } vis = (vis & ~flags) | (oldVis & flags); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1be1572..ee7eb9f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2443,6 +2443,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: { expandQuickSettings(); } return true; + case AccessibilityService.GLOBAL_ACTION_POWER_DIALOG: { + showGlobalActions(); + } return true; } return false; } finally { @@ -2781,6 +2784,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Binder.restoreCallingIdentity(token); } + private void showGlobalActions() { + mWindowManagerService.showGlobalActions(); + } + private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1083c4c..ea05b98 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -87,6 +87,7 @@ import android.net.ProxyDataTracker; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.SamplingDataTracker; +import android.net.UidRange; import android.net.Uri; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -235,7 +236,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { @GuardedBy("mVpns") private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); - private VpnCallback mVpnCallback = new VpnCallback(); private boolean mLockdownEnabled; private LockdownVpnTracker mLockdownTracker; @@ -363,8 +363,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_SET_POLICY_DATA_ENABLE = 12; - private static final int EVENT_VPN_STATE_CHANGED = 13; - /** * Used internally to disable fail fast of mobile data */ @@ -1071,6 +1069,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { NetworkInfo info = getNetworkInfoForType(networkType); + return getFilteredNetworkInfo(info, networkType, uid); + } + + private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) { if (isNetworkBlocked(networkType, uid)) { // network is blocked; clone and override state info = new NetworkInfo(info); @@ -1176,6 +1178,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { } @Override + public NetworkInfo getNetworkInfoForNetwork(Network network) { + enforceAccessPermission(); + if (network == null) return null; + + final int uid = Binder.getCallingUid(); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai == null) return null; + synchronized (nai) { + if (nai.networkInfo == null) return null; + + return getFilteredNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), uid); + } + } + + @Override public NetworkInfo[] getAllNetworkInfo() { enforceAccessPermission(); final int uid = Binder.getCallingUid(); @@ -1192,6 +1212,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { } @Override + public Network[] getAllNetworks() { + enforceAccessPermission(); + final ArrayList<Network> result = new ArrayList(); + synchronized (mNetworkForNetId) { + for (int i = 0; i < mNetworkForNetId.size(); i++) { + result.add(new Network(mNetworkForNetId.valueAt(i).network)); + } + } + return result.toArray(new Network[result.size()]); + } + + @Override public boolean isNetworkSupported(int networkType) { enforceAccessPermission(); return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null)); @@ -1223,16 +1255,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = mNetworkForNetId.get(network.netId); - if (nai != null) return new LinkProperties(nai.linkProperties); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } + } return null; } @Override public NetworkCapabilities getNetworkCapabilities(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = mNetworkForNetId.get(network.netId); - if (nai != null) return new NetworkCapabilities(nai.networkCapabilities); + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai != null) { + synchronized (nai) { + return new NetworkCapabilities(nai.networkCapabilities); + } + } return null; } @@ -1777,8 +1824,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } return false; } - - DetailedState netState = nai.networkInfo.getDetailedState(); + DetailedState netState; + synchronized (nai) { + netState = nai.networkInfo.getDetailedState(); + } if ((netState != DetailedState.CONNECTED && netState != DetailedState.CAPTIVE_PORTAL_CHECK)) { @@ -1792,9 +1841,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - LinkProperties lp = nai.linkProperties; - boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, - nai.network.netId, uid); + LinkProperties lp = null; + int netId = INVALID_NET_ID; + synchronized (nai) { + lp = nai.linkProperties; + netId = nai.network.netId; + } + boolean ok = modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt, netId, uid); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; } finally { @@ -3096,7 +3149,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { if (VDBG) log("Update of Linkproperties for " + nai.name()); LinkProperties oldLp = nai.linkProperties; - nai.linkProperties = (LinkProperties)msg.obj; + synchronized (nai) { + nai.linkProperties = (LinkProperties)msg.obj; + } updateLinkProperties(nai, oldLp); } break; @@ -3121,6 +3176,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (score != null) updateNetworkScore(nai, score.intValue()); break; } + case NetworkAgent.EVENT_UID_RANGES_ADDED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_ADDED from unknown NetworkAgent"); + break; + } + try { + mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } + case NetworkAgent.EVENT_UID_RANGES_REMOVED: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_UID_RANGES_REMOVED from unknown NetworkAgent"); + break; + } + try { + mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj); + } catch (RemoteException e) { + } + break; + } case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; handleConnectionValidated(nai); @@ -3131,6 +3210,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleLingerComplete(nai); break; } + case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { + NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo); + if (nai == null) { + loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); + break; + } + setProvNotificationVisibleIntent(msg.arg1 != 0, nai.networkInfo.getType(), + nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj); + break; + } case NetworkStateTracker.EVENT_STATE_CHANGED: { info = (NetworkInfo) msg.obj; NetworkInfo.State state = info.getState(); @@ -3242,7 +3331,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Error connecting NetworkAgent"); NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo); if (nai != null) { - mNetworkForNetId.remove(nai.network.netId); + synchronized (mNetworkForNetId) { + mNetworkForNetId.remove(nai.network.netId); + } mLegacyTypeTracker.remove(nai); } } @@ -3275,7 +3366,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetworkAgentInfos.remove(msg.replyTo); updateClat(null, nai.linkProperties, nai); mLegacyTypeTracker.remove(nai); - mNetworkForNetId.remove(nai.network.netId); + synchronized (mNetworkForNetId) { + mNetworkForNetId.remove(nai.network.netId); + } // Since we've lost the network, go through all the requests that // it was satisfying and see if any other factory can satisfy them. final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>(); @@ -3388,12 +3481,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (affectedNetwork != null) { // check if this network still has live requests - otherwise, tear down // TODO - probably push this to the NF/NA - boolean keep = false; - for (int i = 0; i < affectedNetwork.networkRequests.size(); i++) { + boolean keep = affectedNetwork.isVPN(); + for (int i = 0; i < affectedNetwork.networkRequests.size() && !keep; i++) { NetworkRequest r = affectedNetwork.networkRequests.valueAt(i); if (mNetworkRequests.get(r).isRequest) { keep = true; - break; } } if (keep == false) { @@ -3473,12 +3565,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetPolicyDataEnable(networkType, enabled); break; } - case EVENT_VPN_STATE_CHANGED: { - if (mLockdownTracker != null) { - mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj); - } - break; - } case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: { int tag = mEnableFailFastMobileDataTag.get(); if (msg.arg1 == tag) { @@ -3613,6 +3699,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mTethering.getErroredIfaces(); } + public String[] getTetheredDhcpRanges() { + enforceConnectivityInternalPermission(); + return mTethering.getTetheredDhcpRanges(); + } + // if ro.tether.denied = true we default to no tethering // gservices could set the secure setting to 1 though to enable it on a build where it // had previously been turned off. @@ -3981,36 +4072,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * Protect a socket from VPN routing rules. This method is used by - * VpnBuilder and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - * @hide - */ - @Override - public boolean protectVpn(ParcelFileDescriptor socket) { - throwIfLockdownEnabled(); - try { - int type = mActiveDefaultNetwork; - int user = UserHandle.getUserId(Binder.getCallingUid()); - if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { - synchronized(mVpns) { - mVpns.get(user).protect(socket); - } - return true; - } - } catch (Exception e) { - // ignore - } finally { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } - return false; - } - - /** * Prepare for a VPN application. This method is used by VpnDialogs * and not available in ConnectivityManager. Permissions are checked * in Vpn class. @@ -4104,144 +4165,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Callback for VPN subsystem. Currently VPN is not adapted to the service - * through NetworkStateTracker since it works differently. For example, it - * needs to override DNS servers but never takes the default routes. It - * relies on another data network, and it could keep existing connections - * alive after reconnecting, switching between networks, or even resuming - * from deep sleep. Calls from applications should be done synchronously - * to avoid race conditions. As these are all hidden APIs, refactoring can - * be done whenever a better abstraction is developed. - */ - public class VpnCallback { - private VpnCallback() { - } - - public void onStateChanged(NetworkInfo info) { - mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); - } - - public void override(String iface, List<String> dnsServers, List<String> searchDomains) { - if (dnsServers == null) { - restore(); - return; - } - - // Convert DNS servers into addresses. - List<InetAddress> addresses = new ArrayList<InetAddress>(); - for (String address : dnsServers) { - // Double check the addresses and remove invalid ones. - try { - addresses.add(InetAddress.parseNumericAddress(address)); - } catch (Exception e) { - // ignore - } - } - if (addresses.isEmpty()) { - restore(); - return; - } - - // Concatenate search domains into a string. - StringBuilder buffer = new StringBuilder(); - if (searchDomains != null) { - for (String domain : searchDomains) { - buffer.append(domain).append(' '); - } - } - String domains = buffer.toString().trim(); - - // Apply DNS changes. - synchronized (mDnsLock) { - // TODO: Re-enable this when the netId of the VPN is known. - // updateDnsLocked("VPN", netId, addresses, domains); - } - - // Temporarily disable the default proxy (not global). - synchronized (mProxyLock) { - mDefaultProxyDisabled = true; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(null); - } - } - - // TODO: support proxy per network. - } - - public void restore() { - synchronized (mProxyLock) { - mDefaultProxyDisabled = false; - if (mGlobalProxy == null && mDefaultProxy != null) { - sendProxyBroadcast(mDefaultProxy); - } - } - } - - public void protect(ParcelFileDescriptor socket) { - try { - final int mark = mNetd.getMarkForProtect(); - NetworkUtils.markSocket(socket.getFd(), mark); - } catch (RemoteException e) { - } - } - - public void setRoutes(String interfaze, List<RouteInfo> routes) { - for (RouteInfo route : routes) { - try { - mNetd.setMarkedForwardingRoute(interfaze, route); - } catch (RemoteException e) { - } - } - } - - public void setMarkedForwarding(String interfaze) { - try { - mNetd.setMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void clearMarkedForwarding(String interfaze) { - try { - mNetd.clearMarkedForwarding(interfaze); - } catch (RemoteException e) { - } - } - - public void addUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - addUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) { - int uidStart = uid * UserHandle.PER_USER_RANGE; - int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; - clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns); - } - - public void addUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.setUidRangeRoute(netId, uidStart, uidEnd, forwardDns); - // } catch (RemoteException e) { - // } - - } - - public void clearUidForwarding(String interfaze, int uidStart, int uidEnd, - boolean forwardDns) { - // TODO: Re-enable this when the netId of the VPN is known. - // try { - // mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); - // } catch (RemoteException e) { - // } - - } - } - @Override public boolean updateLockdownVpn() { if (Binder.getCallingUid() != Process.SYSTEM_UID) { @@ -5039,6 +4962,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType + " extraInfo=" + extraInfo + " url=" + url); } + Intent intent = null; + PendingIntent pendingIntent = null; + if (visible) { + switch (networkType) { + case ConnectivityManager.TYPE_WIFI: + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + break; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); + intent.putExtra("EXTRA_URL", url); + intent.setFlags(0); + pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + break; + default: + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + break; + } + } + setProvNotificationVisibleIntent(visible, networkType, extraInfo, pendingIntent); + } + + private void setProvNotificationVisibleIntent(boolean visible, int networkType, + String extraInfo, PendingIntent intent) { + if (DBG) { + log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" + + networkType + " extraInfo=" + extraInfo); + } Resources r = Resources.getSystem(); NotificationManager notificationManager = (NotificationManager) mContext @@ -5048,7 +5005,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { CharSequence title; CharSequence details; int icon; - Intent intent; Notification notification = new Notification(); switch (networkType) { case ConnectivityManager.TYPE_WIFI: @@ -5056,10 +5012,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { details = r.getString(R.string.network_available_sign_in_detailed, extraInfo); icon = R.drawable.stat_notify_wifi_in_range; - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; case ConnectivityManager.TYPE_MOBILE: case ConnectivityManager.TYPE_MOBILE_HIPRI: @@ -5068,20 +5020,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // name has been added to it details = mTelephonyManager.getNetworkOperatorName(); icon = R.drawable.stat_notify_rssi_in_range; - intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); - intent.putExtra("EXTRA_URL", url); - intent.setFlags(0); - notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); break; default: title = r.getString(R.string.network_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, extraInfo); icon = R.drawable.stat_notify_rssi_in_range; - intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; } @@ -5090,6 +5034,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { notification.flags = Notification.FLAG_AUTO_CANCEL; notification.tickerText = title; notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); + notification.contentIntent = intent; try { notificationManager.notify(NOTIFICATION_ID, networkType, notification); @@ -5263,9 +5208,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Starting user already has a VPN"); return; } - userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId); + userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId); mVpns.put(userId, userVpn); - userVpn.startMonitoring(mContext, mTrackerHandler); } } @@ -5565,7 +5509,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleRegisterNetworkAgent(NetworkAgentInfo na) { if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(na.messenger, na); - mNetworkForNetId.put(na.network.netId, na); + synchronized (mNetworkForNetId) { + mNetworkForNetId.put(na.network.netId, na); + } na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger); NetworkInfo networkInfo = na.networkInfo; na.networkInfo = null; @@ -5710,7 +5656,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkCapabilities networkCapabilities) { // TODO - what else here? Verify still satisfies everybody? // Check if satisfies somebody new? call callbacks? - networkAgent.networkCapabilities = networkCapabilities; + synchronized (networkAgent) { + networkAgent.networkCapabilities = networkCapabilities; + } } private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) { @@ -5783,7 +5731,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Unknown NetworkAgentInfo in handleConnectionValidated"); return; } - boolean keep = false; + boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; if (DBG) log("handleConnectionValidated for "+newNetwork.name()); // check if any NetworkRequest wants this NetworkAgent @@ -5845,8 +5793,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } for (NetworkAgentInfo nai : affectedNetworks) { - boolean teardown = true; - for (int i = 0; i < nai.networkRequests.size(); i++) { + boolean teardown = !nai.isVPN(); + for (int i = 0; i < nai.networkRequests.size() && teardown; i++) { NetworkRequest nr = nai.networkRequests.valueAt(i); try { if (mNetworkRequests.get(nr).isRequest) { @@ -5924,8 +5872,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) { NetworkInfo.State state = newInfo.getState(); - NetworkInfo oldInfo = networkAgent.networkInfo; - networkAgent.networkInfo = newInfo; + NetworkInfo oldInfo = null; + synchronized (networkAgent) { + oldInfo = networkAgent.networkInfo; + networkAgent.networkInfo = newInfo; + } + if (networkAgent.isVPN() && mLockdownTracker != null) { + mLockdownTracker.onVpnStateChanged(newInfo); + } if (oldInfo != null && oldInfo.getState() == state) { if (VDBG) log("ignoring duplicate network state non-change"); @@ -5944,7 +5898,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // CONNECTING and back (like wifi on DHCP renew). // TODO: keep track of which networks we've created, or ask netd // to tell us whether we've already created this network or not. - mNetd.createNetwork(networkAgent.network.netId); + if (networkAgent.isVPN()) { + mNetd.createVirtualNetwork(networkAgent.network.netId, + !networkAgent.linkProperties.getDnsServers().isEmpty()); + } else { + mNetd.createPhysicalNetwork(networkAgent.network.netId); + } } catch (Exception e) { loge("Error creating network " + networkAgent.network.netId + ": " + e.getMessage()); @@ -5954,9 +5913,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateLinkProperties(networkAgent, null); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + if (networkAgent.isVPN()) { + // Temporarily disable the default proxy (not global). + synchronized (mProxyLock) { + if (!mDefaultProxyDisabled) { + mDefaultProxyDisabled = true; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(null); + } + } + } + // TODO: support proxy per network. + } } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); + if (networkAgent.isVPN()) { + synchronized (mProxyLock) { + if (mDefaultProxyDisabled) { + mDefaultProxyDisabled = false; + if (mGlobalProxy == null && mDefaultProxy != null) { + sendProxyBroadcast(mDefaultProxy); + } + } + } + } } } @@ -6054,9 +6035,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { private LinkProperties getLinkPropertiesForTypeInternal(int networkType) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - return (nai != null) ? - new LinkProperties(nai.linkProperties) : - new LinkProperties(); + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } + } + return new LinkProperties(); } private NetworkInfo getNetworkInfoForType(int networkType) { @@ -6075,8 +6059,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) { NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - return (nai != null) ? - new NetworkCapabilities(nai.networkCapabilities) : - new NetworkCapabilities(); + if (nai != null) { + synchronized (nai) { + return new NetworkCapabilities(nai.networkCapabilities); + } + } + return new NetworkCapabilities(); } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 8fb2e9f..d4f141d 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -148,7 +148,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_UNBIND_METHOD = 3000; static final int MSG_BIND_METHOD = 3010; static final int MSG_SET_ACTIVE = 3020; - static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 3030; static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040; static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; @@ -2338,26 +2337,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @Override - public void setCursorAnchorMonitorMode(IBinder token, int monitorMode) { - if (DEBUG) { - Slog.d(TAG, "setCursorAnchorMonitorMode: monitorMode=" + monitorMode); - } - if (!calledFromValidUser()) { - return; - } - synchronized (mMethodMap) { - if (!calledWithValidToken(token)) { - final int uid = Binder.getCallingUid(); - Slog.e(TAG, "Ignoring setCursorAnchorMonitorMode due to an invalid token. uid:" - + uid + " token:" + token); - return; - } - executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO( - MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, mCurClient)); - } - } - private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { setInputMethodWithSubtypeIdLocked(token, id, subtypeId); @@ -2595,15 +2574,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + ((ClientState)msg.obj).uid); } return true; - case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: - try { - ((ClientState)msg.obj).client.setCursorAnchorMonitorMode(msg.arg1); - } catch (RemoteException e) { - Slog.w(TAG, "Got RemoteException sending setCursorAnchorMonitorMode " - + "notification to pid " + ((ClientState)msg.obj).pid - + " uid " + ((ClientState)msg.obj).uid); - } - return true; case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: { final int sequenceNumber = msg.arg1; final IInputMethodClient client = (IInputMethodClient)msg.obj; diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index f9c7a78..c9f40cf 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -46,6 +46,7 @@ import android.net.LinkAddress; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.UidRange; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.BatteryStats; @@ -90,6 +91,7 @@ import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -116,6 +118,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; + private static final int MAX_UID_RANGES_PER_COMMAND = 10; + /** * Name representing {@link #setGlobalAlert(long)} limit when delivered to * {@link INetworkManagementEventObserver#limitReached(String, String)}. @@ -1702,44 +1706,46 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setUidRangeRoute(String iface, int uid_start, int uid_end, boolean forward_dns) { + public void addVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "add", iface, uid_start, uid_end, forward_dns ? 1 : 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void clearUidRangeRoute(String iface, int uid_start, int uid_end) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", - "uid", "remove", iface, uid_start, uid_end, 0); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); - } - } - - @Override - public void setMarkedForwarding(String iface) { - mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "add", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "add"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @Override - public void clearMarkedForwarding(String iface) { + public void removeVpnUidRanges(int netId, UidRange[] ranges) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - try { - mConnector.execute("interface", "fwmark", "rule", "remove", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "users"; + argv[1] = "remove"; + argv[2] = netId; + int argc = 3; + // Avoid overly long commands by limiting number of UID ranges per command. + for (int i = 0; i < ranges.length; i++) { + argv[argc++] = ranges[i].toString(); + if (i == (ranges.length - 1) || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } @@ -2015,7 +2021,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void createNetwork(int netId) { + public void createPhysicalNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { @@ -2026,6 +2032,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void createVirtualNetwork(int netId, boolean hasDNS) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "create", netId, "vpn", hasDNS ? "1" : "0"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override public void removeNetwork(int netId) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -2143,4 +2160,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw e.rethrowAsParcelableException(); } } + + @Override + public void allowProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "allow", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void denyProtect(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + try { + mConnector.execute("network", "protect", "deny", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 88598c9..a19eb15 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -47,8 +47,10 @@ import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; import android.telephony.PreciseDisconnectCause; import android.text.TextUtils; +import android.text.format.Time; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -354,10 +356,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } int phoneId = SubscriptionManager.getPhoneId(subId); r.events = events; - if (true/*DBG*/) log("listen: set events record=" + r); + if (true/*DBG*/) log("listen: set events record=" + r + " subId=" + subId + " phoneId=" + phoneId); + toStringLogSSC("listen"); if (notifyNow && validatePhoneId(phoneId)) { if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { try { + log("listen: call onSSC state=" + mServiceState[phoneId]); r.callback.onServiceStateChanged( new ServiceState(mServiceState[phoneId])); } catch (RemoteException ex) { @@ -550,14 +554,17 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { subId = mDefaultSubId; log("notifyServiceStateUsingSubId: using mDefaultSubId=" + mDefaultSubId); } - if (true/*VDBG*/) { - log("notifyServiceStateUsingSubId: subId=" + subId - + " state=" + state); - } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); + if (true/*VDBG*/) { + log("notifyServiceStateUsingSubId: subId=" + subId + " phoneId=" + phoneId + + " state=" + state); + } if (validatePhoneId(phoneId)) { mServiceState[phoneId] = state; + logServiceStateChanged("notifyServiceStateUsingSubId", subId, phoneId, state); + toStringLogSSC("notifyServiceStateUsingSubId"); + for (Record r : mRecords) { log("notifyServiceStateUsingSubId: r.events=0x" + Integer.toHexString(r.events) + " r.subId=" + r.subId + " subId=" + subId + " state=" + state); // FIXME: use DEFAULT_SUB_ID instead?? @@ -591,6 +598,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (true/*VDBG*/) { log("notifySignalStrengthUsingSubId: subId=" + subId + " signalStrength=" + signalStrength); + toStringLogSSC("notifySignalStrengthUsingSubId"); } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); @@ -1295,4 +1303,59 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private static void log(String s) { Rlog.d(TAG, s); } + + private static class LogSSC { + private Time mTime; + private String mS; + private long mSubId; + private int mPhoneId; + private ServiceState mState; + + public void set(Time t, String s, long subId, int phoneId, ServiceState state) { + mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state; + } + + @Override + public String toString() { + return mS + " " + mTime.toString() + " " + mSubId + " " + mPhoneId + " " + mState; + } + } + + private LogSSC logSSC [] = new LogSSC[10]; + private int next = 0; + + private void logServiceStateChanged(String s, long subId, int phoneId, ServiceState state) { + if (logSSC == null || logSSC.length == 0) { + return; + } + if (logSSC[next] == null) { + logSSC[next] = new LogSSC(); + } + Time t = new Time(); + t.setToNow(); + logSSC[next].set(t, s, subId, phoneId, state); + if (++next >= logSSC.length) { + next = 0; + } + } + + private void toStringLogSSC(String prompt) { + if (logSSC == null || logSSC.length == 0 || (next == 0 && logSSC[next] == null)) { + log(prompt + ": logSSC is empty"); + } else { + // There is at least one element + log(prompt + ": logSSC.length=" + logSSC.length + " next=" + next); + int i = next; + if (logSSC[i] == null) { + // logSSC is not full so back to the beginning + i = 0; + } + do { + log(logSSC[i].toString()); + if (++i >= logSSC.length) { + i = 0; + } + } while (i != next); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c7eabe8..e8e1536 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2236,6 +2236,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void start() { + Process.removeAllProcessGroups(); mProcessCpuThread.start(); mBatteryStatsService.publish(mContext); @@ -2827,6 +2828,7 @@ public final class ActivityManagerService extends ActivityManagerNative // An application record is attached to a previous process, // clean it up now. if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app); + Process.killProcessGroup(app.info.uid, app.pid); handleAppDiedLocked(app, true, true); } @@ -4069,6 +4071,8 @@ public final class ActivityManagerService extends ActivityManagerNative stats.noteProcessDiedLocked(app.info.uid, pid); } + Process.killProcessGroup(app.info.uid, pid); + // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { @@ -4307,7 +4311,10 @@ public final class ActivityManagerService extends ActivityManagerNative try { // 0 == continue, -1 = kill process immediately int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation); - if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid); + if (res < 0 && app.pid != MY_PID) { + Process.killProcess(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); + } } catch (RemoteException e) { mController = null; Watchdog.getInstance().setActivityController(null); @@ -4413,6 +4420,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (res != 0) { if (res < 0 && app.pid != MY_PID) { Process.killProcess(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); } else { synchronized (this) { mServices.scheduleServiceTimeoutLocked(app); @@ -5122,6 +5130,7 @@ public final class ActivityManagerService extends ActivityManagerNative mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } killUnneededProcessLocked(app, reason); + Process.killProcessGroup(app.info.uid, app.pid); handleAppDiedLocked(app, true, allowRestart); removeLruProcessLocked(app); @@ -5210,6 +5219,7 @@ public final class ActivityManagerService extends ActivityManagerNative EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid); if (pid > 0 && pid != MY_PID) { Process.killProcessQuiet(pid); + //TODO: Process.killProcessGroup(app.info.uid, pid); } else { try { thread.scheduleExit(); @@ -7409,6 +7419,7 @@ public final class ActivityManagerService extends ActivityManagerNative pr.processName, pr.setAdj, reason); pr.killedByAm = true; Process.killProcessQuiet(pr.pid); + Process.killProcessGroup(pr.info.uid, pr.pid); } } @@ -10749,11 +10760,15 @@ public final class ActivityManagerService extends ActivityManagerNative try { String name = r != null ? r.processName : null; int pid = r != null ? r.pid : Binder.getCallingPid(); + int uid = r != null ? r.info.uid : Binder.getCallingUid(); if (!mController.appCrashed(name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) { Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request"); Process.killProcess(pid); + if (r != null) { + Process.killProcessGroup(uid, pid); + } return; } } catch (RemoteException e) { @@ -16587,6 +16602,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.processName, app.setAdj, "empty"); app.killedByAm = true; Process.killProcessQuiet(app.pid); + Process.killProcessGroup(app.info.uid, app.pid); } else { try { app.thread.scheduleExit(); @@ -17031,22 +17047,34 @@ public final class ActivityManagerService extends ActivityManagerNative try { Intent intent; if (oldUserId >= 0) { - intent = new Intent(Intent.ACTION_USER_BACKGROUND); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY - | Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId); - broadcastIntentLocked(null, null, intent, - null, null, 0, null, null, null, AppOpsManager.OP_NONE, - false, false, MY_PID, Process.SYSTEM_UID, oldUserId); + // Send USER_BACKGROUND broadcast to all profiles of the outgoing user + List<UserInfo> profiles = mUserManager.getProfiles(oldUserId, false); + int count = profiles.size(); + for (int i = 0; i < count; i++) { + int profileUserId = profiles.get(i).id; + intent = new Intent(Intent.ACTION_USER_BACKGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, AppOpsManager.OP_NONE, + false, false, MY_PID, Process.SYSTEM_UID, profileUserId); + } } if (newUserId >= 0) { - intent = new Intent(Intent.ACTION_USER_FOREGROUND); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY - | Intent.FLAG_RECEIVER_FOREGROUND); - intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); - broadcastIntentLocked(null, null, intent, - null, null, 0, null, null, null, AppOpsManager.OP_NONE, - false, false, MY_PID, Process.SYSTEM_UID, newUserId); + // Send USER_FOREGROUND broadcast to all profiles of the incoming user + List<UserInfo> profiles = mUserManager.getProfiles(newUserId, false); + int count = profiles.size(); + for (int i = 0; i < count; i++) { + int profileUserId = profiles.get(i).id; + intent = new Intent(Intent.ACTION_USER_FOREGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, AppOpsManager.OP_NONE, + false, false, MY_PID, Process.SYSTEM_UID, profileUserId); + } intent = new Intent(Intent.ACTION_USER_SWITCHED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1332898..10bdba0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -69,6 +69,10 @@ public class NetworkAgentInfo { networkRequests.put(networkRequest.requestId, networkRequest); } + public boolean isVPN() { + return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); + } + public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} network{" + network + "} lp{" + diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 47789b1..6fb8570 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -16,17 +16,26 @@ package com.android.server.connectivity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.Handler; import android.os.Message; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkAgentInfo; import java.io.BufferedReader; @@ -34,6 +43,7 @@ import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URL; @@ -47,6 +57,19 @@ public class NetworkMonitor extends StateMachine { private static final String DEFAULT_SERVER = "clients3.google.com"; private static final int SOCKET_TIMEOUT_MS = 10000; + // Intent broadcast when user selects sign-in notification. + private static final String ACTION_SIGN_IN_REQUESTED = + "android.net.netmon.sign_in_requested"; + + // Keep these in sync with CaptivePortalLoginActivity.java. + // Intent broadcast from CaptivePortalLogin indicating sign-in is complete. + // Extras: + // EXTRA_TEXT = netId + // LOGGED_IN_RESULT = "1" if we should use network, "0" if not. + private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = + "android.net.netmon.captive_portal_logged_in"; + private static final String LOGGED_IN_RESULT = "result"; + private static final int BASE = Protocol.BASE_NETWORK_MONITOR; /** @@ -87,35 +110,63 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; /** - * Message to self indicating it's time to check for a captive portal again. - * TODO - Remove this once broadcast intents are used to communicate with - * apps to log into captive portals. - * arg1 = Token to ignore old messages. - */ - private static final int CMD_CAPTIVE_PORTAL_REEVALUATE = BASE + 6; - - /** * Message to self indicating it's time to evaluate a network's connectivity. * arg1 = Token to ignore old messages. */ - private static final int CMD_REEVALUATE = BASE + 7; + private static final int CMD_REEVALUATE = BASE + 6; /** * Message to self indicating network evaluation is complete. * arg1 = Token to ignore old messages. * arg2 = HTTP response code of network evaluation. */ - private static final int EVENT_REEVALUATION_COMPLETE = BASE + 8; + private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7; /** * Inform NetworkMonitor that the network has disconnected. */ - public static final int CMD_NETWORK_DISCONNECTED = BASE + 9; + public static final int CMD_NETWORK_DISCONNECTED = BASE + 8; /** * Force evaluation even if it has succeeded in the past. */ - public static final int CMD_FORCE_REEVALUATION = BASE + 10; + public static final int CMD_FORCE_REEVALUATION = BASE + 9; + + /** + * Message to self indicating captive portal login is complete. + * arg1 = Token to ignore old messages. + * arg2 = 1 if we should use this network, 0 otherwise. + */ + private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10; + + /** + * Message to self indicating user desires to log into captive portal. + * arg1 = Token to ignore old messages. + */ + private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11; + + /** + * Request ConnectivityService display provisioning notification. + * arg1 = Whether to make the notification visible. + * obj = Intent to be launched when notification selected by user. + * replyTo = NetworkAgentInfo.messenger so ConnectivityService can identify sender. + */ + public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12; + + /** + * Message to self indicating sign-in app bypassed captive portal. + */ + private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13; + + /** + * Message to self indicating no sign-in app responded. + */ + private static final int EVENT_NO_APP_RESPONSE = BASE + 14; + + /** + * Message to self indicating sign-in app indicates sign-in is not possible. + */ + private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; // Default to 30s linger time-out. @@ -123,16 +174,17 @@ public class NetworkMonitor extends StateMachine { private final int mLingerDelayMs; private int mLingerToken = 0; - private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 5000; - private int mCaptivePortalReevaluateToken = 0; - // Negative values disable reevaluation. private static final String REEVALUATE_DELAY_PROPERTY = "persist.netmon.reeval_delay"; // Default to 5s reevaluation delay. private static final int DEFAULT_REEVALUATE_DELAY_MS = 5000; + private static final int MAX_RETRIES = 10; private final int mReevaluateDelayMs; private int mReevaluateToken = 0; + private int mCaptivePortalLoggedInToken = 0; + private int mUserPromptedToken = 0; + private final Context mContext; private final Handler mConnectivityServiceHandler; private final NetworkAgentInfo mNetworkAgentInfo; @@ -144,6 +196,9 @@ public class NetworkMonitor extends StateMachine { private State mOfflineState = new OfflineState(); private State mValidatedState = new ValidatedState(); private State mEvaluatingState = new EvaluatingState(); + private State mUninteractiveAppsPromptedState = new UninteractiveAppsPromptedState(); + private State mUserPromptedState = new UserPromptedState(); + private State mInteractiveAppsPromptedState = new InteractiveAppsPromptedState(); private State mCaptivePortalState = new CaptivePortalState(); private State mLingeringState = new LingeringState(); @@ -159,6 +214,9 @@ public class NetworkMonitor extends StateMachine { addState(mOfflineState, mDefaultState); addState(mValidatedState, mDefaultState); addState(mEvaluatingState, mDefaultState); + addState(mUninteractiveAppsPromptedState, mDefaultState); + addState(mUserPromptedState, mDefaultState); + addState(mInteractiveAppsPromptedState, mDefaultState); addState(mCaptivePortalState, mDefaultState); addState(mLingeringState, mDefaultState); setInitialState(mOfflineState); @@ -171,9 +229,8 @@ public class NetworkMonitor extends StateMachine { mReevaluateDelayMs = SystemProperties.getInt(REEVALUATE_DELAY_PROPERTY, DEFAULT_REEVALUATE_DELAY_MS); - // TODO: Enable this when we're ready. - // mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), - // Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; + mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; start(); } @@ -237,6 +294,8 @@ public class NetworkMonitor extends StateMachine { } private class EvaluatingState extends State { + private int mRetries; + private class EvaluateInternetConnectivity extends Thread { private int mToken; EvaluateInternetConnectivity(int token) { @@ -249,6 +308,7 @@ public class NetworkMonitor extends StateMachine { @Override public void enter() { + mRetries = 0; sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); } @@ -259,8 +319,11 @@ public class NetworkMonitor extends StateMachine { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) break; + if (mNetworkAgentInfo.isVPN()) { + transitionTo(mValidatedState); + } // If network provides no internet connectivity adjust evaluation. - if (mNetworkAgentInfo.networkCapabilities.hasCapability( + if (!mNetworkAgentInfo.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET)) { // TODO: Try to verify something works. Do all gateways respond to pings? transitionTo(mValidatedState); @@ -276,12 +339,12 @@ public class NetworkMonitor extends StateMachine { if (httpResponseCode == 204) { transitionTo(mValidatedState); } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { - transitionTo(mCaptivePortalState); - } else { - if (mReevaluateDelayMs >= 0) { - Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); - sendMessageDelayed(msg, mReevaluateDelayMs); - } + transitionTo(mUninteractiveAppsPromptedState); + } else if (++mRetries > MAX_RETRIES) { + transitionTo(mOfflineState); + } else if (mReevaluateDelayMs >= 0) { + Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); + sendMessageDelayed(msg, mReevaluateDelayMs); } break; default: @@ -291,30 +354,223 @@ public class NetworkMonitor extends StateMachine { } } - // TODO: Until we add an intent from the app handling captive portal - // login we'll just re-evaluate after a delay. + private class AppRespondedBroadcastReceiver extends BroadcastReceiver { + private static final int CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE = 0; + private boolean mCanceled; + AppRespondedBroadcastReceiver() { + mCanceled = false; + } + public void send(String action) { + Intent intent = new Intent(action); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, this, getHandler(), + CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE, null, null); + } + public void cancel() { + mCanceled = true; + } + @Override + public void onReceive(Context context, Intent intent) { + if (!mCanceled) { + cancel(); + switch (getResultCode()) { + case ConnectivityManager.CAPTIVE_PORTAL_SIGNED_IN: + sendMessage(EVENT_APP_BYPASSED_CAPTIVE_PORTAL); + break; + case ConnectivityManager.CAPTIVE_PORTAL_DISCONNECT: + sendMessage(EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE); + break; + // NOTE: This case label makes compiler enforce no overlap between result codes. + case CAPTIVE_PORTAL_UNINITIALIZED_RETURN_CODE: + default: + sendMessage(EVENT_NO_APP_RESPONSE); + break; + } + } + } + } + + private class UninteractiveAppsPromptedState extends State { + private AppRespondedBroadcastReceiver mReceiver; + @Override + public void enter() { + mReceiver = new AppRespondedBroadcastReceiver(); + mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_DETECTED); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: + transitionTo(mValidatedState); + break; + case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: + transitionTo(mOfflineState); + break; + case EVENT_NO_APP_RESPONSE: + transitionTo(mUserPromptedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + public void exit() { + mReceiver.cancel(); + } + } + + private class UserPromptedState extends State { + private class UserRespondedBroadcastReceiver extends BroadcastReceiver { + private final int mToken; + UserRespondedBroadcastReceiver(int token) { + mToken = token; + } + @Override + public void onReceive(Context context, Intent intent) { + if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == + mNetworkAgentInfo.network.netId) { + sendMessage(obtainMessage(CMD_USER_WANTS_SIGN_IN, mToken)); + } + } + } + + private UserRespondedBroadcastReceiver mUserRespondedBroadcastReceiver; + + @Override + public void enter() { + // Wait for user to select sign-in notifcation. + mUserRespondedBroadcastReceiver = new UserRespondedBroadcastReceiver( + ++mUserPromptedToken); + IntentFilter filter = new IntentFilter(ACTION_SIGN_IN_REQUESTED); + mContext.registerReceiver(mUserRespondedBroadcastReceiver, filter); + // Initiate notification to sign-in. + Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId)); + Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 0, + PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + message.replyTo = mNetworkAgentInfo.messenger; + mConnectivityServiceHandler.sendMessage(message); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_USER_WANTS_SIGN_IN: + if (message.arg1 != mUserPromptedToken) + break; + transitionTo(mInteractiveAppsPromptedState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + @Override + public void exit() { + Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 0, null); + message.replyTo = mNetworkAgentInfo.messenger; + mConnectivityServiceHandler.sendMessage(message); + mContext.unregisterReceiver(mUserRespondedBroadcastReceiver); + mUserRespondedBroadcastReceiver = null; + } + } + + private class InteractiveAppsPromptedState extends State { + private AppRespondedBroadcastReceiver mReceiver; + @Override + public void enter() { + mReceiver = new AppRespondedBroadcastReceiver(); + mReceiver.send(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: + transitionTo(mValidatedState); + break; + case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: + transitionTo(mOfflineState); + break; + case EVENT_NO_APP_RESPONSE: + transitionTo(mCaptivePortalState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + public void exit() { + mReceiver.cancel(); + } + } + private class CaptivePortalState extends State { + private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver { + private final int mToken; + + CaptivePortalLoggedInBroadcastReceiver(int token) { + mToken = token; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == + mNetworkAgentInfo.network.netId) { + sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_LOGGED_IN, mToken, + Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT)))); + } + } + } + + private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver; + @Override public void enter() { - Message message = obtainMessage(CMD_CAPTIVE_PORTAL_REEVALUATE, - ++mCaptivePortalReevaluateToken, 0); - sendMessageDelayed(message, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId)); + intent.setType("text/plain"); + intent.setComponent(new ComponentName("com.android.captiveportallogin", + "com.android.captiveportallogin.CaptivePortalLoginActivity")); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + + // Wait for result. + mCaptivePortalLoggedInBroadcastReceiver = new CaptivePortalLoggedInBroadcastReceiver( + ++mCaptivePortalLoggedInToken); + IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN); + mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter); + // Initiate app to log in. + mContext.startActivityAsUser(intent, UserHandle.CURRENT); } @Override public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString()); switch (message.what) { - case CMD_CAPTIVE_PORTAL_REEVALUATE: - if (message.arg1 != mCaptivePortalReevaluateToken) + case CMD_CAPTIVE_PORTAL_LOGGED_IN: + if (message.arg1 != mCaptivePortalLoggedInToken) break; - transitionTo(mEvaluatingState); + if (message.arg2 == 0) { + // TODO: Should teardown network. + transitionTo(mOfflineState); + } else { + transitionTo(mValidatedState); + } break; default: return NOT_HANDLED; } return HANDLED; } + + @Override + public void exit() { + mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver); + mCaptivePortalLoggedInBroadcastReceiver = null; + } } private class LingeringState extends State { @@ -353,10 +609,12 @@ public class NetworkMonitor extends StateMachine { if (!mIsCaptivePortalCheckEnabled) return 204; String urlString = "http://" + mServer + "/generate_204"; - if (DBG) log("Checking " + urlString); + if (DBG) { + log("Checking " + urlString + " on " + mNetworkAgentInfo.networkInfo.getExtraInfo()); + } HttpURLConnection urlConnection = null; Socket socket = null; - int httpResponseCode = 500; + int httpResponseCode = 599; try { URL url = new URL(urlString); if (false) { @@ -369,25 +627,65 @@ public class NetworkMonitor extends StateMachine { urlConnection.getInputStream(); httpResponseCode = urlConnection.getResponseCode(); } else { - socket = new Socket(); - // TODO: setNetworkForSocket(socket, mNetworkAgentInfo.network.netId); - InetSocketAddress address = new InetSocketAddress(url.getHost(), 80); - // TODO: address = new InetSocketAddress( - // getByNameOnNetwork(mNetworkAgentInfo.network, url.getHost()), 80); - socket.connect(address); + socket = mNetworkAgentInfo.network.getSocketFactory().createSocket(); + socket.setSoTimeout(SOCKET_TIMEOUT_MS); + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = mNetworkAgentInfo.network.getAllByName(url.getHost()); + // Try all addresses. + for (int i = 0; i < hostAddresses.length; i++) { + if (DBG) log("Connecting to " + hostAddresses[i]); + try { + socket.connect(new InetSocketAddress(hostAddresses[i], + url.getDefaultPort()), SOCKET_TIMEOUT_MS); + break; + } catch (IOException e) { + // Ignore exceptions on all but the last. + if (i == (hostAddresses.length - 1)) throw e; + } + } + if (DBG) log("Requesting " + url.getFile()); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream()); - writer.write("GET " + url.getFile() + " HTTP/1.1\r\n\n"); + writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() + + "\r\nConnection: close\r\n\r\n"); writer.flush(); String response = reader.readLine(); - if (response.startsWith("HTTP/1.1 ")) { + if (DBG) log("Received \"" + response + "\""); + if (response != null && (response.startsWith("HTTP/1.1 ") || + // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive + // portal. The only example of this seen so far was a captive portal. For + // the time being go with prior behavior of assuming it's not a captive + // portal. If it is considered a captive portal, a different sign-in URL + // is needed (i.e. can't browse a 204). This could be the result of an HTTP + // proxy server. + response.startsWith("HTTP/1.0 "))) { + // NOTE: We may want to consider an "200" response with "Content-length=0" to + // not be a captive portal. This could be the result of an HTTP proxy server. + // See b/9972012. httpResponseCode = Integer.parseInt(response.substring(9, 12)); + } else { + // A response was received but not understood. The fact that a + // response was sent indicates there's some kind of responsive network + // out there so put up the notification to the user to log into the network + // so the user can have the final say as to whether the network is useful. + httpResponseCode = 399; + while (DBG && response != null && !response.isEmpty()) { + try { + response = reader.readLine(); + } catch (IOException e) { + break; + } + log("Received \"" + response + "\""); + } } } if (DBG) log("isCaptivePortal: ret=" + httpResponseCode); } catch (IOException e) { if (DBG) log("Probably not a portal: exception " + e); + if (httpResponseCode == 599) { + // TODO: Ping gateway and DNS server and log results. + } } finally { if (urlConnection != null) { urlConnection.disconnect(); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index ec3389b..01a2fc2 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -109,13 +109,14 @@ public class Tethering extends BaseNetworkObserver { // Wifi is 192.168.43.1 and 255.255.255.0 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 // with 255.255.255.0 + // P2P is 192.168.49.1 and 255.255.255.0 private String[] mDhcpRange; private static final String[] DHCP_DEFAULT_RANGE = { "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", - "192.168.48.2", "192.168.48.254", + "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254", }; private String[] mDefaultDnsServers; @@ -701,6 +702,10 @@ public class Tethering extends BaseNetworkObserver { return retVal; } + public String[] getTetheredDhcpRanges() { + return mDhcpRange; + } + public String[] getErroredIfaces() { ArrayList<String> list = new ArrayList<String>(); synchronized (mPublicSync) { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index df12995..d15254b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Bitmap; @@ -43,14 +44,18 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.RouteInfo; -import android.net.NetworkInfo.DetailedState; +import android.net.UidRange; import android.os.Binder; import android.os.FileUtils; import android.os.IBinder; import android.os.INetworkManagementService; +import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -69,7 +74,6 @@ import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService.VpnCallback; import com.android.server.net.BaseNetworkObserver; import java.io.File; @@ -78,7 +82,9 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.Inet4Address; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -86,16 +92,18 @@ import libcore.io.IoUtils; /** * @hide */ -public class Vpn extends BaseNetworkStateTracker { +public class Vpn { + private static final String NETWORKTYPE = "VPN"; private static final String TAG = "Vpn"; private static final boolean LOGD = true; - + // TODO: create separate trackers for each unique VPN to support // automated reconnection - private final VpnCallback mCallback; - - private String mPackage = VpnConfig.LEGACY_VPN; + private Context mContext; + private NetworkInfo mNetworkInfo; + private String mPackage; + private int mOwnerUID; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; @@ -103,22 +111,29 @@ public class Vpn extends BaseNetworkStateTracker { private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; + private final INetworkManagementService mNetd; private VpnConfig mConfig; + private NetworkAgent mNetworkAgent; + private final Looper mLooper; + private final NetworkCapabilities mNetworkCapabilities; /* list of users using this VPN. */ @GuardedBy("this") - private SparseBooleanArray mVpnUsers = null; + private List<UidRange> mVpnUsers = null; private BroadcastReceiver mUserIntentReceiver = null; private final int mUserId; - public Vpn(Context context, VpnCallback callback, INetworkManagementService netService, + public Vpn(Looper looper, Context context, INetworkManagementService netService, IConnectivityManager connService, int userId) { - super(ConnectivityManager.TYPE_VPN); mContext = context; - mCallback = callback; + mNetd = netService; mConnService = connService; mUserId = userId; + mLooper = looper; + + mPackage = VpnConfig.LEGACY_VPN; + mOwnerUID = getAppUid(mPackage); try { netService.registerObserver(mObserver); @@ -149,6 +164,12 @@ public class Vpn extends BaseNetworkStateTracker { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); } + + mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, ""); + // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332 + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); } /** @@ -168,35 +189,15 @@ public class Vpn extends BaseNetworkStateTracker { mEnableTeardown = enableTeardown; } - @Override - protected void startMonitoringInternal() { - // Ignored; events are sent through callbacks for now - } - - @Override - public boolean teardown() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public boolean reconnect() { - // TODO: finish migration to unique tracker for each VPN - throw new UnsupportedOperationException(); - } - - @Override - public String getTcpBufferSizesPropName() { - return PROP_TCP_BUFFER_UNKNOWN; - } - /** * Update current state, dispaching event to listeners. */ private void updateState(DetailedState detailedState, String reason) { if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason); mNetworkInfo.setDetailedState(detailedState, reason, null); - mCallback.onStateChanged(new NetworkInfo(mNetworkInfo)); + if (mNetworkAgent != null) { + mNetworkAgent.sendNetworkInfo(mNetworkInfo); + } } /** @@ -234,22 +235,10 @@ public class Vpn extends BaseNetworkStateTracker { // Reset the interface and hide the notification. if (mInterface != null) { - final long token = Binder.clearCallingIdentity(); - try { - mCallback.restore(); - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - - mCallback.clearMarkedForwarding(mInterface); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + agentDisconnect(); jniReset(mInterface); mInterface = null; mVpnUsers = null; @@ -270,34 +259,125 @@ public class Vpn extends BaseNetworkStateTracker { mLegacyVpnRunner = null; } + long token = Binder.clearCallingIdentity(); + try { + mNetd.denyProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } + Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + mOwnerUID = getAppUid(newPackage); + token = Binder.clearCallingIdentity(); + try { + mNetd.allowProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); + } finally { + Binder.restoreCallingIdentity(token); + } mConfig = null; updateState(DetailedState.IDLE, "prepare"); return true; } - /** - * Protect a socket from VPN rules by binding it to the main routing table. - * The socket is NOT closed by this method. - * - * @param socket The socket to be bound. - */ - public void protect(ParcelFileDescriptor socket) throws Exception { - + private int getAppUid(String app) { + if (app == VpnConfig.LEGACY_VPN) { + return Process.myUid(); + } PackageManager pm = mContext.getPackageManager(); - int appUid = pm.getPackageUid(mPackage, mUserId); - if (Binder.getCallingUid() != appUid) { - throw new SecurityException("Unauthorized Caller"); + int result; + try { + result = pm.getPackageUid(app, mUserId); + } catch (NameNotFoundException e) { + result = -1; } - // protect the socket from routing rules - final long token = Binder.clearCallingIdentity(); + return result; + } + + public NetworkInfo getNetworkInfo() { + return mNetworkInfo; + } + + private void agentConnect() { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(mInterface); + boolean hasDefaultRoute = false; + for (RouteInfo route : mConfig.routes) { + lp.addRoute(route); + if (route.isDefaultRoute()) hasDefaultRoute = true; + } + if (hasDefaultRoute) { + mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } else { + mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + if (mConfig.dnsServers != null) { + for (String dnsServer : mConfig.dnsServers) { + lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer)); + } + } + // Concatenate search domains into a string. + StringBuilder buffer = new StringBuilder(); + if (mConfig.searchDomains != null) { + for (String domain : mConfig.searchDomains) { + buffer.append(domain).append(' '); + } + } + lp.setDomains(buffer.toString().trim()); + mNetworkInfo.setIsAvailable(true); + mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); + long token = Binder.clearCallingIdentity(); try { - mCallback.protect(socket); + mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, + mNetworkInfo, mNetworkCapabilities, lp, 0) { + public void unwanted() { + // We are user controlled, not driven by NetworkRequest. + }; + }; } finally { Binder.restoreCallingIdentity(token); } + addVpnUserLocked(mUserId); + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + token = Binder.clearCallingIdentity(); + List<UserInfo> users; + try { + users = UserManager.get(mContext).getUsers(); + } finally { + Binder.restoreCallingIdentity(token); + } + for (UserInfo user : users) { + if (user.isRestricted()) { + addVpnUserLocked(user.id); + } + } + } + mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()])); + } + + private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) { + networkInfo.setIsAvailable(false); + networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null); + if (networkAgent != null) { + networkAgent.sendNetworkInfo(networkInfo); + } + } + + private void agentDisconnect(NetworkAgent networkAgent) { + NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo); + agentDisconnect(networkInfo, networkAgent); + } + private void agentDisconnect() { + if (mNetworkInfo.isConnected()) { + agentDisconnect(mNetworkInfo, mNetworkAgent); + mNetworkAgent = null; + } } /** @@ -311,14 +391,7 @@ public class Vpn extends BaseNetworkStateTracker { public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check if the caller is already prepared. UserManager mgr = UserManager.get(mContext); - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = null; - try { - app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); - if (Binder.getCallingUid() != app.uid) { - return null; - } - } catch (Exception e) { + if (Binder.getCallingUid() != mOwnerUID) { return null; } // Check if the service is properly declared. @@ -350,7 +423,9 @@ public class Vpn extends BaseNetworkStateTracker { VpnConfig oldConfig = mConfig; String oldInterface = mInterface; Connection oldConnection = mConnection; - SparseBooleanArray oldUsers = mVpnUsers; + NetworkAgent oldNetworkAgent = mNetworkAgent; + mNetworkAgent = null; + List<UidRange> oldUsers = mVpnUsers; // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); @@ -382,67 +457,27 @@ public class Vpn extends BaseNetworkStateTracker { mConfig = config; // Set up forwarding and DNS rules. - mVpnUsers = new SparseBooleanArray(); - token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mInterface); - mCallback.setRoutes(mInterface, config.routes); - mCallback.override(mInterface, config.dnsServers, config.searchDomains); - addVpnUserLocked(mUserId); - // If we are owner assign all Restricted Users to this VPN - if (mUserId == UserHandle.USER_OWNER) { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); - } - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + agentConnect(); if (oldConnection != null) { mContext.unbindService(oldConnection); } + // Remove the old tun's user forwarding rules + // The new tun's user rules have already been added so they will take over + // as rules are deleted. This prevents data leakage as the rules are moved over. + agentDisconnect(oldNetworkAgent); if (oldInterface != null && !oldInterface.equals(interfaze)) { - // Remove the old tun's user forwarding rules - // The new tun's user rules have already been added so they will take over - // as rules are deleted. This prevents data leakage as the rules are moved over. - token = Binder.clearCallingIdentity(); - try { - final int size = oldUsers.size(); - final boolean forwardDns = (oldConfig.dnsServers != null && - oldConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = oldUsers.keyAt(i); - mCallback.clearUserForwarding(oldInterface, user, forwardDns); - } - mCallback.clearMarkedForwarding(oldInterface); - } finally { - Binder.restoreCallingIdentity(token); - } jniReset(oldInterface); } } catch (RuntimeException e) { - updateState(DetailedState.FAILED, "establish"); IoUtils.closeQuietly(tun); - // make sure marked forwarding is cleared if it was set - token = Binder.clearCallingIdentity(); - try { - mCallback.clearMarkedForwarding(mInterface); - } catch (Exception ingored) { - // ignored - } finally { - Binder.restoreCallingIdentity(token); - } + agentDisconnect(); // restore old state mConfig = oldConfig; mConnection = oldConnection; mVpnUsers = oldUsers; + mNetworkAgent = oldNetworkAgent; mInterface = oldInterface; throw e; } @@ -469,29 +504,27 @@ public class Vpn extends BaseNetworkStateTracker { return mVpnUsers != null; } + // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. private void addVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - // add the user - mCallback.addUserForwarding(mInterface, user, forwardDns); - mVpnUsers.put(user, true); + mVpnUsers.add(UidRange.createForUser(user)); // show the notification if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { // Load everything for the user's notification PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = null; + final long token = Binder.clearCallingIdentity(); try { app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); } catch (RemoteException e) { throw new IllegalStateException("Invalid application"); + } finally { + Binder.restoreCallingIdentity(token); } String label = app.loadLabel(pm).toString(); // Load the icon and convert it into a bitmap. @@ -515,15 +548,14 @@ public class Vpn extends BaseNetworkStateTracker { } private void removeVpnUserLocked(int user) { - enforceControlPermission(); - if (!isRunningLocked()) { throw new IllegalStateException("VPN is not active"); } - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - mVpnUsers.delete(user); + UidRange uidRange = UidRange.createForUser(user); + if (mNetworkAgent != null) { + mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); + } + mVpnUsers.remove(uidRange); hideNotification(user); } @@ -535,6 +567,10 @@ public class Vpn extends BaseNetworkStateTracker { if (user.isRestricted()) { try { addVpnUserLocked(userId); + if (mNetworkAgent != null) { + UidRange uidRange = UidRange.createForUser(userId); + mNetworkAgent.addUidRanges(new UidRange[] { uidRange }); + } } catch (Exception e) { Log.wtf(TAG, "Failed to add restricted user to owner", e); } @@ -588,28 +624,15 @@ public class Vpn extends BaseNetworkStateTracker { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - final long token = Binder.clearCallingIdentity(); - try { - final int size = mVpnUsers.size(); - final boolean forwardDns = (mConfig.dnsServers != null && - mConfig.dnsServers.size() != 0); - for (int i = 0; i < size; i++) { - int user = mVpnUsers.keyAt(i); - mCallback.clearUserForwarding(mInterface, user, forwardDns); - hideNotification(user); - } - mVpnUsers = null; - mCallback.clearMarkedForwarding(mInterface); - - mCallback.restore(); - } finally { - Binder.restoreCallingIdentity(token); + for (UidRange uidRange : mVpnUsers) { + hideNotification(uidRange.getStartUser()); } + mVpnUsers = null; mInterface = null; if (mConnection != null) { mContext.unbindService(mConnection); mConnection = null; - updateState(DetailedState.DISCONNECTED, "interfaceRemoved"); + agentDisconnect(); } else if (mLegacyVpnRunner != null) { mLegacyVpnRunner.exit(); mLegacyVpnRunner = null; @@ -658,27 +681,32 @@ public class Vpn extends BaseNetworkStateTracker { private void showNotification(String label, Bitmap icon, int user) { if (!mEnableNotif) return; - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - String title = (label == null) ? mContext.getString(R.string.vpn_title) : - mContext.getString(R.string.vpn_title_long, label); - String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, mConfig.session); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(icon) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(mStatusIntent) - .setDefaults(0) - .setOngoing(true) - .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + + NotificationManager nm = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + + if (nm != null) { + String title = (label == null) ? mContext.getString(R.string.vpn_title) : + mContext.getString(R.string.vpn_title_long, label); + String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : + mContext.getString(R.string.vpn_text_long, mConfig.session); + + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.vpn_connected) + .setLargeIcon(icon) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(mStatusIntent) + .setDefaults(0) + .setOngoing(true) + .build(); + nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); + } + } finally { + Binder.restoreCallingIdentity(token); } } @@ -690,14 +718,18 @@ public class Vpn extends BaseNetworkStateTracker { mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + final long token = Binder.clearCallingIdentity(); + try { + nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); + } finally { + Binder.restoreCallingIdentity(token); + } } } private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); - private native int jniSetRoutes(String interfaze, String routes); private native void jniReset(String interfaze); private native int jniCheck(String interfaze); @@ -959,7 +991,7 @@ public class Vpn extends BaseNetworkStateTracker { for (LocalSocket socket : mSockets) { IoUtils.closeQuietly(socket); } - updateState(DetailedState.DISCONNECTED, "exit"); + agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); } catch (IllegalArgumentException e) {} @@ -1018,7 +1050,7 @@ public class Vpn extends BaseNetworkStateTracker { restart = restart || (arguments != null); } if (!restart) { - updateState(DetailedState.DISCONNECTED, "execute"); + agentDisconnect(); return; } updateState(DetailedState.CONNECTING, "execute"); @@ -1129,15 +1161,6 @@ public class Vpn extends BaseNetworkStateTracker { } } - // Set the routes. - long token = Binder.clearCallingIdentity(); - try { - mCallback.setMarkedForwarding(mConfig.interfaze); - mCallback.setRoutes(mConfig.interfaze, mConfig.routes); - } finally { - Binder.restoreCallingIdentity(token); - } - // Here is the last step and it must be done synchronously. synchronized (Vpn.this) { // Set the start time @@ -1153,44 +1176,14 @@ public class Vpn extends BaseNetworkStateTracker { // Now INetworkManagementEventObserver is watching our back. mInterface = mConfig.interfaze; - mVpnUsers = new SparseBooleanArray(); - - token = Binder.clearCallingIdentity(); - try { - mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains); - addVpnUserLocked(mUserId); - } finally { - Binder.restoreCallingIdentity(token); - } + mVpnUsers = new ArrayList<UidRange>(); + + agentConnect(); - // Assign all restircted users to this VPN - // (Legacy VPNs are Owner only) - UserManager mgr = UserManager.get(mContext); - token = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id - + " to owner's VPN"); - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } Log.i(TAG, "Connected!"); - updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); - // make sure the routing is cleared - try { - mCallback.clearMarkedForwarding(mConfig.interfaze); - } catch (Exception ignored) { - } exit(); } finally { // Kill the daemons if they fail to stop. @@ -1202,7 +1195,7 @@ public class Vpn extends BaseNetworkStateTracker { // Do not leave an unstable state. if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { - updateState(DetailedState.FAILED, "execute"); + agentDisconnect(); } } } @@ -1232,7 +1225,7 @@ public class Vpn extends BaseNetworkStateTracker { SystemService.stop(daemon); } - updateState(DetailedState.DISCONNECTED, "babysit"); + agentDisconnect(); } } } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 0e0664f..1cdf44c 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -154,6 +154,9 @@ final class Constants { static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + static final int TRUE = 1; + static final int FALSE = 0; + // Constants related to operands of HDMI CEC commands. // Refer to CEC Table 29 in HDMI Spec v1.4b. // [Abort Reason] diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 3c699bc..a75b485 100644 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -51,8 +51,6 @@ final class DeviceDiscoveryAction extends FeatureAction { // State in which the action is waiting for gathering vendor id of non-local devices. private static final int STATE_WAITING_FOR_VENDOR_ID = 4; - private static final int DEVICE_POLLING_RETRY = 1; - /** * Interface used to report result of device discovery. */ @@ -118,7 +116,7 @@ final class DeviceDiscoveryAction extends FeatureAction { startPhysicalAddressStage(); } }, Constants.POLL_ITERATION_REVERSE_ORDER - | Constants.POLL_STRATEGY_REMOTES_DEVICES, DEVICE_POLLING_RETRY); + | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY); return true; } @@ -154,7 +152,7 @@ final class DeviceDiscoveryAction extends FeatureAction { return; } sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address)); - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } private void startOsdNameStage() { @@ -177,7 +175,7 @@ final class DeviceDiscoveryAction extends FeatureAction { return; } sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address)); - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } private void startVendorIdStage() { @@ -202,7 +200,7 @@ final class DeviceDiscoveryAction extends FeatureAction { } sendCommand( HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address)); - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } private boolean mayProcessMessageIfCached(int address, int opcode) { diff --git a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java index 87c8d92..1106810 100644 --- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java +++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java @@ -59,7 +59,7 @@ final class DevicePowerStatusAction extends FeatureAction { boolean start() { queryDevicePowerStatus(); mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; - addTimer(mState, FeatureAction.TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index 9767d21..d4fffcf 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -111,7 +111,7 @@ final class DeviceSelectAction extends FeatureAction { } }); mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } @Override diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java index f8ebed2..7d15f4c 100644 --- a/services/core/java/com/android/server/hdmi/FeatureAction.java +++ b/services/core/java/com/android/server/hdmi/FeatureAction.java @@ -47,10 +47,6 @@ abstract class FeatureAction { // Timer handler message used for timeout event protected static final int MSG_TIMEOUT = 100; - // Default timeout for the incoming command to arrive in response to a request. - // TODO: Consider reading this value from configuration to allow customization. - protected static final int TIMEOUT_MS = 2000; - // Default state used in common by all the feature actions. protected static final int STATE_NONE = 0; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index a5a502a..14d9b75 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -77,8 +77,6 @@ final class HdmiCecController { private static final int NUM_LOGICAL_ADDRESS = 16; - private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3; - // Predicate for whether the given logical address is remote device's one or not. private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() { @Override @@ -198,8 +196,7 @@ final class HdmiCecController { int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS; if (curAddress != Constants.ADDR_UNREGISTERED && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) { - if (!sendPollMessage(curAddress, curAddress, - RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) { + if (!sendPollMessage(curAddress, curAddress, HdmiConfig.ADDRESS_ALLOCATION_RETRY)) { logicalAddress = curAddress; break; } @@ -276,6 +273,12 @@ final class HdmiCecController { nativeClearLogicalAddress(mNativePtr); } + @ServiceThreadOnly + void clearLocalDevices() { + assertRunOnServiceThread(); + mLocalDevices.clear(); + } + /** * Return the physical address of the device. * @@ -530,16 +533,25 @@ final class HdmiCecController { @Override public void run() { byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams()); - final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(), - cecMessage.getDestination(), body); - if (error != Constants.SEND_RESULT_SUCCESS) { + int i = 0; + int errorCode = Constants.SEND_RESULT_SUCCESS; + do { + errorCode = nativeSendCecCommand(mNativePtr, cecMessage.getSource(), + cecMessage.getDestination(), body); + if (errorCode == Constants.SEND_RESULT_SUCCESS) { + break; + } + } while (i++ < HdmiConfig.RETRANSMISSION_COUNT); + + final int finalError = errorCode; + if (finalError != Constants.SEND_RESULT_SUCCESS) { Slog.w(TAG, "Failed to send " + cecMessage); } if (callback != null) { runOnServiceThread(new Runnable() { @Override public void run() { - callback.onSendCompleted(error); + callback.onSendCompleted(finalError); } }); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 0cae459..46c53a8 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -17,7 +17,9 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiCecDeviceInfo; +import android.os.Handler; import android.os.Looper; +import android.os.Message; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -35,6 +37,11 @@ import java.util.List; abstract class HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevice"; + private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; + // Timeout in millisecond for device clean up (5s). + // Normal actions timeout is 2s but some of them would have several sequence of timeout. + private static final int DEVICE_CLEANUP_TIMEOUT = 5000; + protected final HdmiControlService mService; protected final int mDeviceType; protected int mAddress; @@ -58,6 +65,27 @@ abstract class HdmiCecLocalDevice { // Note that access to this collection should happen in service thread. private final LinkedList<FeatureAction> mActions = new LinkedList<>(); + private Handler mHandler = new Handler () { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DISABLE_DEVICE_TIMEOUT: + handleDisableDeviceTimeout(); + break; + } + } + }; + + /** + * A callback interface to get notified when all pending action is cleared. + * It can be called when timeout happened. + */ + interface PendingActionClearedCallback { + void onCleared(HdmiCecLocalDevice device); + } + + protected PendingActionClearedCallback mPendingActionClearedCallback; + protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { mService = service; mDeviceType = deviceType; @@ -87,7 +115,7 @@ abstract class HdmiCecLocalDevice { /** * Called once a logical address of the local device is allocated. */ - protected abstract void onAddressAllocated(int logicalAddress); + protected abstract void onAddressAllocated(int logicalAddress, boolean fromBootup); /** * Dispatch incoming message. @@ -366,10 +394,10 @@ abstract class HdmiCecLocalDevice { } @ServiceThreadOnly - final void handleAddressAllocated(int logicalAddress) { + final void handleAddressAllocated(int logicalAddress, boolean fromBootup) { assertRunOnServiceThread(); mAddress = mPreferredAddress = logicalAddress; - onAddressAllocated(logicalAddress); + onAddressAllocated(logicalAddress, fromBootup); } @ServiceThreadOnly @@ -482,10 +510,11 @@ abstract class HdmiCecLocalDevice { } protected void checkIfPendingActionsCleared() { - if (mActions.isEmpty()) { - mService.onPendingActionsCleared(); + if (mActions.isEmpty() && mPendingActionClearedCallback != null) { + mPendingActionClearedCallback.onCleared(this); } } + protected void assertRunOnServiceThread() { if (Looper.myLooper() != mService.getServiceLooper()) { throw new IllegalStateException("Should run on service thread."); @@ -578,21 +607,56 @@ abstract class HdmiCecLocalDevice { } /** - * Called when the system started transition to standby mode. + * Called when the system goes to standby mode. * * @param initiatedByCec true if this power sequence is initiated - * by the reception the CEC messages like <StandBy> + * by the reception the CEC messages like <Standby> */ - protected void onTransitionToStandby(boolean initiatedByCec) { - // If there are no outstanding actions, we'll go to STANDBY state. - checkIfPendingActionsCleared(); + protected void onStandby(boolean initiatedByCec) {} + + /** + * Disable device. {@code callback} is used to get notified when all pending + * actions are completed or timeout is issued. + * + * @param initiatedByCec true if this sequence is initiated + * by the reception the CEC messages like <Standby> + * @param origialCallback callback interface to get notified when all pending actions are + * cleared + */ + protected void disableDevice(boolean initiatedByCec, + final PendingActionClearedCallback origialCallback) { + mPendingActionClearedCallback = new PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); + origialCallback.onCleared(device); + } + }; + mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), + DEVICE_CLEANUP_TIMEOUT); + } + + @ServiceThreadOnly + private void handleDisableDeviceTimeout() { + assertRunOnServiceThread(); + + // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. + // onCleard will be called at the last action's finish method. + Iterator<FeatureAction> iter = mActions.iterator(); + while (iter.hasNext()) { + FeatureAction action = iter.next(); + action.finish(); + iter.remove(); + } } /** - * Called when the system goes to standby mode. + * Send a key event to other device. * - * @param initiatedByCec true if this power sequence is initiated - * by the reception the CEC messages like <StandBy> + * @param keyCode key code defined in {@link android.view.KeyEvent} + * @param isPressed {@code true} for key down event */ - protected void onStandBy(boolean initiatedByCec) {} + protected void sendKeyEvent(int keyCode, boolean isPressed) { + Slog.w(TAG, "sendKeyEvent not implemented"); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 7ab2e8c..06907ce 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -38,7 +38,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onAddressAllocated(int logicalAddress) { + protected void onAddressAllocated(int logicalAddress, boolean fromBootup) { assertRunOnServiceThread(); mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); @@ -141,7 +141,9 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onTransitionToStandby(boolean initiatedByCec) { + protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { + super.disableDevice(initiatedByCec, callback); + assertRunOnServiceThread(); if (!initiatedByCec && mIsActiveSource) { mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 4185d25..c1a9e2f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -20,9 +20,14 @@ import android.content.Intent; import android.hardware.hdmi.HdmiCecDeviceInfo; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiControlCallback; +import android.media.AudioManager; +import android.media.AudioManager.OnAudioPortUpdateListener; +import android.media.AudioPatch; +import android.media.AudioPort; import android.media.AudioSystem; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings.Global; import android.util.Slog; import android.util.SparseArray; @@ -86,21 +91,45 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiCecDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; - // TODO: load system audio mode and set it to mSystemAudioMode. } @Override @ServiceThreadOnly - protected void onAddressAllocated(int logicalAddress) { + protected void onAddressAllocated(int logicalAddress, boolean fromBootup) { assertRunOnServiceThread(); - // TODO: vendor-specific initialization here. - mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); - launchRoutingControl(true); + mSystemAudioMode = mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); + launchRoutingControl(fromBootup); launchDeviceDiscovery(); + registerAudioPortUpdateListener(); + // TODO: unregister audio port update listener if local device is released. + } + + private void registerAudioPortUpdateListener() { + mService.getAudioManager().registerAudioPortUpdateListener( + new OnAudioPortUpdateListener() { + @Override + public void OnAudioPatchListUpdate(AudioPatch[] patchList) {} + + @Override + public void OnAudioPortListUpdate(AudioPort[] portList) { + if (!mSystemAudioMode) { + return; + } + int devices = mService.getAudioManager().getDevicesForStream( + AudioSystem.STREAM_MUSIC); + if ((devices & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER) + != 0) { + // TODO: release system audio here. + } + } + + @Override + public void OnServiceDied() {} + }); } /** @@ -224,10 +253,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { * Sends key to a target CEC device. * * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. - * @param isPressed true if this is keypress event + * @param isPressed true if this is key press event */ + @Override @ServiceThreadOnly - void sendKeyEvent(int keyCode, boolean isPressed) { + protected void sendKeyEvent(int keyCode, boolean isPressed) { assertRunOnServiceThread(); List<SendKeyAction> action = getActions(SendKeyAction.class); if (!action.isEmpty()) { @@ -485,10 +515,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // If there is AVR, initiate System Audio Auto initiation action, // which turns on and off system audio according to last system // audio setting. - HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); - if (avrInfo != null) { + if (mSystemAudioMode && getAvrDeviceInfo() != null) { addAndStartAction(new SystemAudioAutoInitiationAction( - HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); + HdmiCecLocalDeviceTv.this, + getAvrDeviceInfo().getLogicalAddress())); if (mArcEstablished) { startArcAction(true); } @@ -511,7 +541,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); HdmiCecDeviceInfo avr = getAvrDeviceInfo(); if (avr == null) { - invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); + invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); return; } @@ -520,11 +550,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } // # Seq 25 - void setSystemAudioMode(boolean on) { + void setSystemAudioMode(boolean on, boolean updateSetting) { synchronized (mLock) { if (on != mSystemAudioMode) { mSystemAudioMode = on; - // TODO: Need to set the preference for SystemAudioMode. + if (updateSetting) { + mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); + } mService.announceSystemAudioModeChange(on); } } @@ -623,8 +655,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { synchronized (mLock) { mSystemAudioMute = mute; mSystemAudioVolume = volume; - // TODO: pass volume to service (audio service) after scale it to local volume level. - mService.setAudioStatus(mute, volume); + int maxVolume = mService.getAudioManager().getStreamMaxVolume( + AudioManager.STREAM_MUSIC); + mService.setAudioStatus(mute, + VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); } } @@ -725,7 +759,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!isMessageForSystemAudio(message)) { return false; } - setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); + setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); return true; } @@ -1033,19 +1067,58 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onTransitionToStandby(boolean initiatedByCec) { + protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { + super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); // Remove any repeated working actions. // HotplugDetectionAction will be reinstated during the wake up process. // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). + removeAction(DeviceDiscoveryAction.class); removeAction(HotplugDetectionAction.class); + + disableSystemAudioIfExist(); + disableArcIfExist(); checkIfPendingActionsCleared(); } + @ServiceThreadOnly + private void disableSystemAudioIfExist() { + assertRunOnServiceThread(); + if (getAvrDeviceInfo() == null) { + return; + } + + // Seq #31. + removeAction(SystemAudioActionFromAvr.class); + removeAction(SystemAudioActionFromTv.class); + removeAction(SystemAudioAutoInitiationAction.class); + removeAction(SystemAudioStatusAction.class); + removeAction(VolumeControlAction.class); + + // Turn off the mode but do not write it the settings, so that the next time TV powers on + // the system audio mode setting can be restored automatically. + setSystemAudioMode(false, false); + } + + @ServiceThreadOnly + private void disableArcIfExist() { + assertRunOnServiceThread(); + HdmiCecDeviceInfo avr = getAvrDeviceInfo(); + if (avr == null) { + return; + } + + // Seq #44. + removeAction(RequestArcInitiationAction.class); + if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { + addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); + } + } + @Override @ServiceThreadOnly - protected void onStandBy(boolean initiatedByCec) { + protected void onStandby(boolean initiatedByCec) { assertRunOnServiceThread(); // Seq #11 if (!mService.isControlEnabled()) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 57f89b1..0b6c3c5 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -274,7 +274,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return buildCommand(src, Constants.ADDR_TV, Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiConfig.java b/services/core/java/com/android/server/hdmi/HdmiConfig.java new file mode 100644 index 0000000..0793107 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiConfig.java @@ -0,0 +1,50 @@ +/* + * 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.hdmi; + +/** + * Class that holds together the constants that may require per-product configuration. + */ +final class HdmiConfig { + + // Default timeout for the incoming command to arrive in response to a request. + static final int TIMEOUT_MS = 2000; + + // IRT(Initiator Repetition Time) in millisecond as recommended in the standard. + // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart + // from the adjacent one so as not to place unnecessarily heavy load on the CEC line. + static final int IRT_MS = 300; + + // Number of retries for polling each device in device discovery phase after TV powers on + // or HDMI control is enabled. + static final int DEVICE_POLLING_RETRY = 1; + + // Number of retries for polling each device in periodic check (hotplug detection). + static final int HOTPLUG_DETECTION_RETRY = 2; + + // Number of retries for polling each device in address allocation mechanism. + static final int ADDRESS_ALLOCATION_RETRY = 3; + + // CEC spec said that it should try retransmission at least once. + // The actual number of send request for a single command will be at most + // RETRANSMISSION_COUNT + 1. Note that it affects only to normal commands + // and polling message for logical address allocation and device discovery + // action. They will have their own retransmission count. + static final int RETRANSMISSION_COUNT = 1; + + private HdmiConfig() { /* cannot be instantiated */ } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d35f03d..c04b2ba 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import android.annotation.Nullable; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -42,6 +43,8 @@ import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.provider.Settings.Global; +import android.provider.Settings.SettingNotFoundException; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -50,6 +53,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; +import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; import java.util.ArrayList; import java.util.Collections; @@ -72,9 +76,11 @@ public final class HdmiControlService extends SystemService { * Called when {@link HdmiControlService#sendCecCommand} is completed. * * @param error result of send request. - * @see {@link #SEND_RESULT_SUCCESS} - * @see {@link #SEND_RESULT_NAK} - * @see {@link #SEND_RESULT_FAILURE} + * <ul> + * <li>{@link Constants#SEND_RESULT_SUCCESS} + * <li>{@link Constants#SEND_RESULT_NAK} + * <li>{@link Constants#SEND_RESULT_FAILURE} + * </ul> */ void onSendCompleted(int error); } @@ -203,16 +209,19 @@ public final class HdmiControlService extends SystemService { public void onStart() { mIoThread.start(); mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; - mCecController = HdmiCecController.create(this); + mProhibitMode = false; + mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true); + mCecController = HdmiCecController.create(this); if (mCecController != null) { // TODO: Remove this as soon as OEM's HAL implementation is corrected. mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, HdmiTvClient.ENABLED); - mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, - HdmiTvClient.ENABLED); - initializeLocalDevices(mLocalDevices); + // TODO: load value for mHdmiControlEnabled from preference. + if (mHdmiControlEnabled) { + initializeCec(true); + } } else { Slog.i(TAG, "Device does not support HDMI-CEC."); } @@ -232,16 +241,26 @@ public final class HdmiControlService extends SystemService { filter.addAction(Intent.ACTION_SCREEN_ON); getContext().registerReceiver(mPowerStateReceiver, filter); } + } - // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and - // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. - mHdmiControlEnabled = true; - // TODO: Get control flag from persistent storage - mProhibitMode = false; + boolean readBooleanSetting(String key, boolean defVal) { + ContentResolver cr = getContext().getContentResolver(); + return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE; + } + + void writeBooleanSetting(String key, boolean value) { + ContentResolver cr = getContext().getContentResolver(); + Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE); + } + + private void initializeCec(boolean fromBootup) { + mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, + HdmiTvClient.ENABLED); + initializeLocalDevices(mLocalDevices, fromBootup); } @ServiceThreadOnly - private void initializeLocalDevices(final List<Integer> deviceTypes) { + private void initializeLocalDevices(final List<Integer> deviceTypes, final boolean fromBootup) { assertRunOnServiceThread(); // A container for [Logical Address, Local device info]. final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); @@ -270,7 +289,7 @@ public final class HdmiControlService extends SystemService { if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; } - notifyAddressAllocated(devices); + notifyAddressAllocated(devices, fromBootup); } } }); @@ -278,12 +297,13 @@ public final class HdmiControlService extends SystemService { } @ServiceThreadOnly - private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { + private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices, + boolean fromBootup) { assertRunOnServiceThread(); for (int i = 0; i < devices.size(); ++i) { int address = devices.keyAt(i); HdmiCecLocalDevice device = devices.valueAt(i); - device.handleAddressAllocated(address); + device.handleAddressAllocated(address, fromBootup); } } @@ -557,7 +577,22 @@ public final class HdmiControlService extends SystemService { } void setAudioStatus(boolean mute, int volume) { - // TODO: Hook up with AudioManager. + AudioManager audioManager = getAudioManager(); + boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); + if (mute) { + if (!muted) { + audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); + } + } else { + if (muted) { + audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); + } + // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing + // volume change notification back to hdmi control service. + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, + AudioManager.FLAG_SHOW_UI | + AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); + } } void announceSystemAudioModeChange(boolean enabled) { @@ -691,19 +726,17 @@ public final class HdmiControlService extends SystemService { } @Override - public void sendKeyEvent(final int keyCode, final boolean isPressed) { + public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { - // TODO: sendKeyEvent is for TV device only for now. Allow other - // local devices of different types to use this as well. - HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - Slog.w(TAG, "Local tv device not available"); + HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + if (localDevice == null) { + Slog.w(TAG, "Local device not available"); return; } - tv.sendKeyEvent(keyCode, isPressed); + localDevice.sendKeyEvent(keyCode, isPressed); } }); } @@ -841,27 +874,11 @@ public final class HdmiControlService extends SystemService { @Override public void setControlEnabled(final boolean enabled) { enforceAccessPermission(); - synchronized (mLock) { - mHdmiControlEnabled = enabled; - } - // TODO: Stop the running actions when disabled, and start - // address allocation/device discovery when enabled. - if (!enabled) { - return; - } runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - return; - } - int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; - mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); - if (mMhlController != null) { - mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); - } - tv.launchRoutingControl(false); + handleHdmiControlStatusChanged(enabled); + } }); } @@ -1229,8 +1246,9 @@ public final class HdmiControlService extends SystemService { assertRunOnServiceThread(); mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; if (mCecController != null) { - mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.ENABLED); - initializeLocalDevices(mLocalDevices); + if (mHdmiControlEnabled) { + initializeCec(true); + } } else { Slog.i(TAG, "Device does not support HDMI-CEC."); } @@ -1241,25 +1259,48 @@ public final class HdmiControlService extends SystemService { private void onStandby() { assertRunOnServiceThread(); mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; + + final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); + disableDevices(new PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); + devices.remove(device); + if (devices.isEmpty()) { + clearLocalDevices(); + onStandbyCompleted(); + } + } + }); + } + + private void disableDevices(PendingActionClearedCallback callback) { for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.onTransitionToStandby(mStandbyMessageReceived); + device.disableDevice(mStandbyMessageReceived, callback); } } - /** - * Called when there are the outstanding actions in the local devices. - * This callback is used to wait for when the action queue is empty - * during the power state transition to standby. - */ @ServiceThreadOnly - void onPendingActionsCleared() { + private void clearLocalDevices() { + assertRunOnServiceThread(); + if (mCecController == null) { + return; + } + mCecController.clearLogicalAddress(); + mCecController.clearLocalDevices(); + } + + @ServiceThreadOnly + private void onStandbyCompleted() { assertRunOnServiceThread(); + Slog.v(TAG, "onStandbyCompleted"); + if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { return; } mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.onStandBy(mStandbyMessageReceived); + device.onStandby(mStandbyMessageReceived); } mStandbyMessageReceived = false; mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED); @@ -1305,4 +1346,31 @@ public final class HdmiControlService extends SystemService { mProhibitMode = enabled; } } + + @ServiceThreadOnly + private void handleHdmiControlStatusChanged(boolean enabled) { + assertRunOnServiceThread(); + + int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; + mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); + if (mMhlController != null) { + mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); + } + + synchronized (mLock) { + mHdmiControlEnabled = enabled; + } + + if (enabled) { + initializeCec(false); + } else { + disableDevices(new PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + assertRunOnServiceThread(); + clearLocalDevices(); + } + }); + } + } } diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index e3d8ee2..185de59 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -38,7 +38,6 @@ final class HotplugDetectionAction extends FeatureAction { private static final int POLLING_INTERVAL_MS = 5000; private static final int TIMEOUT_COUNT = 3; - private static final int POLL_RETRY_COUNT = 2; // State in which waits for next polling private static final int STATE_WAIT_FOR_NEXT_POLLING = 1; @@ -126,7 +125,7 @@ final class HotplugDetectionAction extends FeatureAction { checkHotplug(ackedAddress, false); } }, Constants.POLL_ITERATION_IN_ORDER - | Constants.POLL_STRATEGY_REMOTES_DEVICES, POLL_RETRY_COUNT); + | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY); } private void pollAudioSystem() { @@ -138,7 +137,7 @@ final class HotplugDetectionAction extends FeatureAction { checkHotplug(ackedAddress, true); } }, Constants.POLL_ITERATION_IN_ORDER - | Constants.POLL_STRATEGY_SYSTEM_AUDIO, POLL_RETRY_COUNT); + | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY); } private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) { @@ -241,8 +240,8 @@ final class HotplugDetectionAction extends FeatureAction { return; } - // Turn off system audio mode. - tv().setSystemAudioMode(false); + // Turn off system audio mode and update settings. + tv().setSystemAudioMode(false, true); if (tv().isArcEstabilished()) { addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); } diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index a950afb..80deaab 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -87,7 +87,7 @@ final class NewDeviceAction extends FeatureAction { sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), mDeviceLogicalAddress)); - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } @@ -161,7 +161,7 @@ final class NewDeviceAction extends FeatureAction { } sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), mDeviceLogicalAddress)); - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } private void addDeviceInfo() { diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 40ce7ed..6299b99 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -76,7 +76,7 @@ final class OneTouchPlayAction extends FeatureAction { broadcastActiveSource(); queryDevicePowerStatus(); mState = STATE_WAITING_FOR_REPORT_POWER_STATUS; - addTimer(mState, FeatureAction.TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); return true; } @@ -116,7 +116,7 @@ final class OneTouchPlayAction extends FeatureAction { if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) { if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) { queryDevicePowerStatus(); - addTimer(mState, FeatureAction.TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } else { // Couldn't wake up the TV for whatever reason. Report failure. invokeCallback(HdmiControlManager.RESULT_TIMEOUT); diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java index 692f961..f25363d 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -42,7 +42,7 @@ final class RequestArcInitiationAction extends RequestArcAction { public void onSendCompleted(int error) { if (error == Constants.SEND_RESULT_SUCCESS) { mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } else { // If failed to send <Request ARC Initiation>, start "Disabled" // ARC transmission action. diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java index 31cbe32..1491c72 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java @@ -42,7 +42,7 @@ final class RequestArcTerminationAction extends RequestArcAction { public void onSendCompleted(int error) { if (error == Constants.SEND_RESULT_SUCCESS) { mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } else { // If failed to send <Request ARC Termination>, start "Disabled" ARC // transmission action. diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 15375f3..e0c1ad5 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -17,7 +17,6 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiCecDeviceInfo; - import android.util.Slog; /** @@ -80,7 +79,7 @@ final class SetArcTransmissionStateAction extends FeatureAction { // If succeeds to send <Report ARC Initiated>, wait general timeout // to check whether there is no <Feature Abort> for <Report ARC Initiated>. mState = STATE_WAITING_TIMEOUT; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); } else { // If fails to send <Report ARC Initiated>, disable ARC and // send <Report ARC Terminated> directly. diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index dab8ae9..86895cc 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -35,7 +35,7 @@ abstract class SystemAudioAction extends FeatureAction { private static final int MAX_SEND_RETRY_COUNT = 2; private static final int ON_TIMEOUT_MS = 5000; - private static final int OFF_TIMEOUT_MS = TIMEOUT_MS; + private static final int OFF_TIMEOUT_MS = HdmiConfig.TIMEOUT_MS; // Logical address of AV Receiver. protected final int mAvrLogicalAddress; @@ -78,7 +78,7 @@ abstract class SystemAudioAction extends FeatureAction { addTimer(mState, mTargetAudioStatus ? ON_TIMEOUT_MS : OFF_TIMEOUT_MS); } else { setSystemAudioMode(false); - finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); } } }); @@ -95,14 +95,19 @@ abstract class SystemAudioAction extends FeatureAction { } protected void setSystemAudioMode(boolean mode) { - tv().setSystemAudioMode(mode); + tv().setSystemAudioMode(mode, true); } @Override final boolean processCommand(HdmiCecMessage cmd) { switch (mState) { case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: - // TODO: Handle <FeatureAbort> of <SystemAudioModeRequest> + if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT + && cmd.getParams()[0] == Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST) { + setSystemAudioMode(false); + finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + return true; + } if (cmd.getOpcode() != Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE || !HdmiUtils.checkCommandSource(cmd, mAvrLogicalAddress, TAG)) { return false; diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java index a2b4beb..80954d4 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java @@ -37,7 +37,7 @@ final class SystemAudioAutoInitiationAction extends FeatureAction { boolean start() { mState = STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); sendGiveSystemAudioModeStatus(); return true; } @@ -48,7 +48,7 @@ final class SystemAudioAutoInitiationAction extends FeatureAction { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { - tv().setSystemAudioMode(false); + tv().setSystemAudioMode(false, true); finish(); } } @@ -79,7 +79,7 @@ final class SystemAudioAutoInitiationAction extends FeatureAction { } else { // If the last setting is non-system audio, turn off system audio mode // and update system audio status (volume or mute). - tv().setSystemAudioMode(false); + tv().setSystemAudioMode(false, true); if (canChangeSystemAudio()) { addAndStartAction(new SystemAudioStatusAction(tv(), mAvrAddress, null)); } @@ -106,7 +106,7 @@ final class SystemAudioAutoInitiationAction extends FeatureAction { addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true, null)); } } else { - tv().setSystemAudioMode(false); + tv().setSystemAudioMode(false, true); } finish(); } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java index ce9cc5c..91805c5 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java @@ -46,7 +46,7 @@ final class SystemAudioStatusAction extends FeatureAction { @Override boolean start() { mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS; - addTimer(mState, TIMEOUT_MS); + addTimer(mState, HdmiConfig.TIMEOUT_MS); sendGiveAudioStatus(); return true; } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index cab2728..7f8b232 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -140,12 +140,10 @@ public class JobSchedulerService extends com.android.server.SystemService * This cancels the job if it's already been scheduled, and replaces it with the one provided. * @param job JobInfo object containing execution parameters * @param uId The package identifier of the application this job is for. - * @param canPersistJob Whether or not the client has the appropriate permissions for - * persisting this job. * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes. */ - public int schedule(JobInfo job, int uId, boolean canPersistJob) { - JobStatus jobStatus = new JobStatus(job, uId, canPersistJob); + public int schedule(JobInfo job, int uId) { + JobStatus jobStatus = new JobStatus(job, uId); cancelJob(uId, job.getId()); startTrackingJob(jobStatus); return JobScheduler.RESULT_SUCCESS; @@ -668,11 +666,16 @@ public class JobSchedulerService extends com.android.server.SystemService final int uid = Binder.getCallingUid(); enforceValidJobRequest(uid, job); - final boolean canPersist = canPersistJobs(pid, uid); + if (job.isPersisted()) { + if (!canPersistJobs(pid, uid)) { + throw new IllegalArgumentException("Error: requested job be persisted without" + + " holding RECEIVE_BOOT_COMPLETED permission."); + } + } long ident = Binder.clearCallingIdentity(); try { - return JobSchedulerService.this.schedule(job, uid, canPersist); + return JobSchedulerService.this.schedule(job, uid); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index 4ac26c1..8736980 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -160,7 +160,9 @@ public class JobStore { } return false; } - maybeWriteStatusToDiskAsync(); + if (jobStatus.isPersisted()) { + maybeWriteStatusToDiskAsync(); + } return removed; } @@ -429,7 +431,8 @@ public class JobStore { } } - private List<JobStatus> readJobMapImpl(FileInputStream fis) throws XmlPullParserException, IOException { + private List<JobStatus> readJobMapImpl(FileInputStream fis) + throws XmlPullParserException, IOException { XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); @@ -498,6 +501,7 @@ public class JobStore { // Read out job identifier attributes. try { jobBuilder = buildBuilderFromXml(parser); + jobBuilder.setIsPersisted(true); uid = Integer.valueOf(parser.getAttributeValue(null, "uid")); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 53337c4..9ee2869 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -43,9 +43,6 @@ public class JobStatus { final JobInfo job; final int uId; - /** At reschedule time we need to know whether to update job on disk. */ - final boolean persisted; - // Constraints. final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean(); @@ -72,16 +69,15 @@ public class JobStatus { return uId; } - private JobStatus(JobInfo job, int uId, boolean persisted, int numFailures) { + private JobStatus(JobInfo job, int uId, int numFailures) { this.job = job; this.uId = uId; this.numFailures = numFailures; - this.persisted = persisted; } /** Create a newly scheduled job. */ - public JobStatus(JobInfo job, int uId, boolean persisted) { - this(job, uId, persisted, 0); + public JobStatus(JobInfo job, int uId) { + this(job, uId, 0); final long elapsedNow = SystemClock.elapsedRealtime(); @@ -105,7 +101,7 @@ public class JobStatus { */ public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { - this(job, uId, true, 0); + this(job, uId, 0); this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; @@ -114,7 +110,7 @@ public class JobStatus { /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt) { - this(rescheduling.job, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt); + this(rescheduling.job, rescheduling.getUid(), backoffAttempt); earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis; latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis; @@ -172,6 +168,10 @@ public class JobStatus { return job.isRequireDeviceIdle(); } + public boolean isPersisted() { + return job.isPersisted(); + } + public long getEarliestRunTime() { return earliestRunTimeElapsedMillis; } @@ -180,9 +180,6 @@ public class JobStatus { return latestRunTimeElapsedMillis; } - public boolean isPersisted() { - return persisted; - } /** * @return Whether or not this job is ready to run, based on its requirements. */ diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java index 2699fea..495d3a9 100644 --- a/services/core/java/com/android/server/location/FlpHardwareProvider.java +++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java @@ -420,9 +420,8 @@ public class FlpHardwareProvider { return GeofenceHardware.GEOFENCE_SUCCESS; case FLP_RESULT_ERROR: return GeofenceHardware.GEOFENCE_FAILURE; - // TODO: uncomment this once the ERROR definition is marked public - //case FLP_RESULT_INSUFFICIENT_MEMORY: - // return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; + case FLP_RESULT_INSUFFICIENT_MEMORY: + return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; case FLP_RESULT_TOO_MANY_GEOFENCES: return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; case FLP_RESULT_ID_EXISTS: diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3a6cec2..892f61f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2301,7 +2301,7 @@ public class NotificationManagerService extends SystemService { int speedBumpIndex = -1; final int N = mNotificationList.size(); ArrayList<String> keys = new ArrayList<String>(N); - ArrayList<String> dndKeys = new ArrayList<String>(N); + ArrayList<String> interceptedKeys = new ArrayList<String>(N); for (int i = 0; i < N; i++) { NotificationRecord record = mNotificationList.get(i); if (!info.enabledAndUserMatches(record.sbn.getUserId())) { @@ -2309,7 +2309,7 @@ public class NotificationManagerService extends SystemService { } keys.add(record.sbn.getKey()); if (record.isIntercepted()) { - dndKeys.add(record.sbn.getKey()); + interceptedKeys.add(record.sbn.getKey()); } if (speedBumpIndex == -1 && record.sbn.getNotification().priority == Notification.PRIORITY_MIN) { @@ -2317,8 +2317,8 @@ public class NotificationManagerService extends SystemService { } } String[] keysAr = keys.toArray(new String[keys.size()]); - String[] dndKeysAr = dndKeys.toArray(new String[dndKeys.size()]); - return new NotificationRankingUpdate(keysAr, dndKeysAr, speedBumpIndex); + String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]); + return new NotificationRankingUpdate(keysAr, interceptedKeysAr, speedBumpIndex); } public class NotificationListeners extends ManagedServices { diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index 96e8f30..871c94f 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -236,12 +236,12 @@ public class KeySetManagerService { * Returns {@code null} if the identifier doesn't * identify a {@link KeySet}. */ - public Set<PublicKey> getPublicKeysFromKeySet(long id) { + public ArraySet<PublicKey> getPublicKeysFromKeySet(long id) { synchronized (mLockObject) { if(mKeySetMapping.get(id) == null) { return null; } - Set<PublicKey> mPubKeys = new ArraySet<PublicKey>(); + ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>(); for (long pkId : mKeySetMapping.get(id)) { mPubKeys.add(mPublicKeys.get(pkId)); } @@ -280,9 +280,9 @@ public class KeySetManagerService { * @throws IllegalArgumentException if the package has no keyset data. * @throws NullPointerException if the package is unknown. */ - public Set<KeySet> getUpgradeKeySetsByPackageName(String packageName) { + public ArraySet<KeySet> getUpgradeKeySetsByPackageName(String packageName) { synchronized (mLockObject) { - Set<KeySet> upgradeKeySets = new ArraySet<KeySet>(); + ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>(); PackageSetting p = mPackages.get(packageName); if (p == null) { throw new NullPointerException("Unknown package"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5c05f45..d5294aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4822,7 +4822,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Fix that up here. if (isSystemApp(pkg)) { PackageSetting ps = mSettings.mPackages.get(pkg.applicationInfo.packageName); - setBundledAppAbisAndRoots(pkg, ps); + if (!isUpdatedSystemApp(pkg)) { + setBundledAppAbisAndRoots(pkg, ps); + } } if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { @@ -5059,7 +5061,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, - destResourceFile, pkg.applicationInfo.legacyNativeLibraryDir, + destResourceFile, pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags, user, false); @@ -5285,7 +5287,7 @@ public class PackageManagerService extends IPackageManager.Stub { + pkg.applicationInfo.uid + "/fs_" + currentUid; pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir; - pkg.applicationInfo.legacyNativeLibraryDir = pkg.applicationInfo.dataDir; + pkg.applicationInfo.nativeLibraryRootDir = pkg.applicationInfo.dataDir; String msg = "Package " + pkg.packageName + " has mismatched uid: " + currentUid + " on disk, " @@ -5344,22 +5346,20 @@ public class PackageManagerService extends IPackageManager.Stub { NativeLibraryHelper.removeNativeBinariesFromDirLI( new File(codePath, LIB_DIR_NAME), false /* delete dirs */); setBundledAppAbisAndRoots(pkg, pkgSetting); + setNativeLibraryPaths(pkg); } else { // TODO: We can probably be smarter about this stuff. For installed apps, // we can calculate this information at install time once and for all. For // system apps, we can probably assume that this information doesn't change // after the first boot scan. As things stand, we do lots of unnecessary work. + // Give ourselves some initial paths; we'll come back for another + // pass once we've determined ABI below. + setNativeLibraryPaths(pkg); + final boolean isAsec = isForwardLocked(pkg) || isExternal(pkg); - final String nativeLibraryRootStr; - final boolean useIsaSpecificSubdirs; - if (pkg.applicationInfo.legacyNativeLibraryDir != null) { - nativeLibraryRootStr = pkg.applicationInfo.legacyNativeLibraryDir; - useIsaSpecificSubdirs = false; - } else { - nativeLibraryRootStr = new File(pkg.codePath, LIB_DIR_NAME).getAbsolutePath(); - useIsaSpecificSubdirs = true; - } + final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir; + final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa; NativeLibraryHelper.Handle handle = null; try { @@ -5465,6 +5465,10 @@ public class PackageManagerService extends IPackageManager.Stub { IoUtils.closeQuietly(handle); } + // Now that we've calculated the ABIs and determined if it's an internal app, + // we will go ahead and populate the nativeLibraryPath. + setNativeLibraryPaths(pkg); + if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path); final int[] userIds = sUserManager.getUserIds(); synchronized (mInstallLock) { @@ -5473,14 +5477,7 @@ public class PackageManagerService extends IPackageManager.Stub { // this symlink for 64 bit libraries. if (pkg.applicationInfo.primaryCpuAbi != null && !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { - final String nativeLibPath; - if (pkg.applicationInfo.legacyNativeLibraryDir != null) { - nativeLibPath = pkg.applicationInfo.legacyNativeLibraryDir; - } else { - nativeLibPath = new File(nativeLibraryRootStr, - VMRuntime.getInstructionSet(pkg.applicationInfo.primaryCpuAbi)).getAbsolutePath(); - } - + final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; for (int userId : userIds) { if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { Slog.w(TAG, "Failed linking native library dir (user=" + userId @@ -5496,19 +5493,20 @@ public class PackageManagerService extends IPackageManager.Stub { pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi; } + Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.applicationInfo.packageName + + " to root=" + pkg.applicationInfo.nativeLibraryRootDir + ", isa=" + + pkg.applicationInfo.nativeLibraryRootRequiresIsa); + + // Push the derived path down into PackageSettings so we know what to + // clean up at uninstall time. + pkgSetting.legacyNativeLibraryPathString = pkg.applicationInfo.nativeLibraryRootDir; + if (DEBUG_ABI_SELECTION) { Log.d(TAG, "Abis for package[" + pkg.packageName + "] are" + " primary=" + pkg.applicationInfo.primaryCpuAbi + " secondary=" + pkg.applicationInfo.secondaryCpuAbi); } - // Check if we have a legacy native library path, use it if we do. - pkg.applicationInfo.legacyNativeLibraryDir = pkgSetting.legacyNativeLibraryPathString; - - // Now that we've calculated the ABIs and determined if it's an internal app, - // we will go ahead and populate the nativeLibraryPath. - populateDefaultNativeLibraryPath(pkg, pkg.applicationInfo); - if ((scanMode&SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) { // We don't do this here during boot because we can do it all // at once after scanning all existing packages. @@ -5692,15 +5690,14 @@ public class PackageManagerService extends IPackageManager.Stub { ksms.removeAppKeySetData(pkg.packageName); ksms.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys); if (pkg.mKeySetMapping != null) { - for (Map.Entry<String, Set<PublicKey>> entry : + for (Map.Entry<String, ArraySet<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) { if (entry.getValue() != null) { ksms.addDefinedKeySetToPackage(pkg.packageName, entry.getValue(), entry.getKey()); } } - if (pkg.mUpgradeKeySets != null - && pkg.mKeySetMapping.keySet().containsAll(pkg.mUpgradeKeySets)) { + if (pkg.mUpgradeKeySets != null) { for (String upgradeAlias : pkg.mUpgradeKeySets) { ksms.addUpgradeKeySetToPackage(pkg.packageName, upgradeAlias); } @@ -6160,49 +6157,66 @@ public class PackageManagerService extends IPackageManager.Stub { return codeRoot.getPath(); } - private void populateDefaultNativeLibraryPath(PackageParser.Package pkg, - ApplicationInfo info) { - if (info.legacyNativeLibraryDir != null) { - // Not a cluster install. - if (DEBUG_ABI_SELECTION) { - Log.i(TAG, "Set nativeLibraryDir [non_cluster] for: " + pkg.packageName + - " to " + info.legacyNativeLibraryDir); - } - info.nativeLibraryDir = info.legacyNativeLibraryDir; - } else if (info.primaryCpuAbi != null) { - final boolean is64Bit = VMRuntime.is64BitAbi(info.primaryCpuAbi); - if (info.apkRoot != null) { - // This is a bundled system app so choose the path based on the ABI. - // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this - // is just the default path. - final String apkName = deriveCodePathName(pkg.applicationInfo.getCodePath()); - final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME; - info.nativeLibraryDir = (new File(info.apkRoot, new File(libDir, apkName).getAbsolutePath())) - .getAbsolutePath(); + /** + * Derive and set the location of native libraries for the given package, + * which varies depending on where and how the package was installed. + */ + private void setNativeLibraryPaths(PackageParser.Package pkg) { + final ApplicationInfo info = pkg.applicationInfo; + final String codePath = pkg.codePath; + final File codeFile = new File(codePath); - if (DEBUG_ABI_SELECTION) { - Log.i(TAG, "Set nativeLibraryDir [system] for: " + pkg.packageName + - " to " + info.nativeLibraryDir); - } + final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info); + final boolean asecApp = isForwardLocked(info) || isExternal(info); + + info.nativeLibraryRootDir = null; + info.nativeLibraryRootRequiresIsa = false; + info.nativeLibraryDir = null; + + if (bundledApp) { + // Monolithic bundled install + // TODO: support cluster bundled installs? + + final boolean is64Bit = (info.primaryCpuAbi != null) + && VMRuntime.is64BitAbi(info.primaryCpuAbi); + + // This is a bundled system app so choose the path based on the ABI. + // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this + // is just the default path. + final String apkName = deriveCodePathName(codePath); + final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME; + info.nativeLibraryRootDir = Environment.buildPath(new File(info.apkRoot), libDir, + apkName).getAbsolutePath(); + info.nativeLibraryRootRequiresIsa = false; + + } else if (isApkFile(codeFile)) { + // Monolithic install + if (asecApp) { + info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME) + .getAbsolutePath(); + info.nativeLibraryRootRequiresIsa = false; } else { - // Cluster install. legacyNativeLibraryDir == null && primaryCpuAbi = null - // implies this must be a cluster package. - final String codePath = pkg.codePath; - final File libPath = new File(new File(codePath, LIB_DIR_NAME), - VMRuntime.getInstructionSet(info.primaryCpuAbi)); - info.nativeLibraryDir = libPath.getAbsolutePath(); - - if (DEBUG_ABI_SELECTION) { - Log.i(TAG, "Set nativeLibraryDir [cluster] for: " + pkg.packageName + - " to " + info.nativeLibraryDir); - } + final String apkName = deriveCodePathName(codePath); + info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName) + .getAbsolutePath(); + info.nativeLibraryRootRequiresIsa = false; } } else { - if (DEBUG_ABI_SELECTION) { - Log.i(TAG, "Setting nativeLibraryDir to null for: " + pkg.packageName); - } + // Cluster install + info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath(); + info.nativeLibraryRootRequiresIsa = true; + } - info.nativeLibraryDir = null; + if (info.nativeLibraryRootRequiresIsa) { + if (info.primaryCpuAbi != null) { + info.nativeLibraryDir = new File(info.nativeLibraryRootDir, + VMRuntime.getInstructionSet(info.primaryCpuAbi)).getAbsolutePath(); + } else { + Slog.w(TAG, "Package " + info.packageName + + " missing ABI; unable to derive nativeLibraryDir"); + } + } else { + info.nativeLibraryDir = info.nativeLibraryRootDir; } } @@ -6329,8 +6343,7 @@ public class PackageManagerService extends IPackageManager.Stub { subDir = nativeLibraryRoot; } - int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, - subDir, Build.SUPPORTED_ABIS[abi]); + int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]); if (copyRet != PackageManager.INSTALL_SUCCEEDED) { return copyRet; } @@ -9123,13 +9136,13 @@ public class PackageManagerService extends IPackageManager.Stub { } /** Existing install */ - FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryRoot, + FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath, String[] instructionSets, boolean isMultiArch) { super(null, false, null, 0, null, null, null, instructionSets, null, isMultiArch); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; - this.legacyNativeLibraryPath = (legacyNativeLibraryRoot != null) ? - new File(legacyNativeLibraryRoot) : null; + this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ? + new File(legacyNativeLibraryPath) : null; } /** New install from existing */ @@ -9313,8 +9326,6 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(pkg.codePath); pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath); pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths); - // Null out the legacy native dir so we stop using it. - pkg.applicationInfo.legacyNativeLibraryDir = null; return true; } @@ -9599,9 +9610,6 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(getResourcePath()); pkg.applicationInfo.setBaseResourcePath(getResourcePath()); pkg.applicationInfo.setSplitResourcePaths(null); - // ASEC installs are considered "legacy" because we don't support - // multiarch on them yet, and use the old style paths on them. - pkg.applicationInfo.legacyNativeLibraryDir = legacyNativeLibraryDir; return true; } @@ -10073,7 +10081,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.args = createInstallArgsForExisting(0, deletedPackage.applicationInfo.getCodePath(), deletedPackage.applicationInfo.getResourcePath(), - deletedPackage.applicationInfo.legacyNativeLibraryDir, + deletedPackage.applicationInfo.nativeLibraryRootDir, getAppDexInstructionSets(deletedPackage.applicationInfo), isMultiArch(deletedPackage.applicationInfo)); } else { @@ -10383,6 +10391,9 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; } + private static boolean isForwardLocked(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; + } private boolean isForwardLocked(PackageSetting ps) { return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; @@ -10404,6 +10415,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } + private static boolean isExternal(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + private static boolean isSystemApp(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } @@ -10428,6 +10443,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } + private static boolean isUpdatedSystemApp(ApplicationInfo info) { + return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + private int packageFlagsToInstallFlags(PackageSetting ps) { int installFlags = 0; if (isExternal(ps)) { @@ -10470,6 +10489,26 @@ public class PackageManagerService extends IPackageManager.Stub { return; } + boolean blocked = false; + if ((flags & PackageManager.DELETE_ALL_USERS) != 0) { + int[] users = sUserManager.getUserIds(); + for (int i = 0; i < users.length; ++i) { + if (getBlockUninstallForUser(packageName, users[i])) { + blocked = true; + break; + } + } + } else { + blocked = getBlockUninstallForUser(packageName, userId); + } + if (blocked) { + try { + observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_OWNER_BLOCKED); + } catch (RemoteException re) { + } + return; + } + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId); // Queue up an async operation since the package deletion may take a little while. mHandler.post(new Runnable() { @@ -12849,7 +12888,7 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean multiArch = isMultiArch(pkg.applicationInfo); InstallArgs srcArgs = createInstallArgsForExisting(currFlags, pkg.applicationInfo.getCodePath(), pkg.applicationInfo.getResourcePath(), - pkg.applicationInfo.legacyNativeLibraryDir, instructionSets, multiArch); + pkg.applicationInfo.nativeLibraryRootDir, instructionSets, multiArch); MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, instructionSets, pkg.applicationInfo.uid, user, multiArch); msg.obj = mp; @@ -12976,9 +13015,6 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.setResourcePath(newResPath); pkg.applicationInfo.setBaseResourcePath(newResPath); pkg.applicationInfo.setSplitResourcePaths(null); - // Null out the legacy nativeLibraryDir so that we stop using it and - // always derive the codepath. - pkg.applicationInfo.legacyNativeLibraryDir = null; PackageSetting ps = (PackageSetting) pkg.mExtras; ps.codePath = new File(pkg.applicationInfo.getCodePath()); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 3390efe..e164f5f 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -57,10 +57,11 @@ class PackageSettingBase extends GrantedPermissions { String resourcePathString; /** - * The path under which native libraries for legacy apps are unpacked. - * Will be set to {@code null} for newer installs, where the path can be - * derived from {@link #codePath} unambiguously. + * The path under which native libraries have been unpacked. This path is + * always derived at runtime, and is only stored here for cleanup when a + * package is uninstalled. */ + @Deprecated String legacyNativeLibraryPathString; String primaryCpuAbiString; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 71b8974..40561ba 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -82,6 +82,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.Map.Entry; @@ -317,11 +318,11 @@ final class Settings { PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryRoot, String primaryCpuAbi, String secondaryCpuAbi, int pkgFlags, + String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi, int pkgFlags, UserHandle user, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, - resourcePath, nativeLibraryRoot, primaryCpuAbi, secondaryCpuAbi, + resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi, pkg.mVersionCode, pkgFlags, user, add, true /* allowInstall */); return p; } @@ -689,24 +690,24 @@ final class Settings { // pkg.mSetStopped = p.getStopped(userId); final String codePath = pkg.applicationInfo.getCodePath(); final String resourcePath = pkg.applicationInfo.getResourcePath(); + final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir; // Update code path if needed - if (!codePath.equalsIgnoreCase(p.codePathString)) { + if (!Objects.equals(codePath, p.codePathString)) { Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName + " changing from " + p.codePathString + " to " + codePath); p.codePath = new File(codePath); p.codePathString = codePath; } //Update resource path if needed - if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) { + if (!Objects.equals(resourcePath, p.resourcePathString)) { Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName + " changing from " + p.resourcePathString + " to " + resourcePath); p.resourcePath = new File(resourcePath); p.resourcePathString = resourcePath; } // Update the native library paths if needed - final String nativeLibraryRoot = pkg.applicationInfo.legacyNativeLibraryDir; - if (nativeLibraryRoot != null && !nativeLibraryRoot.equalsIgnoreCase(p.legacyNativeLibraryPathString)) { - p.legacyNativeLibraryPathString = nativeLibraryRoot; + if (!Objects.equals(legacyNativeLibraryPath, p.legacyNativeLibraryPathString)) { + p.legacyNativeLibraryPathString = legacyNativeLibraryPath; } // Update the required Cpu Abi @@ -3136,6 +3137,9 @@ final class Settings { FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); for (PackageSetting ps : mPackages.values()) { + if (ps.pkg == null || ps.pkg.applicationInfo == null) { + continue; + } // Only system apps are initially installed. ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle); // Need to create a data directory for all apps under this user. diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index bf767e2..6be4d4f 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -46,6 +46,7 @@ import android.media.tv.TvContract; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputService; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -432,75 +433,40 @@ public final class TvInputManagerService extends SystemService { } @Override - public void onVideoStreamChanged(int width, int height, boolean interlaced) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onVideoStreamChanged(" + width + ", " + height + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onVideoStreamChanged(width, height, interlaced, - sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onVideoStreamChanged"); - } - } - } - - @Override - public void onAudioStreamChanged(int channelCount) { + public void onChannelRetuned(Uri channelUri) { synchronized (mLock) { if (DEBUG) { - Slog.d(TAG, "onAudioStreamChanged(" + channelCount + ")"); + Slog.d(TAG, "onChannelRetuned(" + channelUri + ")"); } if (sessionState.mSession == null || sessionState.mClient == null) { return; } try { - sessionState.mClient.onAudioStreamChanged(channelCount, sessionState.mSeq); + // TODO: Consider adding this channel change in the watch log. When we do + // that, how we can protect the watch log from malicious tv inputs should + // be addressed. e.g. add a field which represents where the channel change + // originated from. + sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq); } catch (RemoteException e) { - Slog.e(TAG, "error in onAudioStreamChanged"); + Slog.e(TAG, "error in onChannelRetuned"); } } } @Override - public void onClosedCaptionStreamChanged(boolean hasClosedCaption) { + public void onTrackInfoChanged(List<TvTrackInfo> tracks) { synchronized (mLock) { if (DEBUG) { - Slog.d(TAG, "onClosedCaptionStreamChanged(" + hasClosedCaption + ")"); + Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")"); } if (sessionState.mSession == null || sessionState.mClient == null) { return; } try { - sessionState.mClient.onClosedCaptionStreamChanged(hasClosedCaption, + sessionState.mClient.onTrackInfoChanged(tracks, sessionState.mSeq); } catch (RemoteException e) { - Slog.e(TAG, "error in onClosedCaptionStreamChanged"); - } - } - } - - @Override - public void onChannelRetuned(Uri channelUri) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onChannelRetuned(" + channelUri + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - // TODO: Consider adding this channel change in the watch log. When we do - // that, how we can protect the watch log from malicious tv inputs should - // be addressed. e.g. add a field which represents where the channel change - // originated from. - sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onChannelRetuned"); + Slog.e(TAG, "error in onTrackInfoChanged"); } } } @@ -895,6 +861,46 @@ public final class TvInputManagerService extends SystemService { } @Override + public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "selectTrack"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack( + track); + } catch (RemoteException e) { + Slog.e(TAG, "error in selectTrack", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "unselectTrack"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack( + track); + } catch (RemoteException e) { + Slog.e(TAG, "error in unselectTrack", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, int userId) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 771b53b..396ec8f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5171,6 +5171,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + void showGlobalActions() { + mPolicy.showGlobalActions(); + } + @Override public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { @@ -11130,7 +11134,12 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isKeyguardLocked() { - return isKeyguardLocked(); + return WindowManagerService.this.isKeyguardLocked(); + } + + @Override + public void showGlobalActions() { + WindowManagerService.this.showGlobalActions(); } @Override diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index bda10de..8387b65 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -130,7 +130,8 @@ class WindowStateAnimator { // For debugging, this is the last information given to the surface flinger. boolean mSurfaceShown; - float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; + float mSurfaceX, mSurfaceY; + float mSurfaceW, mSurfaceH; int mSurfaceLayer; float mSurfaceAlpha; @@ -666,117 +667,149 @@ class WindowStateAnimator { } SurfaceControl createSurfaceLocked() { + final WindowState w = mWin; if (mSurfaceControl == null) { if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG, "createSurface " + this + ": mDrawState=DRAW_PENDING"); mDrawState = DRAW_PENDING; - if (mWin.mAppToken != null) { - if (mWin.mAppToken.mAppAnimator.animation == null) { - mWin.mAppToken.allDrawn = false; - mWin.mAppToken.deferClearAllDrawn = false; + if (w.mAppToken != null) { + if (w.mAppToken.mAppAnimator.animation == null) { + w.mAppToken.allDrawn = false; + w.mAppToken.deferClearAllDrawn = false; } else { // Currently animating, persist current state of allDrawn until animation // is complete. - mWin.mAppToken.deferClearAllDrawn = true; + w.mAppToken.deferClearAllDrawn = true; } } - mService.makeWindowFreezingScreenIfNeededLocked(mWin); + mService.makeWindowFreezingScreenIfNeededLocked(w); int flags = SurfaceControl.HIDDEN; - final WindowManager.LayoutParams attrs = mWin.mAttrs; + final WindowManager.LayoutParams attrs = w.mAttrs; if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { flags |= SurfaceControl.SECURE; } - if (DEBUG_VISIBILITY) Slog.v( - TAG, "Creating surface in session " - + mSession.mSurfaceSession + " window " + this - + " w=" + mWin.mCompatFrame.width() - + " h=" + mWin.mCompatFrame.height() + " format=" - + attrs.format + " flags=" + flags); - int w = mWin.mCompatFrame.width(); - int h = mWin.mCompatFrame.height(); + int width; + int height; if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) { // for a scaled surface, we always want the requested // size. - w = mWin.mRequestedWidth; - h = mWin.mRequestedHeight; + width = w.mRequestedWidth; + height = w.mRequestedHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); } // Something is wrong and SurfaceFlinger will not like this, // try to revert to sane values - if (w <= 0) w = 1; - if (h <= 0) h = 1; + if (width <= 0) { + width = 1; + } + if (height <= 0) { + height = 1; + } + + float left = w.mFrame.left + w.mXOffset; + float top = w.mFrame.top + w.mYOffset; + + // Adjust for surface insets. + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + if (DEBUG_VISIBILITY) { + Slog.v(TAG, "Creating surface in session " + + mSession.mSurfaceSession + " window " + this + + " w=" + width + " h=" + height + + " x=" + left + " y=" + top + + " format=" + attrs.format + " flags=" + flags); + } + + // We may abort, so initialize to defaults. mSurfaceShown = false; mSurfaceLayer = 0; mSurfaceAlpha = 0; mSurfaceX = 0; mSurfaceY = 0; - mSurfaceW = w; - mSurfaceH = h; - mWin.mLastSystemDecorRect.set(0, 0, 0, 0); + w.mLastSystemDecorRect.set(0, 0, 0, 0); + + // Set up surface control with initial size. try { + mSurfaceW = width; + mSurfaceH = height; + final boolean isHwAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; if (!PixelFormat.formatHasAlpha(attrs.format)) { flags |= SurfaceControl.OPAQUE; } + if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( mSession.mSurfaceSession, attrs.getTitle().toString(), - w, h, format, flags); + width, height, format, flags); } else { mSurfaceControl = new SurfaceControl( mSession.mSurfaceSession, attrs.getTitle().toString(), - w, h, format, flags); + width, height, format, flags); + } + + w.mHasSurface = true; + + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + Slog.i(TAG, " CREATE SURFACE " + + mSurfaceControl + " IN SESSION " + + mSession.mSurfaceSession + + ": pid=" + mSession.mPid + " format=" + + attrs.format + " flags=0x" + + Integer.toHexString(flags) + + " / " + this); } - mWin.mHasSurface = true; - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, - " CREATE SURFACE " - + mSurfaceControl + " IN SESSION " - + mSession.mSurfaceSession - + ": pid=" + mSession.mPid + " format=" - + attrs.format + " flags=0x" - + Integer.toHexString(flags) - + " / " + this); } catch (OutOfResourcesException e) { - mWin.mHasSurface = false; + w.mHasSurface = false; Slog.w(TAG, "OutOfResourcesException creating surface"); mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); mDrawState = NO_SURFACE; return null; } catch (Exception e) { - mWin.mHasSurface = false; + w.mHasSurface = false; Slog.e(TAG, "Exception creating surface", e); mDrawState = NO_SURFACE; return null; } - if (WindowManagerService.localLOGV) Slog.v( - TAG, "Got surface: " + mSurfaceControl - + ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top - + ", animLayer=" + mAnimLayer); + if (WindowManagerService.localLOGV) { + Slog.v(TAG, "Got surface: " + mSurfaceControl + + ", set left=" + w.mFrame.left + " top=" + w.mFrame.top + + ", animLayer=" + mAnimLayer); + } + if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); - WindowManagerService.logSurface(mWin, "CREATE pos=(" - + mWin.mFrame.left + "," + mWin.mFrame.top + ") (" - + mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height() + WindowManagerService.logSurface(w, "CREATE pos=(" + + w.mFrame.left + "," + w.mFrame.top + ") (" + + w.mCompatFrame.width() + "x" + w.mCompatFrame.height() + "), layer=" + mAnimLayer + " HIDE", null); } + + // Start a new transaction and apply position & offset. SurfaceControl.openTransaction(); try { + mSurfaceX = left; + mSurfaceY = top; + try { - mSurfaceX = mWin.mFrame.left + mWin.mXOffset; - mSurfaceY = mWin.mFrame.top + mWin.mYOffset; - mSurfaceControl.setPosition(mSurfaceX, mSurfaceY); + mSurfaceControl.setPosition(left, top); mSurfaceLayer = mAnimLayer; - final DisplayContent displayContent = mWin.getDisplayContent(); + final DisplayContent displayContent = w.getDisplayContent(); if (displayContent != null) { mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack()); } @@ -1107,14 +1140,27 @@ class WindowStateAnimator { void applyDecorRect(final Rect decorRect) { final WindowState w = mWin; + int width = w.mFrame.width(); + int height = w.mFrame.height(); + // Compute the offset of the window in relation to the decor rect. - final int offX = w.mXOffset + w.mFrame.left; - final int offY = w.mYOffset + w.mFrame.top; + int left = w.mXOffset + w.mFrame.left; + int top = w.mYOffset + w.mFrame.top; + + // Adjust for surface insets. + final WindowManager.LayoutParams attrs = w.mAttrs; + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + // Initialize the decor rect to the entire frame. - w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height()); + w.mSystemDecorRect.set(0, 0, width, height); + // Intersect with the decor rect, offsetted by window position. - w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY, - decorRect.right-offX, decorRect.bottom-offY); + w.mSystemDecorRect.intersect(decorRect.left - left, decorRect.top - top, + decorRect.right - left, decorRect.bottom - top); + // If size compatibility is being applied to the window, the // surface is scaled relative to the screen. Also apply this // scaling to the crop rect. We aren't using the standard rect @@ -1212,10 +1258,12 @@ class WindowStateAnimator { void setSurfaceBoundariesLocked(final boolean recoveringMemory) { final WindowState w = mWin; - int width, height; + + int width; + int height; if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { - // for a scaled surface, we just want to use - // the requested size. + // for a scaled surface, we always want the requested + // size. width = w.mRequestedWidth; height = w.mRequestedHeight; } else { @@ -1223,42 +1271,52 @@ class WindowStateAnimator { height = w.mCompatFrame.height(); } + // Something is wrong and SurfaceFlinger will not like this, + // try to revert to sane values if (width < 1) { width = 1; } if (height < 1) { height = 1; } - final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height; - if (surfaceResized) { - mSurfaceW = width; - mSurfaceH = height; - } - final float left = w.mShownFrame.left; - final float top = w.mShownFrame.top; - if (mSurfaceX != left || mSurfaceY != top) { + float left = w.mShownFrame.left; + float top = w.mShownFrame.top; + + // Adjust for surface insets. + final LayoutParams attrs = w.getAttrs(); + width += attrs.shadowInsets.left + attrs.shadowInsets.right; + height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; + left -= attrs.shadowInsets.left; + top -= attrs.shadowInsets.top; + + final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top; + if (surfaceMoved) { + mSurfaceX = left; + mSurfaceY = top; + try { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "POS " + left + ", " + top, null); - mSurfaceX = left; - mSurfaceY = top; mSurfaceControl.setPosition(left, top); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + w - + " pos=(" + left - + "," + top + ")", e); + + " pos=(" + left + "," + top + ")", e); if (!recoveringMemory) { mService.reclaimSomeSurfaceMemoryLocked(this, "position", true); } } } + final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height; if (surfaceResized) { + mSurfaceW = width; + mSurfaceH = height; + mSurfaceResized = true; + try { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "SIZE " + width + "x" + height, null); - mSurfaceResized = true; mSurfaceControl.setSize(width, height); mAnimator.setPendingLayoutChanges(w.getDisplayId(), WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index bf34a74..6031906 100644 --- a/services/core/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp @@ -187,96 +187,6 @@ static int set_addresses(const char *name, const char *addresses) return count; } -static int set_routes(const char *name, const char *routes) -{ - int index = get_interface_index(name); - if (index < 0) { - return index; - } - - rtentry rt4; - memset(&rt4, 0, sizeof(rt4)); - rt4.rt_dev = (char *)name; - rt4.rt_flags = RTF_UP; - rt4.rt_dst.sa_family = AF_INET; - rt4.rt_genmask.sa_family = AF_INET; - - in6_rtmsg rt6; - memset(&rt6, 0, sizeof(rt6)); - rt6.rtmsg_ifindex = index; - rt6.rtmsg_flags = RTF_UP; - - char address[65]; - int prefix; - int chars; - int count = 0; - - while (sscanf(routes, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { - routes += chars; - - if (strchr(address, ':')) { - // Add an IPv6 route. - if (inet_pton(AF_INET6, address, &rt6.rtmsg_dst) != 1 || - prefix < 0 || prefix > 128) { - count = BAD_ARGUMENT; - break; - } - - rt6.rtmsg_dst_len = prefix ? prefix : 1; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - rt6.rtmsg_dst.s6_addr[0] ^= 0x80; - if (ioctl(inet6, SIOCADDRT, &rt6) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } else { - // Add an IPv4 route. - if (inet_pton(AF_INET, address, as_in_addr(&rt4.rt_dst)) != 1 || - prefix < 0 || prefix > 32) { - count = BAD_ARGUMENT; - break; - } - - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0x80000000; - *as_in_addr(&rt4.rt_genmask) = htonl(mask); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - if (!prefix) { - // Split the route instead of replacing the default route. - *as_in_addr(&rt4.rt_dst) ^= htonl(0x80000000); - if (ioctl(inet4, SIOCADDRT, &rt4) && errno != EEXIST) { - count = SYSTEM_ERROR; - break; - } - } - } - ALOGD("Route added on %s: %s/%d", name, address, prefix); - ++count; - } - - if (count == BAD_ARGUMENT) { - ALOGE("Invalid route: %s/%d", address, prefix); - } else if (count == SYSTEM_ERROR) { - ALOGE("Cannot add route: %s/%d: %s", - address, prefix, strerror(errno)); - } else if (*routes) { - ALOGE("Invalid route: %s", routes); - count = BAD_ARGUMENT; - } - - return count; -} - static int reset_interface(const char *name) { ifreq ifr4; @@ -366,39 +276,6 @@ error: return count; } -static jint setRoutes(JNIEnv *env, jobject thiz, jstring jName, - jstring jRoutes) -{ - const char *name = NULL; - const char *routes = NULL; - int count = -1; - - name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - goto error; - } - routes = jRoutes ? env->GetStringUTFChars(jRoutes, NULL) : NULL; - if (!routes) { - jniThrowNullPointerException(env, "routes"); - goto error; - } - count = set_routes(name, routes); - if (count < 0) { - throwException(env, count, "Cannot set route"); - count = -1; - } - -error: - if (name) { - env->ReleaseStringUTFChars(jName, name); - } - if (routes) { - env->ReleaseStringUTFChars(jRoutes, routes); - } - return count; -} - static void reset(JNIEnv *env, jobject thiz, jstring jName) { const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; @@ -430,7 +307,6 @@ static JNINativeMethod gMethods[] = { {"jniCreate", "(I)I", (void *)create}, {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, - {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes}, {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, }; diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp index 71a7aa6..47f83d0 100644 --- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp +++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp @@ -95,7 +95,7 @@ public: break; case HDMI_EVENT_HOT_PLUG: mEvent.hotplug.connected = event.hotplug.connected; - mEvent.hotplug.port = event.hotplug.port; + mEvent.hotplug.port_id = event.hotplug.port_id; break; default: // TODO: add more type whenever new type is introduced. @@ -162,7 +162,7 @@ private: void propagateHotplugEvent(const hotplug_event_t& event) { // Note that this method should be called in service thread. JNIEnv* env = AndroidRuntime::getJNIEnv(); - jint port = event.port; + jint port = event.port_id; jboolean connected = (jboolean) event.connected; env->CallVoidMethod(mController->getCallbacksObj(), gHdmiCecControllerClassInfo.handleHotplug, port, connected); @@ -248,7 +248,7 @@ jobjectArray HdmiCecController::getPortInfos() { hdmi_port_info* info = &ports[i]; jboolean cecSupported = (jboolean) info->cec_supported; jboolean arcSupported = (jboolean) info->arc_supported; - jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_num, info->type, + jobject infoObj = env->NewObject(hdmiPortInfo, ctor, info->port_id, info->type, info->physical_address, cecSupported, mhlSupported, arcSupported); env->SetObjectArrayElement(res, i, infoObj); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 61f09f6..5445dc0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3592,6 +3592,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public boolean switchUser(ComponentName who, UserHandle userHandle) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + long id = Binder.clearCallingIdentity(); + try { + int userId = UserHandle.USER_OWNER; + if (userHandle != null) { + userId = userHandle.getIdentifier(); + } + return ActivityManagerNative.getDefault().switchUser(userId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Couldn't switch user", e); + return false; + } finally { + restoreCallingIdentity(id); + } + } + } + + @Override public Bundle getApplicationRestrictions(ComponentName who, String packageName) { final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java index 7a7fa07..cb8da70 100644 --- a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java @@ -63,8 +63,9 @@ public class TaskStoreTest extends AndroidTestCase { .setBackoffCriteria(initialBackoff, JobInfo.BackoffPolicy.EXPONENTIAL) .setOverrideDeadline(runByMillis) .setMinimumLatency(runFromMillis) + .setIsPersisted(true) .build(); - final JobStatus ts = new JobStatus(task, SOME_UID, true /* persisted */); + final JobStatus ts = new JobStatus(task, SOME_UID); mTaskStoreUnderTest.add(ts); Thread.sleep(IO_WAIT); // Manually load tasks from xml file. @@ -89,15 +90,17 @@ public class TaskStoreTest extends AndroidTestCase { .setRequiresDeviceIdle(true) .setPeriodic(10000L) .setRequiresCharging(true) + .setIsPersisted(true) .build(); final JobInfo task2 = new Builder(12, mComponent) .setMinimumLatency(5000L) .setBackoffCriteria(15000L, JobInfo.BackoffPolicy.LINEAR) .setOverrideDeadline(30000L) .setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED) + .setIsPersisted(true) .build(); - final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID, true /* persisted */); - final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID, true /* persisted */); + final JobStatus taskStatus1 = new JobStatus(task1, SOME_UID); + final JobStatus taskStatus2 = new JobStatus(task2, SOME_UID); mTaskStoreUnderTest.add(taskStatus1); mTaskStoreUnderTest.add(taskStatus2); Thread.sleep(IO_WAIT); @@ -128,7 +131,8 @@ public class TaskStoreTest extends AndroidTestCase { JobInfo.Builder b = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) - .setRequiresCharging(true); + .setRequiresCharging(true) + .setIsPersisted(true); PersistableBundle extras = new PersistableBundle(); extras.putDouble("hello", 3.2); @@ -136,7 +140,7 @@ public class TaskStoreTest extends AndroidTestCase { extras.putInt("into", 3); b.setExtras(extras); final JobInfo task = b.build(); - JobStatus taskStatus = new JobStatus(task, SOME_UID, true /* persisted */); + JobStatus taskStatus = new JobStatus(task, SOME_UID); mTaskStoreUnderTest.add(taskStatus); Thread.sleep(IO_WAIT); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 16afc8f..85042f7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.hardware.soundtrigger.KeyphraseSoundModel; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -35,6 +36,7 @@ import android.provider.Settings; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; import android.util.Slog; + import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; @@ -44,6 +46,7 @@ import com.android.server.UiThread; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; /** @@ -53,6 +56,10 @@ public class VoiceInteractionManagerService extends SystemService { static final String TAG = "VoiceInteractionManagerService"; + // TODO: Add descriptive error codes. + public static final int STATUS_ERROR = -1; + public static final int STATUS_OK = 1; + final Context mContext; final ContentResolver mResolver; @@ -231,6 +238,59 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels( + IVoiceInteractionService service) { + // Allow the call if this is the current voice interaction service + // or the caller holds the MANAGE_VOICE_KEYPHRASES permission. + synchronized (this) { + boolean permissionGranted = + mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + == PackageManager.PERMISSION_GRANTED; + boolean currentVoiceInteractionService = service != null + && mImpl != null + && mImpl.mService != null + && service.asBinder() == mImpl.mService.asBinder(); + + if (!permissionGranted && !currentVoiceInteractionService) { + if (!currentVoiceInteractionService) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + if (!permissionGranted) { + throw new SecurityException("Caller does not hold the permission " + + Manifest.permission.MANAGE_VOICE_KEYPHRASES); + } + } + + final long caller = Binder.clearCallingIdentity(); + try { + // TODO: Add the implementation here. + return null; + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public int updateKeyphraseSoundModel(KeyphraseSoundModel model) { + synchronized (this) { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Caller does not hold the permission " + + Manifest.permission.MANAGE_VOICE_KEYPHRASES); + } + final long caller = Binder.clearCallingIdentity(); + try { + // TODO: Add the implementation here. + return VoiceInteractionManagerService.STATUS_ERROR; + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 8d96fb3..1f40c26 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -42,6 +41,7 @@ import android.service.voice.VoiceInteractionServiceInfo; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManager; + import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; diff --git a/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java index 2e0152a..924526e 100644 --- a/telecomm/java/android/telecomm/CallCapabilities.java +++ b/telecomm/java/android/telecomm/CallCapabilities.java @@ -42,6 +42,12 @@ public final class CallCapabilities { /** Call supports generic conference mode. */ public static final int GENERIC_CONFERENCE = 0x00000080; + /** Local device supports video telephony. */ + public static final int SUPPORTS_VT_LOCAL = 0x00000100; + + /** Remote device supports video telephony. */ + public static final int SUPPORTS_VT_REMOTE = 0x00000200; + public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE; @@ -72,6 +78,12 @@ public final class CallCapabilities { if ((capabilities & GENERIC_CONFERENCE) != 0) { builder.append(" GENERIC_CONFERENCE"); } + if ((capabilities & SUPPORTS_VT_LOCAL) != 0) { + builder.append(" SUPPORTS_VT_LOCAL"); + } + if ((capabilities & SUPPORTS_VT_REMOTE) != 0) { + builder.append(" SUPPORTS_VT_REMOTE"); + } builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java index 2fd001a..05b0062 100644 --- a/telecomm/java/android/telecomm/Connection.java +++ b/telecomm/java/android/telecomm/Connection.java @@ -112,6 +112,7 @@ public abstract class Connection { private boolean mRequestingRingback = false; private int mCallCapabilities; private Connection mParentConnection; + private CallVideoProvider mCallVideoProvider; private boolean mAudioModeIsVoip; private StatusHints mStatusHints; @@ -349,11 +350,16 @@ public abstract class Connection { * @param callVideoProvider The call video provider. */ public final void setCallVideoProvider(CallVideoProvider callVideoProvider) { + mCallVideoProvider = callVideoProvider; for (Listener l : mListeners) { l.onSetCallVideoProvider(this, callVideoProvider); } } + public final CallVideoProvider getCallVideoProvider() { + return mCallVideoProvider; + } + /** * Sets state to disconnected. * diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index 1bc184b..d0ad0cc 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.telecomm.CallVideoProvider; import com.android.internal.os.SomeArgs; import com.android.internal.telecomm.IConnectionService; @@ -688,6 +689,12 @@ public abstract class ConnectionService extends Service { mIdByConnection.put(connection, callId); connection.addConnectionListener(mConnectionListener); onConnectionAdded(connection); + + // Trigger listeners for properties set before connection listener was added. + CallVideoProvider callVideoProvider = connection.getCallVideoProvider(); + if (callVideoProvider != null) { + connection.setCallVideoProvider(callVideoProvider); + } } private void removeConnection(Connection connection) { diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java index b60e7c6..f71544b 100644 --- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java @@ -28,6 +28,7 @@ import com.android.internal.telecomm.RemoteServiceCallback; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; @@ -61,9 +62,12 @@ final class ConnectionServiceAdapter implements DeathRecipient { /** ${inheritDoc} */ @Override public void binderDied() { - for (IConnectionServiceAdapter adapter : mAdapters) { + Iterator<IConnectionServiceAdapter> it = mAdapters.iterator(); + while (it.hasNext()) { + IConnectionServiceAdapter adapter = it.next(); if (!adapter.asBinder().isBinderAlive()) { - removeAdapter(adapter); + it.remove(); + adapter.asBinder().unlinkToDeath(this, 0); } } } diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml new file mode 100644 index 0000000..a3f0447 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_drawable25.xml @@ -0,0 +1,93 @@ +<!-- + Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + + <size + android:height="64dp" + android:width="64dp" /> + + <viewport + android:viewportHeight="400" + android:viewportWidth="400" /> + + <group + android:name="FirstLevelGroup" + android:alpha="0.9" + android:translateX="100.0" + android:translateY="0.0" > + <group + android:name="SecondLevelGroup1" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FF00FF00" + android:pathData="@string/rectangle200" /> + + <group + android:name="ThridLevelGroup1" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FF0000FF" + android:pathData="@string/rectangle200" /> + </group> + <group + android:name="ThridLevelGroup2" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF000000" + android:pathData="@string/rectangle200" /> + </group> + </group> + <group + android:name="SecondLevelGroup2" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF0000FF" + android:pathData="@string/rectangle200" /> + + <group + android:name="ThridLevelGroup3" + android:alpha="0.9" + android:translateX="-100.0" + android:translateY="50.0" > + <path + android:fill="#FFFF0000" + android:pathData="@string/rectangle200" /> + </group> + <group + android:name="ThridLevelGroup4" + android:alpha="0.8" + android:translateX="100.0" + android:translateY="50.0" > + <path + android:fill="#FF00FF00" + android:pathData="@string/rectangle200" /> + </group> + </group> + + <path + android:fill="#FFFF0000" + android:pathData="@string/rectangle200" /> + </group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml index 54cffa8..a550549 100644 --- a/tests/VectorDrawableTest/res/values/strings.xml +++ b/tests/VectorDrawableTest/res/values/strings.xml @@ -24,5 +24,5 @@ <string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string> <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string> <string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string> - + <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string> </resources>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java index 814deb8..e8b6952 100644 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java @@ -52,6 +52,7 @@ public class VectorDrawablePerformance extends Activity { R.drawable.vector_drawable22, R.drawable.vector_drawable23, R.drawable.vector_drawable24, + R.drawable.vector_drawable25, }; @Override diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index 00c2c64..db43be3 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -32,7 +32,7 @@ public class MainInteractionService extends VoiceInteractionService { Log.i(TAG, "Creating " + this); Log.i(TAG, "Keyphrase enrollment error? " + getKeyphraseEnrollmentInfo().getParseError()); Log.i(TAG, "Keyphrase enrollment meta-data: " - + Arrays.toString(getKeyphraseEnrollmentInfo().getKeyphrases())); + + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata())); } @Override diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index e83eed7..22ba924 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -18,6 +18,7 @@ package android.net.wifi; import android.net.wifi.BatchedScanResult; import android.net.wifi.BatchedScanSettings; +import android.net.wifi.WifiAdapter; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.ScanSettings; @@ -25,6 +26,7 @@ import android.net.wifi.WifiChannel; import android.net.wifi.ScanResult; import android.net.DhcpInfo; + import android.os.Messenger; import android.os.WorkSource; @@ -35,6 +37,8 @@ import android.os.WorkSource; */ interface IWifiManager { + List<WifiAdapter> getAdaptors(); + List<WifiConfiguration> getConfiguredNetworks(); int addOrUpdateNetwork(in WifiConfiguration config); diff --git a/wifi/java/android/net/wifi/WifiAdapter.aidl b/wifi/java/android/net/wifi/WifiAdapter.aidl new file mode 100644 index 0000000..931da92 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiAdapter.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable WifiAdapter; diff --git a/wifi/java/android/net/wifi/WifiAdapter.java b/wifi/java/android/net/wifi/WifiAdapter.java new file mode 100644 index 0000000..f6ee730 --- /dev/null +++ b/wifi/java/android/net/wifi/WifiAdapter.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class WifiAdapter implements Parcelable { + + /* Keep this list in sync with wifi_hal.h */ + public static final int WIFI_FEATURE_INFRA = 0x0001; // Basic infrastructure mode + public static final int WIFI_FEATURE_INFRA_5G = 0x0002; // Support for 5 GHz Band + public static final int WIFI_FEATURE_PASSPOINT = 0x0004; // Support for GAS/ANQP + public static final int WIFI_FEATURE_P2P = 0x0008; // Wifi-Direct + public static final int WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010; // Soft AP + public static final int WIFI_FEATURE_SCANNER = 0x0020; // WifiScanner APIs + public static final int WIFI_FEATURE_NAN = 0x0040; // Neighbor Awareness Networking + public static final int WIFI_FEATURE_D2D_RTT = 0x0080; // Device-to-device RTT + public static final int WIFI_FEATURE_D2AP_RTT = 0x0100; // Device-to-AP RTT + public static final int WIFI_FEATURE_BATCH_SCAN = 0x0200; // Batched Scan (deprecated) + public static final int WIFI_FEATURE_PNO = 0x0400; // Preferred network offload + public static final int WIFI_FEATURE_ADDITIONAL_STA = 0x0800; // Support for two STAs + public static final int WIFI_FEATURE_TDLS = 0x1000; // Tunnel directed link setup + public static final int WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000; // Support for TDLS off channel + public static final int WIFI_FEATURE_EPR = 0x4000; // Enhanced power reporting + + private String name; + private int supportedFeatures; + + public WifiAdapter(String name, int supportedFeatures) { + this.name = name; + this.supportedFeatures = supportedFeatures; + } + + public String getName() { + return name; + } + + private int getSupportedFeatures() { + return supportedFeatures; + } + + private boolean isFeatureSupported(int feature) { + return (supportedFeatures & feature) == feature; + } + + public boolean isPasspointSupported() { + return isFeatureSupported(WIFI_FEATURE_PASSPOINT); + } + + public boolean isWifiDirectSupported() { + return isFeatureSupported(WIFI_FEATURE_P2P); + } + + public boolean isMobileHotstpoSupported() { + return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT); + } + + public boolean isWifiScannerSupported() { + return isFeatureSupported(WIFI_FEATURE_SCANNER); + } + + public boolean isNanSupported() { + return isFeatureSupported(WIFI_FEATURE_NAN); + } + + public boolean isDeviceToDeviceRttSupported() { + return isFeatureSupported(WIFI_FEATURE_D2D_RTT); + } + + public boolean isDeviceToApRttSupported() { + return isFeatureSupported(WIFI_FEATURE_D2AP_RTT); + } + + public boolean isPreferredNetworkOffloadSupported() { + return isFeatureSupported(WIFI_FEATURE_PNO); + } + + public boolean isAdditionalStaSupported() { + return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA); + } + + public boolean isTdlsSupported() { + return isFeatureSupported(WIFI_FEATURE_TDLS); + } + + public boolean isOffChannelTdlsSupported() { + return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL); + } + + public boolean isEnhancedPowerReportingSupported() { + return isFeatureSupported(WIFI_FEATURE_EPR); + } + + /* Parcelable implementation */ + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeInt(supportedFeatures); + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<WifiAdapter> CREATOR = + new Creator<WifiAdapter>() { + public WifiAdapter createFromParcel(Parcel in) { + WifiAdapter adaptor = new WifiAdapter(in.readString(), in.readInt()); + return adaptor; + } + + public WifiAdapter[] newArray(int size) { + return new WifiAdapter[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index e99ea35..f9a9e7d 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -568,6 +568,17 @@ public class WifiManager { } /** + * @hide + */ + public List<WifiAdapter> getAdaptors() { + try { + return mService.getAdaptors(); + } catch (RemoteException e) { + return null; + } + } + + /** * Return a list of all the networks configured in the supplicant. * Not all fields of WifiConfiguration are returned. Only the following * fields are filled in: diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 21b700d..f3294bb 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -16,6 +16,7 @@ package android.net.wifi; +import android.annotation.SystemApi; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; @@ -42,6 +43,7 @@ import java.util.concurrent.CountDownLatch; * .WIFI_SCANNING_SERVICE)}. * @hide */ +@SystemApi public class WifiScanner { /** no band specified; use channel list instead */ @@ -61,7 +63,7 @@ public class WifiScanner { public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ /** Minimum supported scanning period */ - public static final int MIN_SCAN_PERIOD_MS = 2000; /* minimum supported period */ + public static final int MIN_SCAN_PERIOD_MS = 1000; /* minimum supported period */ /** Maximum supported scanning period */ public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ @@ -78,6 +80,7 @@ public class WifiScanner { * Generic action callback invocation interface * @hide */ + @SystemApi public static interface ActionListener { public void onSuccess(); public void onFailure(int reason, String description); @@ -138,7 +141,7 @@ public class WifiScanner { public int band; /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ public ChannelSpec[] channels; - /** period of background scan; in millisecond */ + /** period of background scan; in millisecond, 0 => single shot scan */ public int periodInMs; /** must have a valid REPORT_EVENT value */ public int reportEvents; @@ -267,6 +270,7 @@ public class WifiScanner { /** @hide */ public void scan(ScanSettings settings, ScanListener listener) { validateChannel(); + settings.periodInMs = 0; sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings); } @@ -313,6 +317,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public static class WifiChangeSettings implements Parcelable { public int rssiSampleSize; /* sample size for RSSI averaging */ public int lostApSampleSize; /* samples to confirm AP's loss */ @@ -443,6 +448,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public void configureWifiChange(WifiChangeSettings settings) { validateChannel(); sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); @@ -457,6 +463,7 @@ public class WifiScanner { } /** @hide */ + @SystemApi public static class HotlistSettings implements Parcelable { public HotspotInfo[] hotspotInfos; public int apLostThreshold; |