summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt163
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java5
-rw-r--r--core/java/android/app/ActionBar.java27
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/app/UiAutomation.java1
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java18
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--core/java/android/app/job/JobInfo.java31
-rw-r--r--core/java/android/content/Context.java1
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java32
-rw-r--r--core/java/android/content/pm/LauncherApps.java116
-rw-r--r--core/java/android/content/pm/PackageManager.java5
-rw-r--r--core/java/android/content/pm/PackageParser.java174
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java11
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java3
-rw-r--r--core/java/android/hardware/camera2/DngCreator.java351
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java2
-rw-r--r--core/java/android/hardware/camera2/legacy/PerfMeasurement.java309
-rw-r--r--core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java101
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableBlackLevelPattern.java76
-rw-r--r--core/java/android/hardware/camera2/params/BlackLevelPattern.java128
-rw-r--r--core/java/android/hardware/camera2/params/RggbChannelVector.java8
-rw-r--r--core/java/android/hardware/hdmi/HdmiClient.java28
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl2
-rw-r--r--core/java/android/hardware/location/GeofenceHardware.java4
-rw-r--r--core/java/android/hardware/soundtrigger/DspInfo.java (renamed from core/java/android/service/voice/DspInfo.java)5
-rw-r--r--core/java/android/hardware/soundtrigger/Keyphrase.aidl4
-rw-r--r--core/java/android/hardware/soundtrigger/Keyphrase.java101
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java (renamed from core/java/android/service/voice/KeyphraseEnrollmentInfo.java)64
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseMetadata.java60
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseSoundModel.aidl4
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseSoundModel.java68
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java2
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerHelper.java217
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java25
-rw-r--r--core/java/android/net/ConnectivityManager.java56
-rw-r--r--core/java/android/net/IConnectivityManager.aidl6
-rw-r--r--core/java/android/net/NetworkAgent.java30
-rw-r--r--core/java/android/net/NetworkCapabilities.java96
-rw-r--r--core/java/android/net/NetworkRequest.java19
-rw-r--r--core/java/android/net/NetworkUtils.java7
-rw-r--r--core/java/android/net/UidRange.aidl24
-rw-r--r--core/java/android/net/UidRange.java102
-rw-r--r--core/java/android/net/VpnService.java172
-rw-r--r--core/java/android/os/BatteryStats.java2
-rw-r--r--core/java/android/os/INetworkManagementService.aidl42
-rw-r--r--core/java/android/os/Process.java14
-rw-r--r--core/java/android/print/PrintDocumentInfo.java8
-rw-r--r--core/java/android/provider/CallLog.java50
-rw-r--r--core/java/android/provider/ContactsContract.java33
-rw-r--r--core/java/android/provider/SearchIndexablesContract.java19
-rw-r--r--core/java/android/provider/Settings.java55
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java97
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java14
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java270
-rw-r--r--core/java/android/service/voice/KeyphraseInfo.java27
-rw-r--r--core/java/android/service/voice/SoundTriggerManager.java73
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java54
-rw-r--r--core/java/android/text/SpannableStringBuilder.java15
-rw-r--r--core/java/android/view/HardwareRenderer.java24
-rw-r--r--core/java/android/view/ThreadedRenderer.java31
-rw-r--r--core/java/android/view/View.java3
-rw-r--r--core/java/android/view/ViewGroup.java57
-rw-r--r--core/java/android/view/ViewRootImpl.java45
-rw-r--r--core/java/android/view/WindowManager.java24
-rw-r--r--core/java/android/view/WindowManagerInternal.java5
-rw-r--r--core/java/android/view/WindowManagerPolicy.java6
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java15
-rw-r--r--core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl19
-rw-r--r--core/java/android/view/inputmethod/CursorAnchorInfoRequest.java203
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java11
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java140
-rw-r--r--core/java/android/widget/AbsListView.java6
-rw-r--r--core/java/android/widget/ActionMenuView.java44
-rw-r--r--core/java/android/widget/Editor.java5
-rw-r--r--core/java/android/widget/PopupWindow.java46
-rw-r--r--core/java/android/widget/TextView.java21
-rw-r--r--core/java/android/widget/Toolbar.java56
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl13
-rw-r--r--core/java/com/android/internal/app/ToolbarActionBar.java10
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java17
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java37
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java34
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl3
-rw-r--r--core/java/com/android/internal/view/IInputContextCallback.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodClient.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl1
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java35
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java15
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java3
-rw-r--r--core/java/com/android/internal/view/menu/MenuPresenter.java3
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java25
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java7
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java10
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java15
-rw-r--r--core/jni/Android.mk4
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_hardware_camera2_DngCreator.cpp1293
-rw-r--r--core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp335
-rw-r--r--core/jni/android_net_NetUtils.cpp6
-rw-r--r--core/jni/android_util_Process.cpp13
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp9
-rw-r--r--core/res/res/drawable/popup_background_material.xml13
-rw-r--r--core/res/res/values/attrs.xml18
-rw-r--r--core/res/res/values/attrs_manifest.xml5
-rw-r--r--core/res/res/values/public.xml3
-rw-r--r--core/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/styles_material.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/values/themes.xml1
-rw-r--r--core/res/res/values/themes_holo.xml1
-rw-r--r--core/res/res/values/themes_material.xml7
-rw-r--r--core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/apks/keyset/uA/AndroidManifest.xml13
-rw-r--r--core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml18
-rw-r--r--core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/apks/keyset/uB/AndroidManifest.xml13
-rwxr-xr-xcore/tests/inputmethodtests/run_core_inputmethod_test.sh2
-rw-r--r--core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java101
-rw-r--r--data/fonts/fallback_fonts.xml4
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java3
-rw-r--r--graphics/java/android/graphics/drawable/MaterialProgressDrawable.java548
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java256
-rw-r--r--libs/hwui/Program.cpp13
-rw-r--r--media/java/android/media/AudioManager.java6
-rw-r--r--media/java/android/media/AudioService.java124
-rw-r--r--media/java/android/media/AudioSystem.java8
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/tv/ITvInputClient.aidl5
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSessionCallback.aidl5
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java26
-rw-r--r--media/java/android/media/tv/TvInputManager.java163
-rw-r--r--media/java/android/media/tv/TvInputService.java126
-rw-r--r--media/java/android/media/tv/TvTrackInfo.aidl19
-rw-r--r--media/java/android/media/tv/TvTrackInfo.java292
-rw-r--r--media/java/android/media/tv/TvView.java141
-rw-r--r--packages/CaptivePortalLogin/Android.mk11
-rw-r--r--packages/CaptivePortalLogin/AndroidManifest.xml36
-rw-r--r--packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml20
-rw-r--r--packages/CaptivePortalLogin/res/menu/captive_portal_login.xml15
-rw-r--r--packages/CaptivePortalLogin/res/values/dimens.xml7
-rw-r--r--packages/CaptivePortalLogin/res/values/strings.xml9
-rw-r--r--packages/CaptivePortalLogin/res/values/styles.xml20
-rw-r--r--packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java160
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded_header.xml2
-rw-r--r--packages/SystemUI/res/layout/zen_mode_panel.xml12
-rw-r--r--packages/SystemUI/res/values/dimens.xml8
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java18
-rw-r--r--phone/java/android/phone/PhoneManager.java54
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindowManager.java20
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java7
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java429
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java30
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java106
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java73
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityManagerService.java58
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkAgentInfo.java4
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java384
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java7
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java431
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java3
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java10
-rw-r--r--services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/DeviceSelectAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/FeatureAction.java4
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java28
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java92
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java109
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiConfig.java50
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java172
-rw-r--r--services/core/java/com/android/server/hdmi/HotplugDetectionAction.java9
-rw-r--r--services/core/java/com/android/server/hdmi/NewDeviceAction.java4
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java4
-rw-r--r--services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java3
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioAction.java13
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java8
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java2
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java15
-rw-r--r--services/core/java/com/android/server/job/JobStore.java8
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java21
-rw-r--r--services/core/java/com/android/server/location/FlpHardwareProvider.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java8
-rw-r--r--services/core/java/com/android/server/pm/KeySetManagerService.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java202
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java7
-rw-r--r--services/core/java/com/android/server/pm/Settings.java18
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java100
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java194
-rw-r--r--services/core/jni/com_android_server_connectivity_Vpn.cpp124
-rw-r--r--services/core/jni/com_android_server_hdmi_HdmiCecController.cpp6
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java14
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java60
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java2
-rw-r--r--telecomm/java/android/telecomm/CallCapabilities.java12
-rw-r--r--telecomm/java/android/telecomm/Connection.java6
-rw-r--r--telecomm/java/android/telecomm/ConnectionService.java7
-rw-r--r--telecomm/java/android/telecomm/ConnectionServiceAdapter.java8
-rw-r--r--tests/VectorDrawableTest/res/drawable/vector_drawable25.xml93
-rw-r--r--tests/VectorDrawableTest/res/values/strings.xml2
-rw-r--r--tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java1
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java2
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl4
-rw-r--r--wifi/java/android/net/wifi/WifiAdapter.aidl19
-rw-r--r--wifi/java/android/net/wifi/WifiAdapter.java135
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java11
-rw-r--r--wifi/java/android/net/wifi/WifiScanner.java11
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 &lt;Vendor Command With ID&gt; do not
+ * include the first 3 bytes (vendor ID).
+ * @param hasVendorId {@code true} if the command type will be &lt;Vendor Command With ID&gt;.
+ * {@code false} if the command will be &lt;Vendor Command&gt;
+ */
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
+ * &lt;Text View On&gt; or &lt;Image View On&gt;. (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
- * &quot;Do not disturb&quot;.
+ * 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, &copyright,
- 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*>(&timestamp));
+
+ 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 &lt;Standby&gt;
*/
- 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 &lt;Standby&gt;
+ * @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;