Split PackageInstaller app into installation and permissions management
The two components were mostly independant for a long time. Since
I1e80a3f5e63d02b3859ecf74af21ca4c61f96874 the installation flow does
not grant any permissions anymore and the last connection between these
parts was broken.
The new app "com.android.packageinstaller" in
frameworks/base/packages/PackageInstaller will only handle (side load)
package installtion and uninstallation.
The exisiting app will be renamed to "com.android.permissioncontroller"
and only handle permission granting and permission management.
This change does only minimal cleanup cleanup. In particularly it does
not move any files in the old permissions controller. This is to not
disturb other features currently in development.
This change set also updates the make files to install the two apps on
the appropriate devices.
Further the permisson policy xmls need to be updated to point to the
right packages.
Test: Installed + uninstalled packages
Granted permissions + managed permissions
GtsPackageInstallTestCases
GtsNoPermissionTestCases
GtsNoPermissionTestCases25
GtsPackageInstallerTapjackingTestCases
GtsPackageUninstallTestCases
Change-Id: I2d3796b837fc0049e712c82a990907f305c8febf
@@ -5,6 +5,7 @@ checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPL
|
||||
core/tests/coretests/src/android/
|
||||
packages/PrintRecommendationService/
|
||||
packages/PrintSpooler/
|
||||
packages/PackageInstaller/
|
||||
services/print/
|
||||
services/usb/
|
||||
telephony/
|
||||
|
||||
@@ -48,6 +48,7 @@ public abstract class PackageManagerInternal {
|
||||
public static final int PACKAGE_VERIFIER = 3;
|
||||
public static final int PACKAGE_BROWSER = 4;
|
||||
public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
|
||||
public static final int PACKAGE_PERMISSION_CONTROLLER = 6;
|
||||
@IntDef(value = {
|
||||
PACKAGE_SYSTEM,
|
||||
PACKAGE_SETUP_WIZARD,
|
||||
@@ -55,6 +56,7 @@ public abstract class PackageManagerInternal {
|
||||
PACKAGE_VERIFIER,
|
||||
PACKAGE_BROWSER,
|
||||
PACKAGE_SYSTEM_TEXT_CLASSIFIER,
|
||||
PACKAGE_PERMISSION_CONTROLLER,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface KnownPackage {}
|
||||
|
||||
@@ -38,7 +38,7 @@ platform cert need to be included, as apps signed with the platform cert are exe
|
||||
<hidden-api-whitelisted-app package="com.android.launcher3" />
|
||||
<hidden-api-whitelisted-app package="com.android.mtp" />
|
||||
<hidden-api-whitelisted-app package="com.android.musicfx" />
|
||||
<hidden-api-whitelisted-app package="com.android.packageinstaller" />
|
||||
<hidden-api-whitelisted-app package="com.android.permissioncontroller" />
|
||||
<hidden-api-whitelisted-app package="com.android.printservice.recommendation" />
|
||||
<hidden-api-whitelisted-app package="com.android.printspooler" />
|
||||
<hidden-api-whitelisted-app package="com.android.providers.blockednumber" />
|
||||
|
||||
@@ -133,13 +133,18 @@ applications that come with the platform
|
||||
</privapp-permissions>
|
||||
|
||||
<privapp-permissions package="com.android.packageinstaller">
|
||||
<permission name="android.permission.CLEAR_APP_CACHE"/>
|
||||
<permission name="android.permission.DELETE_PACKAGES"/>
|
||||
<permission name="android.permission.INSTALL_PACKAGES"/>
|
||||
<permission name="android.permission.USE_RESERVED_DISK"/>
|
||||
<permission name="android.permission.MANAGE_USERS"/>
|
||||
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
|
||||
</privapp-permissions>
|
||||
|
||||
<privapp-permissions package="com.android.permissioncontroller">
|
||||
<permission name="android.permission.CLEAR_APP_CACHE"/>
|
||||
<permission name="android.permission.MANAGE_USERS"/>
|
||||
<permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
|
||||
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
|
||||
<permission name="android.permission.USE_RESERVED_DISK"/>
|
||||
</privapp-permissions>
|
||||
|
||||
<privapp-permissions package="com.android.phone">
|
||||
|
||||
@@ -9,42 +9,16 @@ LOCAL_SRC_FILES := \
|
||||
$(call all-java-files-under, src)
|
||||
|
||||
LOCAL_STATIC_ANDROID_LIBRARIES += \
|
||||
androidx.car_car \
|
||||
androidx.design_design \
|
||||
androidx.transition_transition \
|
||||
androidx.core_core \
|
||||
androidx.media_media \
|
||||
androidx.legacy_legacy-support-core-utils \
|
||||
androidx.legacy_legacy-support-core-ui \
|
||||
androidx.fragment_fragment \
|
||||
androidx.appcompat_appcompat \
|
||||
androidx.preference_preference \
|
||||
androidx.recyclerview_recyclerview \
|
||||
androidx.legacy_legacy-preference-v14 \
|
||||
androidx.leanback_leanback \
|
||||
androidx.leanback_leanback-preference \
|
||||
SettingsLib
|
||||
androidx.leanback_leanback
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
xz-java \
|
||||
androidx.annotation_annotation
|
||||
|
||||
LOCAL_PACKAGE_NAME := PackageInstaller
|
||||
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
LOCAL_PRIVILEGED_MODULE := true
|
||||
|
||||
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
|
||||
# Comment for now unitl all private API dependencies are removed
|
||||
# LOCAL_SDK_VERSION := system_current
|
||||
LOCAL_PRIVATE_PLATFORM_APIS := true
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
ifeq (PackageInstaller,$(LOCAL_PACKAGE_NAME))
|
||||
# Use the following include to make our test apk.
|
||||
ifeq (,$(ONE_SHOT_MAKEFILE))
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -1,35 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.packageinstaller" coreApp="true">
|
||||
|
||||
<original-package android:name="com.android.packageinstaller" />
|
||||
package="com.android.packageinstaller">
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_USERS" />
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
|
||||
<uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_USERS" />
|
||||
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
|
||||
<uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
|
||||
<uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
<uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
|
||||
<uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.KILL_UID" />
|
||||
<uses-permission android:name="android.permission.MANAGE_APP_OPS_RESTRICTIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" />
|
||||
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
|
||||
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
|
||||
<uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
|
||||
|
||||
<application android:name=".PackageInstallerApplication"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="false"
|
||||
@@ -131,56 +116,11 @@
|
||||
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name=".permission.ui.GrantPermissionsActivity"
|
||||
android:configChanges="keyboardHidden|screenSize"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/GrantPermissions"
|
||||
android:visibleToInstantApps="true">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".permission.ui.ManagePermissionsActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/app_permissions"
|
||||
android:theme="@style/Settings"
|
||||
android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.intent.action.MANAGE_PERMISSIONS" />
|
||||
<action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" />
|
||||
<action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".permission.ui.ReviewPermissionsActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/Settings.NoActionBar"
|
||||
android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.intent.action.REVIEW_PERMISSIONS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".permission.ui.OverlayWarningDialog"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar" />
|
||||
|
||||
<!-- Wearable Components -->
|
||||
<service android:name=".wear.WearPackageInstallerService"
|
||||
android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
|
||||
android:exported="true"/>
|
||||
|
||||
<service android:name=".permission.service.RuntimePermissionPresenterServiceImpl"
|
||||
android:permission="android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE">
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.permissionpresenterservice.RuntimePermissionPresenterService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider android:name=".wear.WearPackageIconProvider"
|
||||
android:authorities="com.google.android.packageinstaller.wear.provider"
|
||||
android:grantUriPermissions="true"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[Builtin Hooks]
|
||||
xmllint = true
|
||||
commit_msg_changeid_field = true
|
||||
|
||||
[Hook Scripts]
|
||||
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
|
||||
strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
|
||||
@@ -1,8 +0,0 @@
|
||||
# The support library contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
|
||||
-keep class androidx.preference.Preference* {
|
||||
*;
|
||||
}
|
||||
-dontwarn androidx.core.**
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="false"
|
||||
android:alpha="?android:disabledAlpha"
|
||||
android:color="?android:colorButtonNormal" />
|
||||
<item android:color="?android:colorAccent" />
|
||||
</selector>
|
||||
|
Before Width: | Height: | Size: 707 B |
|
Before Width: | Height: | Size: 651 B |
|
Before Width: | Height: | Size: 153 B |
|
Before Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 505 B |
|
Before Width: | Height: | Size: 157 B |
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/grant_permissions_white_text_alpha_100">
|
||||
<item android:drawable="@drawable/grant_permissions_action_item_background" />
|
||||
</ripple>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/grant_permissions_action_item_shape" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/grant_permissions_action_item_shape" />
|
||||
<!-- no default background is specified, making the button transparent when not active -->
|
||||
</selector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetRight="1dp"
|
||||
android:insetBottom="1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp" />
|
||||
<solid android:color="@color/grant_permissions_focus_highlight" />
|
||||
<padding android:left="8dp"
|
||||
android:top="4dp"
|
||||
android:right="8dp"
|
||||
android:bottom="4dp" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<ripple android:color="?android:colorControlHighlight" />
|
||||
</item>
|
||||
<item>
|
||||
<ripple android:color="?android:colorControlHighlight">
|
||||
<item>
|
||||
<shape android:shape="oval"
|
||||
android:tint="?android:colorButtonNormal">
|
||||
<solid android:color="@android:color/white" />
|
||||
<size android:width="@dimen/diag_button_size"
|
||||
android:height="@dimen/diag_button_size" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<ripple android:color="?android:colorAccent" />
|
||||
</item>
|
||||
<item>
|
||||
<ripple android:color="?android:colorControlHighlight">
|
||||
<item>
|
||||
<shape android:shape="oval"
|
||||
android:tint="@color/btn_colored_background_material">
|
||||
<solid android:color="@android:color/white" />
|
||||
<size android:width="@dimen/diag_button_size"
|
||||
android:height="@dimen/diag_button_size" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
<item>
|
||||
<shape android:shape="oval" android:tint="?android:colorButtonNormal">
|
||||
<solid android:color="@android:color/white" />
|
||||
<size android:width="40dp" android:height="40dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?android:colorControlHighlight">
|
||||
<item>
|
||||
<shape android:shape="oval" android:tint="@color/btn_colored_background_material">
|
||||
<solid android:color="@android:color/white" />
|
||||
<size android:width="40dp" android:height="40dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:drawable="@drawable/action_negative_bg" />
|
||||
<item android:drawable="@drawable/ic_cc_clear" android:gravity="center" />
|
||||
</layer-list>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:drawable="@drawable/action_positive_bg" />
|
||||
<item android:drawable="@drawable/ic_cc_checkmark" android:gravity="center" />
|
||||
</layer-list>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:drawable="@drawable/action_negative_bg" />
|
||||
<item android:drawable="@drawable/ic_cc_deny" android:gravity="center" />
|
||||
</layer-list>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 166 B |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/car_card_ripple_background"
|
||||
android:radius="90dp">
|
||||
<item android:id="@android:id/mask"
|
||||
android:drawable="@drawable/rectangle_ripple_mask" />
|
||||
</ripple>
|
||||
@@ -1,30 +0,0 @@
|
||||
<!--
|
||||
~ Copyright (C) 2018 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
|
||||
-->
|
||||
|
||||
<!-- This Icon is used in as the back icon on ActionBar. ActionBar hard code the icon layout and
|
||||
~ does not provide a way to customize it. Here to center the icon in action bar, we make up
|
||||
~ the margin by add the extra space in the icon itself -->
|
||||
<vector
|
||||
android:height="@dimen/car_primary_icon_size"
|
||||
android:width="@dimen/car_primary_icon_size"
|
||||
android:tint="@color/car_accent"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||
</vector>
|
||||
@@ -1,25 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:pathData="M1,21l22,0L12,2L1,21zM13,18l-2,0l0,-2l2,0L13,18zM13,14l-2,0l0,-4l2,0L13,14z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</vector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_fail"
|
||||
android:tint="?android:attr/colorControlNormal" />
|
||||
@@ -1,24 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="?android:attr/colorAccent"
|
||||
android:pathData="M11.0,17.0l2.0,0.0l0.0,-6.0l-2.0,0.0l0.0,6.0zm1.0,-15.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zm0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0zM11.0,9.0l2.0,0.0L13.0,7.0l-2.0,0.0l0.0,2.0z"/>
|
||||
</vector>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7
|
||||
7v2h14V7H7z" />
|
||||
<path
|
||||
android:pathData="M0 0h24v24H0z" />
|
||||
</vector>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="48.0"
|
||||
android:viewportHeight="48.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M26.0,14.0l-4.0,0.0l0.0,4.0l4.0,0.0l0.0,-4.0zm0.0,8.0l-4.0,0.0l0.0,12.0l4.0,0.0L26.0,22.0zm8.0,-19.98L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
|
||||
</vector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_success"
|
||||
android:tint="?android:attr/colorControlNormal" />
|
||||
@@ -1,24 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="48.0"
|
||||
android:viewportHeight="48.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M6.0,18.0l28.0,0.0l0.0,-4.0L6.0,14.0l0.0,4.0zm0.0,8.0l28.0,0.0l0.0,-4.0L6.0,22.0l0.0,4.0zm0.0,8.0l28.0,0.0l0.0,-4.0L6.0,30.0l0.0,4.0zm32.0,0.0l4.0,0.0l0.0,-4.0l-4.0,0.0l0.0,4.0zm0.0,-20.0l0.0,4.0l4.0,0.0l0.0,-4.0l-4.0,0.0zm0.0,12.0l4.0,0.0l0.0,-4.0l-4.0,0.0l0.0,4.0z"/>
|
||||
</vector>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/car_radius_1" />
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
@@ -1,91 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/action_dialog_padding_left"
|
||||
android:paddingRight="@dimen/action_dialog_padding_right"
|
||||
android:paddingTop="@dimen/action_dialog_padding_top"
|
||||
android:paddingBottom="@dimen/action_dialog_padding_bottom"
|
||||
android:background="@color/grant_permissions_background_color">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/permission_icon"
|
||||
android:tint="@color/grant_permissions_app_color"
|
||||
android:layout_width="@dimen/grant_permissions_app_icon_size"
|
||||
android:layout_height="@dimen/grant_permissions_app_icon_size"
|
||||
android:layout_marginTop="@dimen/grant_permissions_app_icon_margin_top"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="@dimen/action_dialog_content_margin_left"
|
||||
android:layout_marginRight="@dimen/action_dialog_content_margin_right">
|
||||
<TextView
|
||||
android:id="@+id/current_page_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/grant_permissions_app_breadcrumb_margin_bottom"
|
||||
android:textAppearance="@style/GrantPermissions.BreadcrumbText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permission_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/grant_permissions_app_title_margin_bottom"
|
||||
android:textAppearance="@style/GrantPermissions.TitleText"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="@dimen/grant_permissions_app_details_margin_bottom"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/grant_dialog_how_to_change"
|
||||
android:textAppearance="@style/GrantPermissions.BodyText" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="@dimen/action_dialog_actions_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/action_dialog_actions_margin_left"
|
||||
android:layout_marginTop="@dimen/action_dialog_actions_margin_top">
|
||||
<Button
|
||||
android:id="@+id/permission_allow_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_allow"
|
||||
style="@style/GrantPermissions.ActionItem" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_deny_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_deny"
|
||||
style="@style/GrantPermissions.ActionItem" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_deny_dont_ask_again_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_deny_dont_ask_again"
|
||||
style="@style/GrantPermissions.ActionItem" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,47 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2016 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/defaultBrandColor"
|
||||
android:elevation="@dimen/lb_preference_decor_title_container_elevation"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/decor_title_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/defaultBrandColor"
|
||||
android:elevation="@dimen/lb_preference_decor_title_container_elevation"
|
||||
android:transitionGroup="false"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/decor_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/lb_preference_decor_title_text_height"
|
||||
android:layout_marginTop="@dimen/lb_preference_decor_title_margin_top"
|
||||
android:layout_marginStart="@dimen/lb_preference_decor_title_margin_start"
|
||||
android:layout_marginEnd="@dimen/lb_preference_decor_title_margin_end"
|
||||
android:fontFamily="sans-serif-condensed"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textSize="@dimen/lb_preference_decor_title_text_size"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:filterTouchesWhenObscured="true">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/lb_settings_pane_width"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/lb_preference_decor_list_background"
|
||||
android:layout_gravity="end">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/prefs_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/no_permissions"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@android:style/TextAppearance.Large"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
@@ -1,111 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fillViewport="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:id="@android:id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/diag_preferred_padding">
|
||||
<ImageView android:id="@android:id/icon"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxHeight="24dp"
|
||||
android:maxWidth="24dp"
|
||||
android:layout_marginTop="@dimen/diag_icon_margin_top"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
android:src="@null" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView android:id="@android:id/title"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/diag_preferred_padding"
|
||||
android:paddingRight="@dimen/diag_preferred_padding"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/message"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingLeft="@dimen/diag_preferred_padding"
|
||||
android:paddingRight="@dimen/diag_preferred_padding"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Subhead"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/buttonPanel"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:id="@+id/buttonContainer"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal|top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="@dimen/diag_button_padding_bottom"
|
||||
android:paddingLeft="@dimen/diag_button_padding_horizontal"
|
||||
android:paddingRight="@dimen/diag_button_padding_horizontal"
|
||||
style="?android:attr/buttonBarStyle">
|
||||
|
||||
<ImageButton
|
||||
android:id="@android:id/button2"
|
||||
android:src="@drawable/ic_cc_clear"
|
||||
android:background="@drawable/accept_deny_dialog_negative_bg"
|
||||
android:contentDescription="@string/generic_cancel"
|
||||
android:layout_width="@dimen/diag_button_size"
|
||||
android:layout_height="@dimen/diag_button_size"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/spacer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@android:id/button1"
|
||||
android:src="@drawable/ic_cc_checkmark"
|
||||
android:background="@drawable/accept_deny_dialog_positive_bg"
|
||||
android:contentDescription="@string/generic_yes"
|
||||
android:layout_width="@dimen/diag_button_size"
|
||||
android:layout_height="@dimen/diag_button_size"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+android:id/title"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/wear_permission_review_pref_padding"
|
||||
android:paddingBottom="@dimen/wear_permission_review_pref_padding"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Title" />
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/wear_permission_review_pref_padding"
|
||||
android:orientation="vertical">
|
||||
<ImageView android:id="@+android:id/icon"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxHeight="@dimen/wear_permission_review_icon_size"
|
||||
android:maxWidth="@dimen/wear_permission_review_icon_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="centerInside" />
|
||||
<TextView android:id="@+android:id/title"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/diag_preferred_padding"
|
||||
android:paddingRight="@dimen/diag_preferred_padding"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Title" />
|
||||
</LinearLayout>
|
||||
@@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/car_app_bar_height"
|
||||
android:gravity="end|center_vertical" >
|
||||
<FrameLayout
|
||||
android:id="@+id/action_bar_icon_container"
|
||||
android:layout_width="@dimen/car_margin"
|
||||
android:layout_height="@dimen/car_app_bar_height"
|
||||
android:foreground="@drawable/button_ripple_bg"
|
||||
android:layout_alignParentStart="true">
|
||||
<ImageView
|
||||
android:layout_width="@dimen/car_primary_icon_size"
|
||||
android:layout_height="@dimen/car_primary_icon_size"
|
||||
android:tint="@color/car_tint"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_arrow_back"/>
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/car_margin"
|
||||
android:textAppearance="@style/TextAppearance.Car.Title2"
|
||||
android:text="@string/app_permissions"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/car_list_divider_height"
|
||||
android:background="@color/car_list_divider"/>
|
||||
|
||||
<androidx.car.widget.PagedListView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:showPagedListViewDivider="true"
|
||||
app:alignDividerStartTo="@id/container"
|
||||
app:gutter="both"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Position subsequent dialogs with the button bar at same height -->
|
||||
<com.android.packageinstaller.permission.ui.ManualLayoutFrame
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false">
|
||||
|
||||
<!-- In (hopefully very rare) case dialog is too high: allow scrolling -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:clipChildren="false">
|
||||
|
||||
<!-- allow some space around dialog, esp. in landscape -->
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="10"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<!-- The dialog -->
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.PermissionGrant"
|
||||
style="@*android:style/PermissionGrantDialog">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/grant_permissions_content" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Button row on bottom of dialog -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="bottom"
|
||||
style="?android:attr/buttonBarStyle">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- dummy to enforce height -->
|
||||
<Button
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
<!-- If several dialogs are shown in a row, show e.g. "1/3" -->
|
||||
<TextView
|
||||
android:id="@+id/current_page_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@*android:style/PermissionGrantIndex" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.android.packageinstaller.permission.ui.ButtonBarLayout
|
||||
android:id="@+id/button_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_more_info_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_more_info"
|
||||
android:visibility="gone"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_deny_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_deny"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_allow_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_dialog_button_allow"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
</com.android.packageinstaller.permission.ui.ButtonBarLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- allow some space around dialog, esp. in landscape -->
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="10"
|
||||
android:visibility="invisible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</com.android.packageinstaller.permission.ui.ManualLayoutFrame>
|
||||
@@ -1,105 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2018 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.
|
||||
-->
|
||||
|
||||
<!-- Title of dialog -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.PermissionGrant">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
style="@*android:style/PermissionGrantDescription">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/permission_icon"
|
||||
style="@*android:style/PermissionGrantTitleIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permission_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:attr/titleTextStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
style="@*android:style/PermissionGrantContent" >
|
||||
|
||||
<!-- Option one: Show a radio group of foreground / always / deny -->
|
||||
<RadioGroup
|
||||
android:id="@+id/foreground_or_always_radiogroup"
|
||||
android:animateLayoutChanges="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@*android:style/PermissionGrantRadioGroup" >
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/foreground_only_radio_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/allow_permission_foreground_only"
|
||||
style="@android:attr/radioButtonStyle" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/always_radio_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/allow_permission_always"
|
||||
style="@android:attr/radioButtonStyle" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/deny_dont_ask_again_radio_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/deny_permission_deny_and_dont_ask_again"
|
||||
style="@android:attr/radioButtonStyle" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<!-- Option two: Show a detailed message about the change -->
|
||||
<TextView
|
||||
android:id="@+id/detail_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@*android:style/PermissionGrantDetailMessage" />
|
||||
|
||||
<!-- Shown when detail_message and do_not_ask_checkbox are shown -->
|
||||
<Space
|
||||
android:id="@+id/detail_message_do_not_ask_checkbox_space"
|
||||
android:layout_width="match_parent"
|
||||
style="@*android:style/PermissionGrantDetailMessageSpace" />
|
||||
|
||||
<!-- Either with option two or by itself: Show a checkbox allowing to deny with
|
||||
prejudice -->
|
||||
<CheckBox
|
||||
android:id="@+id/do_not_ask_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/never_ask_again"
|
||||
style="@android:attr/checkboxStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,54 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:background="?android:attr/colorSecondary"
|
||||
android:gravity="center_vertical" >
|
||||
|
||||
<ImageView android:id="@+id/icon"
|
||||
android:layout_width="@dimen/header_subsettings_margin_start"
|
||||
android:layout_height="40dp"
|
||||
android:gravity="end"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginStart="@dimen/header_subsettings_margin_start"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/info"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="@dimen/header_subsettings_margin_end"
|
||||
android:layout_centerVertical="true"
|
||||
android:minHeight="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_info_outline"
|
||||
android:contentDescription="@string/app_permissions_info_button_label"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2012 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:gravity="center"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:padding="16dp"/>
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/loading_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:gravity="center">
|
||||
|
||||
<ProgressBar style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/loading"
|
||||
android:paddingTop="4dip"
|
||||
android:singleLine="true" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/prefs_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/no_permissions"
|
||||
android:gravity="center"
|
||||
style="?android:attr/textAppearanceLarge">
|
||||
</TextView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/loading_container" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2012 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This is the structure for the list of all permissions.
|
||||
-->
|
||||
<com.android.packageinstaller.CaffeinatedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scrollview"
|
||||
android:fillViewport="true">
|
||||
<LinearLayout
|
||||
android:id="@+id/permission_list"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingEnd="4dp"
|
||||
android:layout_height="wrap_content">
|
||||
</LinearLayout>
|
||||
</com.android.packageinstaller.CaffeinatedScrollView>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Based on frameworks/base/core/res/res/layout/preference_category_material.xml
|
||||
but has a ViewGroup at the root to make the support lib happy.-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dip"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Body2"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textColor="?android:attr/colorAccent"
|
||||
android:paddingTop="16dip" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Based off frameworks/base/core/res/res/layout/preference_material.xml
|
||||
except that this has the negative margin on the image removed
|
||||
and has a set icon size (and some padding to realign). -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@android:id/icon_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
<com.android.packageinstaller.permission.ui.PreferenceImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitCenter" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:ellipsize="marquee" />
|
||||
|
||||
<TextView android:id="@android:id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@android:id/title"
|
||||
android:layout_alignStart="@android:id/title"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="10" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@android:id/widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Based off frameworks/base/core/res/res/layout/preference_material.xml
|
||||
except that this has the negative margin on the image removed
|
||||
and has a set icon size (and some padding to align). -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@android:id/icon_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
<com.android.packageinstaller.permission.ui.PreferenceImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:scaleType="fitCenter" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView android:id="@android:id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:ellipsize="marquee" />
|
||||
|
||||
<TextView android:id="@android:id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@android:id/title"
|
||||
android:layout_alignStart="@android:id/title"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="10" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@android:id/widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end|center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,112 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginTop="32dip"
|
||||
android:layout_marginBottom="8dip"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dip"
|
||||
android:layout_marginEnd="16dip"
|
||||
android:layout_marginBottom="16dip"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="36dip"
|
||||
android:layout_height="36dip"
|
||||
android:scaleType="fitCenter">
|
||||
</ImageView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permissions_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dip"
|
||||
style="?android:attr/textAppearanceMedium">
|
||||
</TextView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/preferences_frame"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_marginStart="2dip"
|
||||
android:layout_marginEnd="2dip"
|
||||
android:layout_weight="1">
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="2dip"
|
||||
android:paddingTop="16dip">
|
||||
|
||||
<Button
|
||||
android:id="@+id/permission_more_info_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:text="@string/grant_dialog_button_more_info">
|
||||
</Button>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="invisible">
|
||||
</Space>
|
||||
|
||||
<com.android.packageinstaller.permission.ui.ButtonBarLayout
|
||||
android:id="@+id/button_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="bottom">
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:text="@string/review_button_cancel">
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continue_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_marginStart="8dip"
|
||||
android:text="@string/review_button_continue">
|
||||
</Button>
|
||||
|
||||
</com.android.packageinstaller.permission.ui.ButtonBarLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -24,16 +24,5 @@
|
||||
|
||||
<color name="lb_header_banner_color">#1f292d</color>
|
||||
|
||||
<color name="grant_permissions_background_color">#ff263238</color>
|
||||
<color name="grant_permissions_app_color">@color/grant_permissions_white_text_alpha_100</color>
|
||||
<color name="grant_permissions_progress_color">@color/grant_permissions_white_text_alpha_100</color>
|
||||
<color name="grant_permissions_title_color">@color/grant_permissions_white_text_alpha_70</color>
|
||||
<color name="grant_permissions_body_color">@color/grant_permissions_white_text_alpha_70</color>
|
||||
<color name="grant_permissions_button_color">@color/grant_permissions_white_text_alpha_100</color>
|
||||
<color name="grant_permissions_focus_highlight">#26eeeeee</color>
|
||||
|
||||
<color name="grant_permissions_white_text_alpha_100">@color/off_white</color>
|
||||
<color name="grant_permissions_white_text_alpha_70">#b2eeeeee</color>
|
||||
|
||||
<color name="off_white">#ffeeeeee</color>
|
||||
</resources>
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="grant_permissions_app_icon_size">64dp</dimen>
|
||||
<dimen name="grant_permissions_app_icon_margin_top">19dp</dimen>
|
||||
|
||||
<dimen name="grant_permissions_app_breadcrumb_margin_bottom">3dp</dimen>
|
||||
<dimen name="grant_permissions_app_title_margin_bottom">18dp</dimen>
|
||||
<dimen name="grant_permissions_app_details_margin_bottom">8dp</dimen>
|
||||
|
||||
<dimen name="action_dialog_z">16dp</dimen>
|
||||
<dimen name="action_dialog_padding_left">52dp</dimen>
|
||||
<dimen name="action_dialog_padding_right">40dp</dimen>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Title for the dialog button to deny a permission grant and never ask the user again. -->
|
||||
<string name="grant_dialog_button_deny_dont_ask_again">Deny and don\'t ask again</string>
|
||||
|
||||
<!-- Instructional text telling the user how to change permission grants later. -->
|
||||
<string name="grant_dialog_how_to_change">You can change this later in Settings > Apps</string>
|
||||
|
||||
<!-- Template for the current permission from the total number of permissions. -->
|
||||
<string name="current_permission_template">
|
||||
<xliff:g id="current_permission_index" example="1">%1$s</xliff:g> /
|
||||
<xliff:g id="permission_count" example="2">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Preference row title for showing system apps. -->
|
||||
<string name="preference_show_system_apps">Show system apps</string>
|
||||
|
||||
<!--decor title displayed as the page title for different TV permission screens-->
|
||||
<string name="app_permissions_decor_title">App permissions</string>
|
||||
<string name="manage_permissions_decor_title">App permissions</string>
|
||||
<string name="permission_apps_decor_title"><xliff:g id="permission" example="Camera">%1$s</xliff:g> permissions</string>
|
||||
<string name="additional_permissions_decor_title">Additional permissions</string>
|
||||
<string name="system_apps_decor_title"><xliff:g id="permission" example="Camera">%1$s</xliff:g> permissions</string>
|
||||
</resources>
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="PreferenceThemeOverlay.v14.Permissions">
|
||||
<item name="preferenceStyle">@style/Preference.Permissions</item>
|
||||
<item name="preferenceCategoryStyle">@style/Preference.Category.Permissions</item>
|
||||
<item name="switchPreferenceStyle">@style/Preference.SwitchPreference.Permissions</item>
|
||||
</style>
|
||||
|
||||
<style name="Preference.Permissions">
|
||||
<item name="layout">@layout/preference_permissions</item>
|
||||
</style>
|
||||
|
||||
<style name="Preference.Category.Permissions">
|
||||
<item name="layout">@layout/preference_category_material</item>
|
||||
</style>
|
||||
|
||||
<style name="Preference.SwitchPreference.Permissions">
|
||||
<item name="layout">@layout/preference_permissions_switch</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<style name="Settings" parent="Theme.Leanback">
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:backgroundDimAmount">0.8</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions" parent="Theme.Leanback">
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:windowAnimationStyle">@style/Animation.Snackbar</item>
|
||||
<item name="android:windowElevation">@dimen/action_dialog_z</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions.BreadcrumbText">
|
||||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/grant_permissions_progress_color</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions.TitleText">
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
<item name="android:textSize">24sp</item>
|
||||
<item name="android:textColor">@color/grant_permissions_title_color</item>
|
||||
<item name="android:lineSpacingMultiplier">1.221</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions.BodyText">
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/grant_permissions_body_color</item>
|
||||
<item name="android:lineSpacingMultiplier">1.465</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions.ActionItem">
|
||||
<item name="android:gravity">left|center_vertical</item>
|
||||
<item name="android:fontFamily">sans-serif-condensed</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/grant_permissions_button_color</item>
|
||||
<item name="android:lineSpacingMultiplier">1</item>
|
||||
<item name="android:background">@drawable/grant_permissions_action_item</item>
|
||||
<item name="android:paddingLeft">@dimen/action_dialog_button_padding_left</item>
|
||||
<item name="android:paddingRight">@dimen/action_dialog_button_padding_right</item>
|
||||
<item name="android:paddingTop">@dimen/action_dialog_button_padding_top</item>
|
||||
<item name="android:paddingBottom">@dimen/action_dialog_button_padding_bottom</item>
|
||||
<item name="android:minHeight">@dimen/action_dialog_button_min_height</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.Snackbar" parent="@android:style/Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/snackbar_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/snackbar_exit</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -15,21 +15,6 @@
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Title for the dialog button to deny a permission grant and never ask the user again. [CHAR LIMIT=29]-->
|
||||
<string name="grant_dialog_button_deny_dont_ask_again">Deny, don\'t ask again</string>
|
||||
|
||||
<!-- Template for the current permission from the total number of permissions. -->
|
||||
<string name="current_permission_template">
|
||||
<xliff:g id="current_permission_index" example="1">%1$s</xliff:g> /
|
||||
<xliff:g id="permission_count" example="2">%2$s</xliff:g>
|
||||
</string>
|
||||
|
||||
<!-- Preference row title for showing system apps. -->
|
||||
<string name="preference_show_system_apps">Show system apps</string>
|
||||
|
||||
<!-- Summary of a permission switch when it's enforced by policy [CHAR LIMIT=17] -->
|
||||
<string name="permission_summary_enforced_by_policy">Can\'t be changed</string>
|
||||
|
||||
<!-- Generic text to indicate a yes. [CHAR LIMIT=10] -->
|
||||
<string name="generic_yes">Yes</string>
|
||||
|
||||
|
||||
@@ -17,10 +17,4 @@
|
||||
|
||||
<resources>
|
||||
<style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
|
||||
|
||||
<style name="Settings" parent="@android:style/Theme.DeviceDefault.NoActionBar" />
|
||||
|
||||
<style name="GrantPermissions" parent="@android:style/Theme.DeviceDefault.NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -172,204 +172,11 @@
|
||||
<!-- Dialog attributes to indicate parse errors -->
|
||||
<string name="Parse_error_dlg_text">There was a problem parsing the package.</string>
|
||||
|
||||
<!-- Tab label for new permissions being added to an existing app [CHAR LIMIT=20] -->
|
||||
<string name="newPerms">New</string>
|
||||
<!-- Tab label for all permissions of an app being installed [CHAR LIMIT=20] -->
|
||||
<string name="allPerms">All</string>
|
||||
<!-- Tab label for permissions related to user privacy [CHAR LIMIT=20] -->
|
||||
<string name="privacyPerms">Privacy</string>
|
||||
<!-- Tab label for permissions related to device behavior [CHAR LIMIT=20] -->
|
||||
<string name="devicePerms">Device Access</string>
|
||||
|
||||
<!-- Body text for new tab when there are no new permissions [CHAR LIMIT=NONE] -->
|
||||
<string name="no_new_perms">This update requires no new permissions.</string>
|
||||
|
||||
<!-- Title for the dialog button to deny a permission grant. [CHAR LIMIT=15] -->
|
||||
<string name="grant_dialog_button_deny">Deny</string>
|
||||
|
||||
<!-- Title for the dialog button to get more info about a permission. [CHAR LIMIT=15] -->
|
||||
<string name="grant_dialog_button_more_info">More info</string>
|
||||
|
||||
<!-- Title for the dialog button to deny a permission grant despite a warning of implications. [CHAR LIMIT=15] -->
|
||||
<string name="grant_dialog_button_deny_anyway">Deny anyway</string>
|
||||
|
||||
<!-- Template for the current permission from the total number of permissions. [CHAR LIMIT=100] -->
|
||||
<string name="current_permission_template">
|
||||
<xliff:g id="current_permission_index" example="1">%1$s</xliff:g> of
|
||||
<xliff:g id="permission_count" example="2">%2$s</xliff:g></string>
|
||||
|
||||
<!-- Template for the warning message when an app requests a permission. [CHAR LIMIT=100] -->
|
||||
<string name="permission_warning_template">Allow
|
||||
<b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to
|
||||
<xliff:g id="action" example="do something">%2$s</xliff:g>?</string>
|
||||
|
||||
<!-- Template for the warning message when an app requests the permission to access a
|
||||
resource even while in the background (i.e. always). [CHAR LIMIT=100] -->
|
||||
<string name="permission_add_background_warning_template">Always allow
|
||||
<b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to
|
||||
<xliff:g id="action" example="do something">%2$s</xliff:g>?</string>
|
||||
|
||||
<!-- Radio button shown for permissions that can be granted either only while the app is in
|
||||
foreground or always. If this button is selected the app only gets the permission while in
|
||||
foreground [CHAR LIMIT=50] -->
|
||||
<string name="allow_permission_foreground_only">Only while using app</string>
|
||||
|
||||
<!-- Radio button shown for permissions that can be granted either only while the app is in
|
||||
foreground or always. If this button is selected the app always gets the permission (while in
|
||||
foreground _and_ while in background) [CHAR LIMIT=50] -->
|
||||
<string name="allow_permission_always">Always</string>
|
||||
|
||||
<!-- Radio button shown for permissions that can be granted either only while the app is in
|
||||
foreground or always. If this button is selected the app does not get the permission and the
|
||||
permissions will always be denied from now on [CHAR LIMIT=50] -->
|
||||
<string name="deny_permission_deny_and_dont_ask_again">Deny and don\u2019t ask again</string>
|
||||
|
||||
<!-- Template for the message how many permissions are disabled. [CHAR LIMIT=30] -->
|
||||
<string name="permission_revoked_count"><xliff:g id="count" example="2">%1$d</xliff:g> disabled</string>
|
||||
|
||||
<!-- Message that all permissions are disabled. [CHAR LIMIT=30] -->
|
||||
<string name="permission_revoked_all">all disabled</string>
|
||||
|
||||
<!-- Message that no permissions are disabled. [CHAR LIMIT=30] -->
|
||||
<string name="permission_revoked_none">none disabled</string>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<!-- Title for the dialog button to allow a permission grant. [CHAR LIMIT=15] -->
|
||||
<string name="grant_dialog_button_allow">Allow</string>
|
||||
|
||||
<!-- Breadcrumb for page of managing application permissions [CHAR LIMIT=50] -->
|
||||
<string name="app_permissions_breadcrumb">Apps</string>
|
||||
|
||||
<!-- Title for page of managing application permissions [CHAR LIMIT=30] -->
|
||||
<string name="app_permissions">App permissions</string>
|
||||
<!-- Checkbox that allows user to not be questioned about this permission
|
||||
request again [CHAR LIMIT=30] -->
|
||||
<string name="never_ask_again">Don\'t ask again</string>
|
||||
|
||||
<!-- Label when app requests no permissions [CHAR LIMIT=30] -->
|
||||
<string name="no_permissions">No permissions</string>
|
||||
|
||||
<!-- Label for button that leads to more permissions [CHAR LIMIT=40] -->
|
||||
<string name="additional_permissions">Additional permissions</string>
|
||||
|
||||
<!-- Accessibility label for button opening the app-info when clicked [CHAR LIMIT=none] -->
|
||||
<string name="app_permissions_info_button_label">Open app info</string>
|
||||
|
||||
<!-- Description of how many more permissions to view on next page [CHAR LIMIT=30] -->
|
||||
<plurals name="additional_permissions_more">
|
||||
<item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> more</item>
|
||||
<item quantity="other"><xliff:g id="count" example="2">%1$d</xliff:g> more</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Warning for turning off permissions on older apps [CHAR LIMIT=none] -->
|
||||
<string name="old_sdk_deny_warning">This app was designed for an older version of Android. Denying permission may cause it to no longer function as intended.</string>
|
||||
|
||||
<!-- The default description of a permission, i.e. what it does. [CHAR LIMIT=40] -->
|
||||
<string name="default_permission_description">perform an unknown action</string>
|
||||
|
||||
<!-- Summary of number of apps currently granted a single permission [CHAR LIMIT=45] -->
|
||||
<string name="app_permissions_group_summary"><xliff:g id="count" example="10">%1$d</xliff:g> of <xliff:g id="count" example="10">%2$d</xliff:g> apps allowed</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Menu for manage permissions to control whether system apps are shown -->
|
||||
<string name="menu_show_system">Show system</string>
|
||||
<!-- [CHAR LIMIT=NONE] Menu for manage permissions to control whether system apps are hidden -->
|
||||
<string name="menu_hide_system">Hide system</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Label when no apps requesting this permission -->
|
||||
<string name="no_apps">No apps</string>
|
||||
|
||||
<!-- [CHAR LIMIT=30] Title of button that leads to location settings -->
|
||||
<string name="location_settings">Location Settings</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Warning about how this app cannot have location permission disabled -->
|
||||
<string name="location_warning"><xliff:g id="app_name" example="Package Installer">%1$s</xliff:g> is a provider of location services for this device. Location access can be modified from location settings.</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Warning message when turning off permission for system apps -->
|
||||
<string name="system_warning">If you deny this permission, basic features of your device may no longer function as intended.</string>
|
||||
|
||||
<!-- [CHAR LIMIT=NONE] Summary of a permission switch when it's enforced by policy -->
|
||||
<string name="permission_summary_enforced_by_policy">Enforced by policy</string>
|
||||
|
||||
<!-- [CHAR LIMIT=60] Summary of a permission switch when the background access is denied by policy -->
|
||||
<string name="permission_summary_disabled_by_policy_background_only">Background access disabled by policy</string>
|
||||
|
||||
<!-- [CHAR LIMIT=60] Summary of a permission switch when the background access is enabled by policy -->
|
||||
<string name="permission_summary_enabled_by_policy_background_only">Background access enabled by policy</string>
|
||||
|
||||
<!-- [CHAR LIMIT=60] Summary of a permission switch when the background access is enabled by policy -->
|
||||
<string name="permission_summary_enabled_by_policy_foreground_only">Foreground access enabled by policy</string>
|
||||
|
||||
<!-- [CHAR LIMIT=60] Summary of a permission switch when it's enforced by an administrator -->
|
||||
<string name="permission_summary_enforced_by_admin">Controlled by admin</string>
|
||||
|
||||
<string-array name="background_access_chooser_dialog_choices">
|
||||
<item>@string/permission_access_always</item>
|
||||
<item>@string/permission_access_only_foreground</item>
|
||||
<item>@string/permission_access_never</item>
|
||||
</string-array>
|
||||
|
||||
<!-- [CHAR LIMIT=30] App can always (when app is in foreground or background) access the resource protected by the permission -->
|
||||
<string name="permission_access_always">Always</string>
|
||||
|
||||
<!-- [CHAR LIMIT=30] App can only access the resource protected by the permission while app is in foregroud -->
|
||||
<string name="permission_access_only_foreground">Only while using app</string>
|
||||
|
||||
<!-- [CHAR LIMIT=30] App can never access the resource protected by the permission (Not while app is in foregound and not while app is in background) -->
|
||||
<string name="permission_access_never">Never</string>
|
||||
|
||||
<!-- Text displayed until loading is done [CHAR LIMIT=50] -->
|
||||
<string name="loading">Loading\u2026</string>
|
||||
|
||||
<!-- [CHAR LIMIT=45] Title of all permissions settings -->
|
||||
<string name="all_permissions">All permissions</string>
|
||||
<!-- [CHAR LIMIT=45] Group of permissions granted to app automatically when installed. -->
|
||||
<string name="other_permissions">Other app capabilities</string>
|
||||
|
||||
<!-- Title of the permission dialog for accessibility purposes- spoken to the user. [CHAR LIMIT=none] -->
|
||||
<string name="permission_request_title">Permission request</string>
|
||||
|
||||
<!-- Title for the dialog that warns the user they need to turn off screen overlays
|
||||
before permissions can be changed. [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_overlay_title">Screen overlay detected</string>
|
||||
|
||||
<!-- Message for the dialog that warns the user they need to turn off screen overlays
|
||||
before permissions can be changed. The "Settings > Apps" conveys to the user to
|
||||
go to Settings and click on apps, this may need updates in RTL languages. [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_overlay_message">To change this permission setting, you first have to turn off the screen overlay from Settings \u003e Apps</string>
|
||||
|
||||
<!-- Button for the dialog that warns the user they need to turn off screen overlays
|
||||
before permissions can be changed. [CHAR LIMIT=NONE] -->
|
||||
<string name="screen_overlay_button">Open settings</string>
|
||||
|
||||
<!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=30] -->
|
||||
<string name="wear_not_allowed_dlg_title">Android Wear</string>
|
||||
<!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=none] -->
|
||||
<string name="wear_not_allowed_dlg_text">Install/Uninstall actions not supported on Wear.</string>
|
||||
|
||||
<!-- Review of runtime permissions for legacy apps -->
|
||||
|
||||
<!-- Template for the screen title when app permissions are reviewed on install. [CHAR LIMIT=none] -->
|
||||
<string name="permission_review_title_template_install">Choose what to allow
|
||||
<b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> to access</string>
|
||||
|
||||
<!-- Template for the screen title when app permissions are reviewed on update. [CHAR LIMIT=none] -->
|
||||
<string name="permission_review_title_template_update">
|
||||
<b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g></b> has been updated.
|
||||
Choose what to allow this app to access.</string>
|
||||
|
||||
<!-- Title for the dialog button to cancel the detailed permission review. [CHAR LIMIT=15] -->
|
||||
<string name="review_button_cancel">Cancel</string>
|
||||
|
||||
<!-- Title for the dialog button to continue accepting the detailed permission review. [CHAR LIMIT=15] -->
|
||||
<string name="review_button_continue">Continue</string>
|
||||
|
||||
<!-- Title for the category listing the new permissions used by an app. [CHAR LIMIT=30] -->
|
||||
<string name="new_permissions_category">New permissions</string>
|
||||
|
||||
<!-- Title for the category listing the current permissions used by an app. [CHAR LIMIT=30] -->
|
||||
<string name="current_permissions_category">Current permissions</string>
|
||||
|
||||
<!-- Message that the app to be installed is being staged [CHAR LIMIT=50] -->
|
||||
<string name="message_staging">Staging app…</string>
|
||||
|
||||
|
||||
@@ -17,28 +17,6 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Settings"
|
||||
parent="@android:style/Theme.DeviceDefault.Settings">
|
||||
</style>
|
||||
|
||||
<style name="CarSettingTheme" parent="Theme.Car.Light.NoActionBar">
|
||||
<item name="android:background">@color/car_card</item>
|
||||
</style>
|
||||
|
||||
<style name="Settings.NoActionBar" parent="@style/Settings">
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.SettingsBase</item>
|
||||
</style>
|
||||
|
||||
<style name="GrantPermissions"
|
||||
parent="@*android:style/Theme.DeviceDefault.Light.Panel.PermissionGrantApp">
|
||||
<!-- The following attributes change the behavior of the dialog, hence they should not be
|
||||
themed -->
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowCloseOnTouchOutside">@*android:bool/config_closeDialogWhenTouchOutside</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogWhenLarge"
|
||||
parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
|
||||
<item name="android:textAppearanceMedium">@style/MediumText</item>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2016 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.
|
||||
-->
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/app_permissions"
|
||||
android:orderingFromXml="true">
|
||||
<Preference
|
||||
android:key="no_permissions"
|
||||
android:title="@string/no_permissions" />
|
||||
</PreferenceScreen>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/all_permissions">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="other_perms"
|
||||
android:title="@string/other_permissions" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 androidx.wear.ble.view;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.StyleRes;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
/**
|
||||
* A dialog to display a title, a message, and/or an icon with a positive and a negative button.
|
||||
*
|
||||
* <p>The buttons are hidden away unless there is a listener attached to the button. Since there's
|
||||
* no click listener attached by default, the buttons are hidden be default.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class AcceptDenyDialog extends Dialog {
|
||||
/** Icon at the top of the dialog. */
|
||||
protected ImageView mIcon;
|
||||
/** Title at the top of the dialog. */
|
||||
protected TextView mTitle;
|
||||
/** Message content of the dialog. */
|
||||
protected TextView mMessage;
|
||||
/** Panel containing the buttons. */
|
||||
protected View mButtonPanel;
|
||||
/** Positive button in the button panel. */
|
||||
protected ImageButton mPositiveButton;
|
||||
/** Negative button in the button panel. */
|
||||
protected ImageButton mNegativeButton;
|
||||
/**
|
||||
* Click listener for the positive button. Positive button should hide if this is <code>null
|
||||
* </code>.
|
||||
*/
|
||||
protected DialogInterface.OnClickListener mPositiveButtonListener;
|
||||
/**
|
||||
* Click listener for the negative button. Negative button should hide if this is <code>null
|
||||
* </code>.
|
||||
*/
|
||||
protected DialogInterface.OnClickListener mNegativeButtonListener;
|
||||
/** Spacer between the positive and negative button. Hidden if one button is hidden. */
|
||||
protected View mSpacer;
|
||||
|
||||
private final View.OnClickListener mButtonHandler = (v) -> {
|
||||
if (v == mPositiveButton && mPositiveButtonListener != null) {
|
||||
mPositiveButtonListener.onClick(this, DialogInterface.BUTTON_POSITIVE);
|
||||
dismiss();
|
||||
} else if (v == mNegativeButton && mNegativeButtonListener != null) {
|
||||
mNegativeButtonListener.onClick(this, DialogInterface.BUTTON_NEGATIVE);
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
public AcceptDenyDialog(Context context) {
|
||||
this(context, 0 /* use default context theme */);
|
||||
}
|
||||
|
||||
public AcceptDenyDialog(Context context, @StyleRes int themeResId) {
|
||||
super(context, themeResId);
|
||||
|
||||
setContentView(R.layout.accept_deny_dialog);
|
||||
|
||||
mTitle = (TextView) findViewById(android.R.id.title);
|
||||
mMessage = (TextView) findViewById(android.R.id.message);
|
||||
mIcon = (ImageView) findViewById(android.R.id.icon);
|
||||
mPositiveButton = (ImageButton) findViewById(android.R.id.button1);
|
||||
mPositiveButton.setOnClickListener(mButtonHandler);
|
||||
mNegativeButton = (ImageButton) findViewById(android.R.id.button2);
|
||||
mNegativeButton.setOnClickListener(mButtonHandler);
|
||||
mSpacer = (Space) findViewById(R.id.spacer);
|
||||
mButtonPanel = findViewById(R.id.buttonPanel);
|
||||
}
|
||||
|
||||
public ImageButton getButton(int whichButton) {
|
||||
switch (whichButton) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
return mPositiveButton;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
return mNegativeButton;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setIcon(Drawable icon) {
|
||||
mIcon.setVisibility(icon == null ? View.GONE : View.VISIBLE);
|
||||
mIcon.setImageDrawable(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resId the resourceId of the drawable to use as the icon or 0 if you don't want an icon.
|
||||
*/
|
||||
public void setIcon(int resId) {
|
||||
mIcon.setVisibility(resId == 0 ? View.GONE : View.VISIBLE);
|
||||
mIcon.setImageResource(resId);
|
||||
}
|
||||
|
||||
/** @param message the content message text of the dialog. */
|
||||
public void setMessage(CharSequence message) {
|
||||
mMessage.setText(message);
|
||||
mMessage.setVisibility(message == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
/** @param title the title text of the dialog. */
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mTitle.setText(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a click listener for a button.
|
||||
*
|
||||
* <p>Will hide button bar if all buttons are hidden (i.e. their click listeners are <code>null
|
||||
* </code>).
|
||||
*
|
||||
* @param whichButton {@link DialogInterface.BUTTON_POSITIVE} or {@link
|
||||
* DialogInterface.BUTTON_NEGATIVE}
|
||||
* @param listener the listener to set for the button. Hide button if <code>null</code>.
|
||||
*/
|
||||
public void setButton(int whichButton, DialogInterface.OnClickListener listener) {
|
||||
switch (whichButton) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
mPositiveButtonListener = listener;
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mNegativeButtonListener = listener;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
mSpacer.setVisibility(mPositiveButtonListener == null || mNegativeButtonListener == null
|
||||
? View.GONE : View.INVISIBLE);
|
||||
mPositiveButton.setVisibility(
|
||||
mPositiveButtonListener == null ? View.GONE : View.VISIBLE);
|
||||
mNegativeButton.setVisibility(
|
||||
mNegativeButtonListener == null ? View.GONE : View.VISIBLE);
|
||||
mButtonPanel.setVisibility(
|
||||
mPositiveButtonListener == null && mNegativeButtonListener == null
|
||||
? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for <code>setButton(DialogInterface.BUTTON_POSITIVE, listener)</code>.
|
||||
*
|
||||
* @param listener the listener for the positive button.
|
||||
*/
|
||||
public void setPositiveButton(DialogInterface.OnClickListener listener) {
|
||||
setButton(DialogInterface.BUTTON_POSITIVE, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for <code>setButton(DialogInterface.BUTTON_NEGATIVE, listener)</code>.
|
||||
*
|
||||
* @param listener the listener for the positive button.
|
||||
*/
|
||||
public void setNegativeButton(DialogInterface.OnClickListener listener) {
|
||||
setButton(DialogInterface.BUTTON_NEGATIVE, listener);
|
||||
}
|
||||
}
|
||||
@@ -1,603 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.wear.ble.view;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.Objects;
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
/**
|
||||
* An image view surrounded by a circle.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class CircledImageView extends View {
|
||||
|
||||
private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
|
||||
|
||||
private Drawable mDrawable;
|
||||
|
||||
private final RectF mOval;
|
||||
private final Paint mPaint;
|
||||
|
||||
private ColorStateList mCircleColor;
|
||||
|
||||
private float mCircleRadius;
|
||||
private float mCircleRadiusPercent;
|
||||
|
||||
private float mCircleRadiusPressed;
|
||||
private float mCircleRadiusPressedPercent;
|
||||
|
||||
private float mRadiusInset;
|
||||
|
||||
private int mCircleBorderColor;
|
||||
|
||||
private float mCircleBorderWidth;
|
||||
private float mProgress = 1f;
|
||||
private final float mShadowWidth;
|
||||
|
||||
private float mShadowVisibility;
|
||||
private boolean mCircleHidden = false;
|
||||
|
||||
private float mInitialCircleRadius;
|
||||
|
||||
private boolean mPressed = false;
|
||||
|
||||
private boolean mProgressIndeterminate;
|
||||
private ProgressDrawable mIndeterminateDrawable;
|
||||
private Rect mIndeterminateBounds = new Rect();
|
||||
private long mColorChangeAnimationDurationMs = 0;
|
||||
|
||||
private float mImageCirclePercentage = 1f;
|
||||
private float mImageHorizontalOffcenterPercentage = 0f;
|
||||
private Integer mImageTint;
|
||||
|
||||
private final Drawable.Callback mDrawableCallback = new Drawable.Callback() {
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable drawable) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
|
||||
// Not needed.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
|
||||
// Not needed.
|
||||
}
|
||||
};
|
||||
|
||||
private int mCurrentColor;
|
||||
|
||||
private final AnimatorUpdateListener mAnimationListener = new AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
int color = (int) animation.getAnimatedValue();
|
||||
if (color != CircledImageView.this.mCurrentColor) {
|
||||
CircledImageView.this.mCurrentColor = color;
|
||||
CircledImageView.this.invalidate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private ValueAnimator mColorAnimator;
|
||||
|
||||
public CircledImageView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircledImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CircledImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircledImageView);
|
||||
mDrawable = a.getDrawable(R.styleable.CircledImageView_android_src);
|
||||
|
||||
mCircleColor = a.getColorStateList(R.styleable.CircledImageView_circle_color);
|
||||
if (mCircleColor == null) {
|
||||
mCircleColor = ColorStateList.valueOf(android.R.color.darker_gray);
|
||||
}
|
||||
|
||||
mCircleRadius = a.getDimension(
|
||||
R.styleable.CircledImageView_circle_radius, 0);
|
||||
mInitialCircleRadius = mCircleRadius;
|
||||
mCircleRadiusPressed = a.getDimension(
|
||||
R.styleable.CircledImageView_circle_radius_pressed, mCircleRadius);
|
||||
mCircleBorderColor = a.getColor(
|
||||
R.styleable.CircledImageView_circle_border_color, Color.BLACK);
|
||||
mCircleBorderWidth = a.getDimension(R.styleable.CircledImageView_circle_border_width, 0);
|
||||
|
||||
if (mCircleBorderWidth > 0) {
|
||||
mRadiusInset += mCircleBorderWidth;
|
||||
}
|
||||
|
||||
float circlePadding = a.getDimension(R.styleable.CircledImageView_circle_padding, 0);
|
||||
if (circlePadding > 0) {
|
||||
mRadiusInset += circlePadding;
|
||||
}
|
||||
mShadowWidth = a.getDimension(R.styleable.CircledImageView_shadow_width, 0);
|
||||
|
||||
mImageCirclePercentage = a.getFloat(
|
||||
R.styleable.CircledImageView_image_circle_percentage, 0f);
|
||||
|
||||
mImageHorizontalOffcenterPercentage = a.getFloat(
|
||||
R.styleable.CircledImageView_image_horizontal_offcenter_percentage, 0f);
|
||||
|
||||
if (a.hasValue(R.styleable.CircledImageView_image_tint)) {
|
||||
mImageTint = a.getColor(R.styleable.CircledImageView_image_tint, 0);
|
||||
}
|
||||
|
||||
mCircleRadiusPercent = a.getFraction(R.styleable.CircledImageView_circle_radius_percent,
|
||||
1, 1, 0f);
|
||||
|
||||
mCircleRadiusPressedPercent = a.getFraction(
|
||||
R.styleable.CircledImageView_circle_radius_pressed_percent, 1, 1,
|
||||
mCircleRadiusPercent);
|
||||
|
||||
a.recycle();
|
||||
|
||||
mOval = new RectF();
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
|
||||
mIndeterminateDrawable = new ProgressDrawable();
|
||||
// {@link #mDrawableCallback} must be retained as a member, as Drawable callback
|
||||
// is held by weak reference, we must retain it for it to continue to be called.
|
||||
mIndeterminateDrawable.setCallback(mDrawableCallback);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
setColorForCurrentState();
|
||||
}
|
||||
|
||||
public void setCircleHidden(boolean circleHidden) {
|
||||
if (circleHidden != mCircleHidden) {
|
||||
mCircleHidden = circleHidden;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean onSetAlpha(int alpha) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
int paddingLeft = getPaddingLeft();
|
||||
int paddingTop = getPaddingTop();
|
||||
|
||||
|
||||
float circleRadius = mPressed ? getCircleRadiusPressed() : getCircleRadius();
|
||||
if (mShadowWidth > 0 && mShadowVisibility > 0) {
|
||||
// First let's find the center of the view.
|
||||
mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
|
||||
getHeight() - getPaddingBottom());
|
||||
// Having the center, lets make the shadow start beyond the circled and possibly the
|
||||
// border.
|
||||
final float radius = circleRadius + mCircleBorderWidth +
|
||||
mShadowWidth * mShadowVisibility;
|
||||
mPaint.setColor(Color.BLACK);
|
||||
mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
|
||||
mPaint.setStyle(Style.FILL);
|
||||
// TODO: precalc and pre-allocate this
|
||||
mPaint.setShader(new RadialGradient(mOval.centerX(), mOval.centerY(), radius,
|
||||
new int[]{Color.BLACK, Color.TRANSPARENT}, new float[]{0.6f, 1f},
|
||||
Shader.TileMode.MIRROR));
|
||||
canvas.drawCircle(mOval.centerX(), mOval.centerY(), radius, mPaint);
|
||||
mPaint.setShader(null);
|
||||
}
|
||||
if (mCircleBorderWidth > 0) {
|
||||
// First let's find the center of the view.
|
||||
mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
|
||||
getHeight() - getPaddingBottom());
|
||||
// Having the center, lets make the border meet the circle.
|
||||
mOval.set(mOval.centerX() - circleRadius, mOval.centerY() - circleRadius,
|
||||
mOval.centerX() + circleRadius, mOval.centerY() + circleRadius);
|
||||
mPaint.setColor(mCircleBorderColor);
|
||||
// {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
|
||||
// color. {@link #Paint.setPaint} will clear any previously set alpha value.
|
||||
mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
|
||||
mPaint.setStyle(Style.STROKE);
|
||||
mPaint.setStrokeWidth(mCircleBorderWidth);
|
||||
|
||||
if (mProgressIndeterminate) {
|
||||
mOval.roundOut(mIndeterminateBounds);
|
||||
mIndeterminateDrawable.setBounds(mIndeterminateBounds);
|
||||
mIndeterminateDrawable.setRingColor(mCircleBorderColor);
|
||||
mIndeterminateDrawable.setRingWidth(mCircleBorderWidth);
|
||||
mIndeterminateDrawable.draw(canvas);
|
||||
} else {
|
||||
canvas.drawArc(mOval, -90, 360 * mProgress, false, mPaint);
|
||||
}
|
||||
}
|
||||
if (!mCircleHidden) {
|
||||
mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
|
||||
getHeight() - getPaddingBottom());
|
||||
// {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
|
||||
// color. {@link #Paint.setPaint} will clear any previously set alpha value.
|
||||
mPaint.setColor(mCurrentColor);
|
||||
mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
|
||||
|
||||
mPaint.setStyle(Style.FILL);
|
||||
float centerX = mOval.centerX();
|
||||
float centerY = mOval.centerY();
|
||||
|
||||
canvas.drawCircle(centerX, centerY, circleRadius, mPaint);
|
||||
}
|
||||
|
||||
if (mDrawable != null) {
|
||||
mDrawable.setAlpha(Math.round(getAlpha() * 255));
|
||||
|
||||
if (mImageTint != null) {
|
||||
mDrawable.setTint(mImageTint);
|
||||
}
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
private void setColorForCurrentState() {
|
||||
int newColor = mCircleColor.getColorForState(getDrawableState(),
|
||||
mCircleColor.getDefaultColor());
|
||||
if (mColorChangeAnimationDurationMs > 0) {
|
||||
if (mColorAnimator != null) {
|
||||
mColorAnimator.cancel();
|
||||
} else {
|
||||
mColorAnimator = new ValueAnimator();
|
||||
}
|
||||
mColorAnimator.setIntValues(new int[] {
|
||||
mCurrentColor, newColor });
|
||||
mColorAnimator.setEvaluator(ARGB_EVALUATOR);
|
||||
mColorAnimator.setDuration(mColorChangeAnimationDurationMs);
|
||||
mColorAnimator.addUpdateListener(this.mAnimationListener);
|
||||
mColorAnimator.start();
|
||||
} else {
|
||||
if (newColor != mCurrentColor) {
|
||||
mCurrentColor = newColor;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
|
||||
final float radius = getCircleRadius() + mCircleBorderWidth +
|
||||
mShadowWidth * mShadowVisibility;
|
||||
float desiredWidth = radius * 2;
|
||||
float desiredHeight = radius * 2;
|
||||
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
if (widthMode == MeasureSpec.EXACTLY) {
|
||||
width = widthSize;
|
||||
} else if (widthMode == MeasureSpec.AT_MOST) {
|
||||
width = (int) Math.min(desiredWidth, widthSize);
|
||||
} else {
|
||||
width = (int) desiredWidth;
|
||||
}
|
||||
|
||||
if (heightMode == MeasureSpec.EXACTLY) {
|
||||
height = heightSize;
|
||||
} else if (heightMode == MeasureSpec.AT_MOST) {
|
||||
height = (int) Math.min(desiredHeight, heightSize);
|
||||
} else {
|
||||
height = (int) desiredHeight;
|
||||
}
|
||||
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
if (mDrawable != null) {
|
||||
// Retrieve the sizes of the drawable and the view.
|
||||
final int nativeDrawableWidth = mDrawable.getIntrinsicWidth();
|
||||
final int nativeDrawableHeight = mDrawable.getIntrinsicHeight();
|
||||
final int viewWidth = getMeasuredWidth();
|
||||
final int viewHeight = getMeasuredHeight();
|
||||
final float imageCirclePercentage = mImageCirclePercentage > 0
|
||||
? mImageCirclePercentage : 1;
|
||||
|
||||
final float scaleFactor = Math.min(1f,
|
||||
Math.min(
|
||||
(float) nativeDrawableWidth != 0
|
||||
? imageCirclePercentage * viewWidth / nativeDrawableWidth : 1,
|
||||
(float) nativeDrawableHeight != 0
|
||||
? imageCirclePercentage
|
||||
* viewHeight / nativeDrawableHeight : 1));
|
||||
|
||||
// Scale the drawable down to fit the view, if needed.
|
||||
final int drawableWidth = Math.round(scaleFactor * nativeDrawableWidth);
|
||||
final int drawableHeight = Math.round(scaleFactor * nativeDrawableHeight);
|
||||
|
||||
// Center the drawable within the view.
|
||||
final int drawableLeft = (viewWidth - drawableWidth) / 2
|
||||
+ Math.round(mImageHorizontalOffcenterPercentage * drawableWidth);
|
||||
final int drawableTop = (viewHeight - drawableHeight) / 2;
|
||||
|
||||
mDrawable.setBounds(drawableLeft, drawableTop, drawableLeft + drawableWidth,
|
||||
drawableTop + drawableHeight);
|
||||
}
|
||||
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
if (drawable != mDrawable) {
|
||||
final Drawable existingDrawable = mDrawable;
|
||||
mDrawable = drawable;
|
||||
|
||||
final boolean skipLayout = drawable != null
|
||||
&& existingDrawable != null
|
||||
&& existingDrawable.getIntrinsicHeight() == drawable.getIntrinsicHeight()
|
||||
&& existingDrawable.getIntrinsicWidth() == drawable.getIntrinsicWidth();
|
||||
|
||||
if (skipLayout) {
|
||||
mDrawable.setBounds(existingDrawable.getBounds());
|
||||
} else {
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageResource(int resId) {
|
||||
setImageDrawable(resId == 0 ? null : getContext().getDrawable(resId));
|
||||
}
|
||||
|
||||
public void setImageCirclePercentage(float percentage) {
|
||||
float clamped = Math.max(0, Math.min(1, percentage));
|
||||
if (clamped != mImageCirclePercentage) {
|
||||
mImageCirclePercentage = clamped;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageHorizontalOffcenterPercentage(float percentage) {
|
||||
if (percentage != mImageHorizontalOffcenterPercentage) {
|
||||
mImageHorizontalOffcenterPercentage = percentage;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageTint(int tint) {
|
||||
if (tint != mImageTint) {
|
||||
mImageTint = tint;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getCircleRadius() {
|
||||
float radius = mCircleRadius;
|
||||
if (mCircleRadius <= 0 && mCircleRadiusPercent > 0) {
|
||||
radius = Math.max(getMeasuredHeight(), getMeasuredWidth()) * mCircleRadiusPercent;
|
||||
}
|
||||
|
||||
return radius - mRadiusInset;
|
||||
}
|
||||
|
||||
public float getCircleRadiusPercent() {
|
||||
return mCircleRadiusPercent;
|
||||
}
|
||||
|
||||
public float getCircleRadiusPressed() {
|
||||
float radius = mCircleRadiusPressed;
|
||||
|
||||
if (mCircleRadiusPressed <= 0 && mCircleRadiusPressedPercent > 0) {
|
||||
radius = Math.max(getMeasuredHeight(), getMeasuredWidth())
|
||||
* mCircleRadiusPressedPercent;
|
||||
}
|
||||
|
||||
return radius - mRadiusInset;
|
||||
}
|
||||
|
||||
public float getCircleRadiusPressedPercent() {
|
||||
return mCircleRadiusPressedPercent;
|
||||
}
|
||||
|
||||
public void setCircleRadius(float circleRadius) {
|
||||
if (circleRadius != mCircleRadius) {
|
||||
mCircleRadius = circleRadius;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of the circle to be a percentage of the largest dimension of the view.
|
||||
* @param circleRadiusPercent A {@code float} from 0 to 1 representing the radius percentage.
|
||||
*/
|
||||
public void setCircleRadiusPercent(float circleRadiusPercent) {
|
||||
if (circleRadiusPercent != mCircleRadiusPercent) {
|
||||
mCircleRadiusPercent = circleRadiusPercent;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCircleRadiusPressed(float circleRadiusPressed) {
|
||||
if (circleRadiusPressed != mCircleRadiusPressed) {
|
||||
mCircleRadiusPressed = circleRadiusPressed;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the radius of the circle to be a percentage of the largest dimension of the view when
|
||||
* pressed.
|
||||
* @param circleRadiusPressedPercent A {@code float} from 0 to 1 representing the radius
|
||||
* percentage.
|
||||
*/
|
||||
public void setCircleRadiusPressedPercent(float circleRadiusPressedPercent) {
|
||||
if (circleRadiusPressedPercent != mCircleRadiusPressedPercent) {
|
||||
mCircleRadiusPressedPercent = circleRadiusPressedPercent;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
setColorForCurrentState();
|
||||
}
|
||||
|
||||
public void setCircleColor(int circleColor) {
|
||||
setCircleColorStateList(ColorStateList.valueOf(circleColor));
|
||||
}
|
||||
|
||||
public void setCircleColorStateList(ColorStateList circleColor) {
|
||||
if (!Objects.equals(circleColor, mCircleColor)) {
|
||||
mCircleColor = circleColor;
|
||||
setColorForCurrentState();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public ColorStateList getCircleColorStateList() {
|
||||
return mCircleColor;
|
||||
}
|
||||
|
||||
public int getDefaultCircleColor() {
|
||||
return mCircleColor.getDefaultColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the circle border as an indeterminate progress spinner.
|
||||
* The views circle border width and color must be set for this to have an effect.
|
||||
*
|
||||
* @param show true if the progress spinner is shown, false to hide it.
|
||||
*/
|
||||
public void showIndeterminateProgress(boolean show) {
|
||||
mProgressIndeterminate = show;
|
||||
if (show) {
|
||||
mIndeterminateDrawable.startAnimation();
|
||||
} else {
|
||||
mIndeterminateDrawable.stopAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (visibility != View.VISIBLE) {
|
||||
showIndeterminateProgress(false);
|
||||
} else if (mProgressIndeterminate) {
|
||||
showIndeterminateProgress(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
if (progress != mProgress) {
|
||||
mProgress = progress;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how much of the shadow should be shown.
|
||||
* @param shadowVisibility Value between 0 and 1.
|
||||
*/
|
||||
public void setShadowVisibility(float shadowVisibility) {
|
||||
if (shadowVisibility != mShadowVisibility) {
|
||||
mShadowVisibility = shadowVisibility;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public float getInitialCircleRadius() {
|
||||
return mInitialCircleRadius;
|
||||
}
|
||||
|
||||
public void setCircleBorderColor(int circleBorderColor) {
|
||||
mCircleBorderColor = circleBorderColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the border around the circle.
|
||||
* @param circleBorderWidth Width of the border around the circle.
|
||||
*/
|
||||
public void setCircleBorderWidth(float circleBorderWidth) {
|
||||
if (circleBorderWidth != mCircleBorderWidth) {
|
||||
mCircleBorderWidth = circleBorderWidth;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPressed(boolean pressed) {
|
||||
super.setPressed(pressed);
|
||||
if (pressed != mPressed) {
|
||||
mPressed = pressed;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getImageDrawable() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the milliseconds duration of the transition animation when the color changes.
|
||||
*/
|
||||
public long getColorChangeAnimationDuration() {
|
||||
return mColorChangeAnimationDurationMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mColorChangeAnimationDurationMs the milliseconds duration of the color change
|
||||
* animation. The color change animation will run if the color changes with {@link #setCircleColor}
|
||||
* or as a result of the active state changing.
|
||||
*/
|
||||
public void setColorChangeAnimationDuration(long mColorChangeAnimationDurationMs) {
|
||||
this.mColorChangeAnimationDurationMs = mColorChangeAnimationDurationMs;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.wear.ble.view;
|
||||
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Interpolator that uses a Bezier derived S shaped curve.
|
||||
* @hide
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
|
||||
class Gusterpolator implements TimeInterpolator {
|
||||
|
||||
/** An instance of {@link androidx.wear.ble.view.Gusterpolator}. */
|
||||
public static final Gusterpolator INSTANCE = new Gusterpolator();
|
||||
|
||||
/**
|
||||
* To avoid users of this class creating multiple copies needlessly, the constructor is
|
||||
* private.
|
||||
*/
|
||||
private Gusterpolator() {}
|
||||
|
||||
/**
|
||||
* Lookup table values.
|
||||
* Generated using a Bezier curve from (0,0) to (1,1) with control points:
|
||||
* P0 (0,0)
|
||||
* P1 (0.4, 0)
|
||||
* P2 (0.2, 1.0)
|
||||
* P3 (1.0, 1.0)
|
||||
*
|
||||
* Values sampled with x at regular intervals between 0 and 1.
|
||||
*/
|
||||
private static final float[] VALUES = new float[] {
|
||||
0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
|
||||
0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f,
|
||||
0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f,
|
||||
0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f,
|
||||
0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f,
|
||||
0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f,
|
||||
0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f,
|
||||
0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f,
|
||||
0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f,
|
||||
0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
private static final float STEP_SIZE = 1.0f / (VALUES.length - 1);
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
if (input >= 1.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (input <= 0f) {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
int position = Math.min(
|
||||
(int)(input * (VALUES.length - 1)),
|
||||
VALUES.length - 2);
|
||||
|
||||
float quantized = position * STEP_SIZE;
|
||||
float difference = input - quantized;
|
||||
float weight = difference / STEP_SIZE;
|
||||
|
||||
return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]);
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.wear.ble.view;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.Property;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
/**
|
||||
* Drawable for showing an indeterminate progress indicator.
|
||||
*
|
||||
* TODO: When Material progress drawable is available in the support library stop using this.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
|
||||
class ProgressDrawable extends Drawable {
|
||||
|
||||
private static Property<ProgressDrawable, Integer> LEVEL =
|
||||
new Property<ProgressDrawable, Integer>(Integer.class, "level") {
|
||||
@Override
|
||||
public Integer get(ProgressDrawable drawable) {
|
||||
return drawable.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(ProgressDrawable drawable, Integer value) {
|
||||
drawable.setLevel(value);
|
||||
drawable.invalidateSelf();
|
||||
}
|
||||
};
|
||||
/** Max level for a level drawable, as specified in developer docs for {@link Drawable}. */
|
||||
private static final int MAX_LEVEL = 10000;
|
||||
|
||||
/** How many different sections are there, five gives us the material style star. **/
|
||||
private static final int NUMBER_OF_SEGMENTS = 5;
|
||||
|
||||
private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS;
|
||||
private static final float STARTING_ANGLE = -90f;
|
||||
private static final long ANIMATION_DURATION = 6000;
|
||||
private static final int FULL_CIRCLE = 360;
|
||||
private static final int MAX_SWEEP = 306;
|
||||
private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP;
|
||||
/** How far through each cycle does the bar stop growing and start shrinking, half way. **/
|
||||
private static final float GROW_SHRINK_RATIO = 0.5f;
|
||||
// TODO: replace this with BakedBezierInterpolator when its available in support library.
|
||||
private static final TimeInterpolator mInterpolator = Gusterpolator.INSTANCE;
|
||||
|
||||
private final RectF mInnerCircleBounds = new RectF();
|
||||
private final Paint mPaint = new Paint();
|
||||
private final ObjectAnimator mAnimator;
|
||||
private float mCircleBorderWidth;
|
||||
private int mCircleBorderColor;
|
||||
|
||||
public ProgressDrawable() {
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL);
|
||||
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
|
||||
mAnimator.setRepeatMode(ValueAnimator.RESTART);
|
||||
mAnimator.setDuration(ANIMATION_DURATION);
|
||||
mAnimator.setInterpolator(new LinearInterpolator());
|
||||
}
|
||||
|
||||
public void setRingColor(int color) {
|
||||
mCircleBorderColor = color;
|
||||
}
|
||||
|
||||
public void setRingWidth(float width) {
|
||||
mCircleBorderWidth = width;
|
||||
}
|
||||
|
||||
public void startAnimation() {
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
public void stopAnimation() {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.save();
|
||||
mInnerCircleBounds.set(getBounds());
|
||||
mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f);
|
||||
mPaint.setStrokeWidth(mCircleBorderWidth);
|
||||
mPaint.setColor(mCircleBorderColor);
|
||||
|
||||
float sweepAngle = FULL_CIRCLE;
|
||||
boolean growing = false;
|
||||
float correctionAngle = 0;
|
||||
int level = getLevel();
|
||||
|
||||
int currentSegment = level / LEVELS_PER_SEGMENT;
|
||||
int offset = currentSegment * LEVELS_PER_SEGMENT;
|
||||
float progress = (level - offset) / (float) LEVELS_PER_SEGMENT;
|
||||
|
||||
growing = progress < GROW_SHRINK_RATIO;
|
||||
correctionAngle = CORRECTION_ANGLE * progress;
|
||||
|
||||
if (growing) {
|
||||
sweepAngle = MAX_SWEEP * mInterpolator.getInterpolation(
|
||||
lerpInv(0f, GROW_SHRINK_RATIO, progress));
|
||||
} else {
|
||||
sweepAngle = MAX_SWEEP * (1.0f - mInterpolator.getInterpolation(
|
||||
lerpInv(GROW_SHRINK_RATIO, 1.0f, progress)));
|
||||
}
|
||||
|
||||
sweepAngle = Math.max(1, sweepAngle);
|
||||
|
||||
canvas.rotate(
|
||||
level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle,
|
||||
mInnerCircleBounds.centerX(),
|
||||
mInnerCircleBounds.centerY());
|
||||
canvas.drawArc(mInnerCircleBounds,
|
||||
growing ? 0 : MAX_SWEEP - sweepAngle,
|
||||
sweepAngle,
|
||||
false,
|
||||
mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onLevelChange(int level) {
|
||||
return true; // Changing the level of this drawable does change its appearance.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interpolation scalar (s) that satisfies the equation:
|
||||
* {@code value = }lerp(a, b, s)
|
||||
*
|
||||
* <p>If {@code a == b}, then this function will return 0.
|
||||
*/
|
||||
private static float lerpInv(float a, float b, float value) {
|
||||
return a != b ? ((value - a) / (b - a)) : 0.0f;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package androidx.wear.ble.view;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Convenience class for listening for Animator events that implements the AnimatorListener
|
||||
* interface and allows extending only methods that are necessary.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
|
||||
public class SimpleAnimatorListener implements Animator.AnimatorListener {
|
||||
|
||||
private boolean mWasCanceled;
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
mWasCanceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
if (!mWasCanceled) {
|
||||
onAnimationComplete(animator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
mWasCanceled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the animation finishes. Not called if the animation was canceled.
|
||||
*/
|
||||
public void onAnimationComplete(Animator animator) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides information if the animation was cancelled.
|
||||
* @return True if animation was cancelled.
|
||||
*/
|
||||
public boolean wasCanceled() {
|
||||
return mWasCanceled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 androidx.wear.ble.view;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
|
||||
/**
|
||||
* Helper to add icons to AlertDialog buttons.AlertDialog buttons.
|
||||
*/
|
||||
public class WearableDialogHelper {
|
||||
private static final String TAG = "WearableDialogHelper";
|
||||
|
||||
private int mPositiveIconId;
|
||||
private Drawable mPositiveIcon;
|
||||
|
||||
private int mNeutralIconId;
|
||||
private Drawable mNeutralIcon;
|
||||
|
||||
private int mNegativeIconId;
|
||||
private Drawable mNegativeIcon;
|
||||
|
||||
@VisibleForTesting /* package */ Resources mResources;
|
||||
@VisibleForTesting /* package */ Resources.Theme mTheme;
|
||||
|
||||
/**
|
||||
* Convenience constructor, equivalent to {@code new WearableDialogHelper(context.getResources(),
|
||||
* context.getTheme())}.
|
||||
*/
|
||||
public WearableDialogHelper(@NonNull Context context) {
|
||||
this(context.getResources(), context.getTheme());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resources the Resources used to obtain Drawables from resource IDs.
|
||||
* @param theme the Theme used to properly obtain Drawables from resource IDs.
|
||||
*/
|
||||
public WearableDialogHelper(@NonNull Resources resources, @NonNull Resources.Theme theme) {
|
||||
mResources = resources;
|
||||
mTheme = theme;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable getPositiveIcon() {
|
||||
return resolveDrawable(mPositiveIcon, mPositiveIconId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable getNegativeIcon() {
|
||||
return resolveDrawable(mNegativeIcon, mNegativeIconId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Drawable getNeutralIcon() {
|
||||
return resolveDrawable(mNeutralIcon, mNeutralIconId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setPositiveIcon(@DrawableRes int resId) {
|
||||
mPositiveIconId = resId;
|
||||
mPositiveIcon = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setPositiveIcon(@Nullable Drawable icon) {
|
||||
mPositiveIcon = icon;
|
||||
mPositiveIconId = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setNegativeIcon(@DrawableRes int resId) {
|
||||
mNegativeIconId = resId;
|
||||
mNegativeIcon = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setNegativeIcon(@Nullable Drawable icon) {
|
||||
mNegativeIcon = icon;
|
||||
mNegativeIconId = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setNeutralIcon(@DrawableRes int resId) {
|
||||
mNeutralIconId = resId;
|
||||
mNeutralIcon = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WearableDialogHelper setNeutralIcon(@Nullable Drawable icon) {
|
||||
mNeutralIcon = icon;
|
||||
mNeutralIconId = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the button icons setup in the helper to the buttons in the dialog.
|
||||
*
|
||||
* <p>Note that this should be called after {@code AlertDialog.create()}, NOT {@code
|
||||
* AlertDialog.Builder.create()}. Calling {@code AlertDialog.Builder.show()} would also accomplish
|
||||
* the same thing.
|
||||
*
|
||||
* @param dialog the AlertDialog to style with the helper.
|
||||
*/
|
||||
public void apply(@NonNull AlertDialog dialog) {
|
||||
applyButton(dialog.getButton(DialogInterface.BUTTON_POSITIVE), getPositiveIcon());
|
||||
applyButton(dialog.getButton(DialogInterface.BUTTON_NEGATIVE), getNegativeIcon());
|
||||
applyButton(dialog.getButton(DialogInterface.BUTTON_NEUTRAL), getNeutralIcon());
|
||||
}
|
||||
|
||||
/** Applies the specified drawable to the button. */
|
||||
@VisibleForTesting
|
||||
/* package */ void applyButton(@Nullable Button button, @Nullable Drawable drawable) {
|
||||
if (button != null) {
|
||||
button.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, null, null, null);
|
||||
button.setAllCaps(false);
|
||||
} else if (drawable != null) {
|
||||
Log.w(TAG, "non-null drawable used with missing button, did you call AlertDialog.create()?");
|
||||
}
|
||||
}
|
||||
|
||||
/** Obtain a drawable between a drawable and a resource ID. */
|
||||
@VisibleForTesting
|
||||
/* package */ Drawable resolveDrawable(@Nullable Drawable drawable, @DrawableRes int resId) {
|
||||
return drawable == null && resId != 0 ? mResources.getDrawable(resId, mTheme) : drawable;
|
||||
}
|
||||
|
||||
/** Convenience builder to generate an AlertDialog with icons in buttons. */
|
||||
public static class DialogBuilder extends AlertDialog.Builder {
|
||||
private final WearableDialogHelper mHelper;
|
||||
|
||||
public DialogBuilder(Context context) {
|
||||
super(context);
|
||||
mHelper = new WearableDialogHelper(context.getResources(), context.getTheme());
|
||||
}
|
||||
|
||||
public DialogBuilder(Context context, int themeResId) {
|
||||
super(context, themeResId);
|
||||
mHelper = new WearableDialogHelper(context.getResources(), context.getTheme());
|
||||
}
|
||||
|
||||
public WearableDialogHelper getHelper() {
|
||||
return mHelper;
|
||||
}
|
||||
|
||||
public DialogBuilder setPositiveIcon(@DrawableRes int iconId) {
|
||||
mHelper.setPositiveIcon(iconId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DialogBuilder setPositiveIcon(@Nullable Drawable icon) {
|
||||
mHelper.setPositiveIcon(icon);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DialogBuilder setNegativeIcon(@DrawableRes int iconId) {
|
||||
mHelper.setNegativeIcon(iconId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DialogBuilder setNegativeIcon(@Nullable Drawable icon) {
|
||||
mHelper.setNegativeIcon(icon);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DialogBuilder setNeutralIcon(@DrawableRes int iconId) {
|
||||
mHelper.setNeutralIcon(iconId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DialogBuilder setNeutralIcon(@Nullable Drawable icon) {
|
||||
mHelper.setNeutralIcon(icon);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog create() {
|
||||
final AlertDialog dialog = super.create();
|
||||
dialog.create();
|
||||
mHelper.apply(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog show() {
|
||||
final AlertDialog dialog = this.create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.BidiFormatter;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An app that requests permissions.
|
||||
*
|
||||
* <p>Allows to query all permission groups of the app and which permission belongs to which group.
|
||||
*/
|
||||
public final class AppPermissions {
|
||||
/**
|
||||
* All permission groups the app requests. Background permission groups are attached to their
|
||||
* foreground groups.
|
||||
*/
|
||||
private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
|
||||
|
||||
/** Cache: group name -> group */
|
||||
private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>();
|
||||
|
||||
/** Cache: permission name -> group. Might point to background group */
|
||||
private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>();
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final CharSequence mAppLabel;
|
||||
|
||||
private final Runnable mOnErrorCallback;
|
||||
|
||||
private final boolean mSortGroups;
|
||||
|
||||
private PackageInfo mPackageInfo;
|
||||
|
||||
public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
|
||||
Runnable onErrorCallback) {
|
||||
mContext = context;
|
||||
mPackageInfo = packageInfo;
|
||||
mAppLabel = BidiFormatter.getInstance().unicodeWrap(
|
||||
packageInfo.applicationInfo.loadSafeLabel(context.getPackageManager(),
|
||||
PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
|
||||
PackageItemInfo.SAFE_LABEL_FLAG_TRIM
|
||||
| PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
|
||||
.toString());
|
||||
mSortGroups = sortGroups;
|
||||
mOnErrorCallback = onErrorCallback;
|
||||
loadPermissionGroups();
|
||||
}
|
||||
|
||||
public PackageInfo getPackageInfo() {
|
||||
return mPackageInfo;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
loadPackageInfo();
|
||||
loadPermissionGroups();
|
||||
}
|
||||
|
||||
public CharSequence getAppLabel() {
|
||||
return mAppLabel;
|
||||
}
|
||||
|
||||
public AppPermissionGroup getPermissionGroup(String name) {
|
||||
return mGroupNameToGroup.get(name);
|
||||
}
|
||||
|
||||
public List<AppPermissionGroup> getPermissionGroups() {
|
||||
return mGroups;
|
||||
}
|
||||
|
||||
public boolean isReviewRequired() {
|
||||
final int groupCount = mGroups.size();
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
AppPermissionGroup group = mGroups.get(i);
|
||||
if (group.isReviewRequired()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadPackageInfo() {
|
||||
try {
|
||||
mPackageInfo = mContext.getPackageManager().getPackageInfo(
|
||||
mPackageInfo.packageName, PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (mOnErrorCallback != null) {
|
||||
mOnErrorCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup}
|
||||
* lookup table.
|
||||
*
|
||||
* @param group The group of permissions to add
|
||||
*/
|
||||
private void addAllPermissions(AppPermissionGroup group) {
|
||||
ArrayList<Permission> perms = group.getPermissions();
|
||||
|
||||
int numPerms = perms.size();
|
||||
for (int permNum = 0; permNum < numPerms; permNum++) {
|
||||
mPermissionNameToGroup.put(perms.get(permNum).getName(), group);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPermissionGroups() {
|
||||
mGroups.clear();
|
||||
mGroupNameToGroup.clear();
|
||||
mPermissionNameToGroup.clear();
|
||||
|
||||
if (mPackageInfo.requestedPermissions != null) {
|
||||
for (String requestedPerm : mPackageInfo.requestedPermissions) {
|
||||
if (getGroupForPermission(requestedPerm) == null) {
|
||||
AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo,
|
||||
requestedPerm);
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mGroups.add(group);
|
||||
mGroupNameToGroup.put(group.getName(), group);
|
||||
|
||||
addAllPermissions(group);
|
||||
|
||||
AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
|
||||
if (backgroundGroup != null) {
|
||||
addAllPermissions(backgroundGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mSortGroups) {
|
||||
Collections.sort(mGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the group a permission belongs to.
|
||||
*
|
||||
* <p>The group found might be a background group.
|
||||
*
|
||||
* @param permission The name of the permission
|
||||
*
|
||||
* @return The group the permission belongs to
|
||||
*/
|
||||
public AppPermissionGroup getGroupForPermission(String permission) {
|
||||
return mPermissionNameToGroup.get(permission);
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.model;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A permission and it's properties.
|
||||
*
|
||||
* @see AppPermissionGroup
|
||||
*/
|
||||
public final class Permission {
|
||||
private final String mName;
|
||||
private final String mBackgroundPermissionName;
|
||||
private final String mAppOp;
|
||||
|
||||
private boolean mGranted;
|
||||
private boolean mAppOpAllowed;
|
||||
private int mFlags;
|
||||
private boolean mIsEphemeral;
|
||||
private boolean mIsRuntimeOnly;
|
||||
private Permission mBackgroundPermission;
|
||||
private ArrayList<Permission> mForegroundPermissions;
|
||||
|
||||
public Permission(String name, String backgroundPermissionName, boolean granted,
|
||||
String appOp, boolean appOpAllowed, int flags, int protectionLevel) {
|
||||
mName = name;
|
||||
mBackgroundPermissionName = backgroundPermissionName;
|
||||
mGranted = granted;
|
||||
mAppOp = appOp;
|
||||
mAppOpAllowed = appOpAllowed;
|
||||
mFlags = flags;
|
||||
mIsEphemeral = (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
|
||||
mIsRuntimeOnly = (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this permission as background permission for {@code foregroundPermissions}.
|
||||
*
|
||||
* @param foregroundPermission The foreground permission
|
||||
*/
|
||||
public void addForegroundPermissions(Permission foregroundPermission) {
|
||||
if (mForegroundPermissions == null) {
|
||||
mForegroundPermissions = new ArrayList<>(1);
|
||||
}
|
||||
mForegroundPermissions.add(foregroundPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this permission as foreground permission for {@code backgroundPermission}.
|
||||
*
|
||||
* @param backgroundPermission The background permission
|
||||
*/
|
||||
public void setBackgroundPermission(Permission backgroundPermission) {
|
||||
mBackgroundPermission = backgroundPermission;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getAppOp() {
|
||||
return mAppOp;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this permission affect app ops.
|
||||
*
|
||||
* <p>I.e. does this permission have a matching app op or is this a background permission. All
|
||||
* background permissions affect the app op of it's assigned foreground permission.
|
||||
*
|
||||
* @return {@code true} if this permission affects app ops
|
||||
*/
|
||||
public boolean affectsAppOp() {
|
||||
return mAppOp != null || isBackgroundPermission();
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
return mGranted;
|
||||
}
|
||||
|
||||
public boolean isReviewRequired() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
|
||||
}
|
||||
|
||||
public void resetReviewRequired() {
|
||||
mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
|
||||
}
|
||||
|
||||
public void setGranted(boolean mGranted) {
|
||||
this.mGranted = mGranted;
|
||||
}
|
||||
|
||||
public boolean isAppOpAllowed() {
|
||||
return mAppOpAllowed;
|
||||
}
|
||||
|
||||
public boolean isUserFixed() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0;
|
||||
}
|
||||
|
||||
public void setUserFixed(boolean userFixed) {
|
||||
if (userFixed) {
|
||||
mFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED;
|
||||
} else {
|
||||
mFlags &= ~PackageManager.FLAG_PERMISSION_USER_FIXED;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSystemFixed() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0;
|
||||
}
|
||||
|
||||
public boolean isPolicyFixed() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
|
||||
}
|
||||
|
||||
public boolean isUserSet() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
|
||||
}
|
||||
|
||||
public boolean isGrantedByDefault() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this permission is split into a foreground and background permission, this is the name
|
||||
* of the background permission.
|
||||
*
|
||||
* @return The name of the background permission or {@code null} if the permission is not split
|
||||
*/
|
||||
public String getBackgroundPermissionName() {
|
||||
return mBackgroundPermissionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If this permission is split into a foreground and background permission,
|
||||
* returns the background permission
|
||||
*/
|
||||
public Permission getBackgroundPermission() {
|
||||
return mBackgroundPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If this permission is split into a foreground and background permission,
|
||||
* returns the foreground permission
|
||||
*/
|
||||
public ArrayList<Permission> getForegroundPermissions() {
|
||||
return mForegroundPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} iff this is the foreground permission of a background-foreground-split
|
||||
* permission
|
||||
*/
|
||||
public boolean hasBackgroundPermission() {
|
||||
return mBackgroundPermissionName != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} iff this is the background permission of a background-foreground-split
|
||||
* permission
|
||||
*/
|
||||
public boolean isBackgroundPermission() {
|
||||
return mForegroundPermissions != null;
|
||||
}
|
||||
|
||||
public void setUserSet(boolean userSet) {
|
||||
if (userSet) {
|
||||
mFlags |= PackageManager.FLAG_PERMISSION_USER_SET;
|
||||
} else {
|
||||
mFlags &= ~PackageManager.FLAG_PERMISSION_USER_SET;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPolicyFixed(boolean policyFixed) {
|
||||
if (policyFixed) {
|
||||
mFlags |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
|
||||
} else {
|
||||
mFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldRevokeOnUpgrade() {
|
||||
return (mFlags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
|
||||
}
|
||||
|
||||
public void setRevokeOnUpgrade(boolean revokeOnUpgrade) {
|
||||
if (revokeOnUpgrade) {
|
||||
mFlags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
|
||||
} else {
|
||||
mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
|
||||
}
|
||||
}
|
||||
|
||||
public void setAppOpAllowed(boolean mAppOpAllowed) {
|
||||
this.mAppOpAllowed = mAppOpAllowed;
|
||||
}
|
||||
|
||||
public boolean isEphemeral() {
|
||||
return mIsEphemeral;
|
||||
}
|
||||
|
||||
public boolean isRuntimeOnly() {
|
||||
return mIsRuntimeOnly;
|
||||
}
|
||||
|
||||
public boolean isGrantingAllowed(boolean isEphemeralApp, boolean supportsRuntimePermissions) {
|
||||
return (!isEphemeralApp || isEphemeral())
|
||||
&& (supportsRuntimePermissions || !isRuntimeOnly());
|
||||
}
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.packageinstaller.permission.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PermissionApps {
|
||||
private static final String LOG_TAG = "PermissionApps";
|
||||
|
||||
private final Context mContext;
|
||||
private final String mGroupName;
|
||||
private final PackageManager mPm;
|
||||
private final Callback mCallback;
|
||||
|
||||
private final PmCache mCache;
|
||||
|
||||
private CharSequence mLabel;
|
||||
private Drawable mIcon;
|
||||
private List<PermissionApp> mPermApps;
|
||||
// Map (pkg|uid) -> AppPermission
|
||||
private ArrayMap<String, PermissionApp> mAppLookup;
|
||||
|
||||
private boolean mSkipUi;
|
||||
private boolean mRefreshing;
|
||||
|
||||
public PermissionApps(Context context, String groupName, Callback callback) {
|
||||
this(context, groupName, callback, null);
|
||||
}
|
||||
|
||||
public PermissionApps(Context context, String groupName, Callback callback, PmCache cache) {
|
||||
mCache = cache;
|
||||
mContext = context;
|
||||
mPm = mContext.getPackageManager();
|
||||
mGroupName = groupName;
|
||||
mCallback = callback;
|
||||
loadGroupInfo();
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return mGroupName;
|
||||
}
|
||||
|
||||
public void loadNowWithoutUi() {
|
||||
mSkipUi = true;
|
||||
createMap(loadPermissionApps());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an async refresh and call back the registered call back once done.
|
||||
*
|
||||
* @param getUiInfo If the UI info should be updated
|
||||
*/
|
||||
public void refresh(boolean getUiInfo) {
|
||||
if (mCallback == null) {
|
||||
throw new IllegalStateException("callback needs to be set");
|
||||
}
|
||||
|
||||
if (!mRefreshing) {
|
||||
mRefreshing = true;
|
||||
mSkipUi = !getUiInfo;
|
||||
new PermissionAppsLoader().execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the state and do not return until it finishes. Should not be called while an {@link
|
||||
* #refresh async referesh} is in progress.
|
||||
*/
|
||||
public void refreshSync() {
|
||||
mSkipUi = true;
|
||||
createMap(loadPermissionApps());
|
||||
}
|
||||
|
||||
public int getGrantedCount(ArraySet<String> launcherPkgs) {
|
||||
int count = 0;
|
||||
for (PermissionApp app : mPermApps) {
|
||||
if (!Utils.shouldShowPermission(app.getPermissionGroup())) {
|
||||
continue;
|
||||
}
|
||||
if (Utils.isSystem(app, launcherPkgs)) {
|
||||
// We default to not showing system apps, so hide them from count.
|
||||
continue;
|
||||
}
|
||||
if (app.areRuntimePermissionsGranted()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getTotalCount(ArraySet<String> launcherPkgs) {
|
||||
int count = 0;
|
||||
for (PermissionApp app : mPermApps) {
|
||||
if (!Utils.shouldShowPermission(app.getPermissionGroup())) {
|
||||
continue;
|
||||
}
|
||||
if (Utils.isSystem(app, launcherPkgs)) {
|
||||
// We default to not showing system apps, so hide them from count.
|
||||
continue;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public List<PermissionApp> getApps() {
|
||||
return mPermApps;
|
||||
}
|
||||
|
||||
public PermissionApp getApp(String key) {
|
||||
return mAppLookup.get(key);
|
||||
}
|
||||
|
||||
public CharSequence getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public Drawable getIcon() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
private List<PermissionApp> loadPermissionApps() {
|
||||
PackageItemInfo groupInfo = getGroupInfo(mGroupName);
|
||||
if (groupInfo == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<PermissionInfo> groupPermInfos = getGroupPermissionInfos(mGroupName);
|
||||
if (groupPermInfos == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<PermissionApp> permApps = new ArrayList<>();
|
||||
IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
|
||||
|
||||
UserManager userManager = mContext.getSystemService(UserManager.class);
|
||||
for (UserHandle user : userManager.getUserProfiles()) {
|
||||
List<PackageInfo> apps = mCache != null ? mCache.getPackages(user.getIdentifier())
|
||||
: mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS,
|
||||
user.getIdentifier());
|
||||
|
||||
final int N = apps.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
PackageInfo app = apps.get(i);
|
||||
if (app.requestedPermissions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < app.requestedPermissions.length; j++) {
|
||||
String requestedPerm = app.requestedPermissions[j];
|
||||
|
||||
PermissionInfo requestedPermissionInfo = null;
|
||||
|
||||
for (PermissionInfo groupPermInfo : groupPermInfos) {
|
||||
if (requestedPerm.equals(groupPermInfo.name)) {
|
||||
requestedPermissionInfo = groupPermInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (requestedPermissionInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((requestedPermissionInfo.protectionLevel
|
||||
& PermissionInfo.PROTECTION_MASK_BASE)
|
||||
!= PermissionInfo.PROTECTION_DANGEROUS
|
||||
|| (requestedPermissionInfo.flags
|
||||
& PermissionInfo.FLAG_INSTALLED) == 0
|
||||
|| (requestedPermissionInfo.flags
|
||||
& PermissionInfo.FLAG_REMOVED) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppPermissionGroup group = AppPermissionGroup.create(mContext,
|
||||
app, groupInfo, groupPermInfos, user);
|
||||
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String label = mSkipUi ? app.packageName
|
||||
: app.applicationInfo.loadLabel(mPm).toString();
|
||||
|
||||
Drawable icon = null;
|
||||
if (!mSkipUi) {
|
||||
icon = iconFactory.getBadgedIcon(app.applicationInfo,
|
||||
UserHandle.getUserId(group.getApp().applicationInfo.uid));
|
||||
}
|
||||
|
||||
PermissionApp permApp = new PermissionApp(app.packageName, group, label, icon,
|
||||
app.applicationInfo);
|
||||
|
||||
permApps.add(permApp);
|
||||
break; // move to the next app.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(permApps);
|
||||
|
||||
return permApps;
|
||||
}
|
||||
|
||||
private void createMap(List<PermissionApp> result) {
|
||||
mAppLookup = new ArrayMap<>();
|
||||
for (PermissionApp app : result) {
|
||||
mAppLookup.put(app.getKey(), app);
|
||||
}
|
||||
mPermApps = result;
|
||||
}
|
||||
|
||||
private PackageItemInfo getGroupInfo(String groupName) {
|
||||
try {
|
||||
return mContext.getPackageManager().getPermissionGroupInfo(groupName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
return mContext.getPackageManager().getPermissionInfo(groupName, 0);
|
||||
} catch (NameNotFoundException e2) {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<PermissionInfo> getGroupPermissionInfos(String groupName) {
|
||||
try {
|
||||
return mContext.getPackageManager().queryPermissionsByGroup(groupName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
PermissionInfo permissionInfo = mContext.getPackageManager()
|
||||
.getPermissionInfo(groupName, 0);
|
||||
List<PermissionInfo> permissions = new ArrayList<>();
|
||||
permissions.add(permissionInfo);
|
||||
return permissions;
|
||||
} catch (NameNotFoundException e2) {
|
||||
/* ignore */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadGroupInfo() {
|
||||
PackageItemInfo info;
|
||||
try {
|
||||
info = mPm.getPermissionGroupInfo(mGroupName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
try {
|
||||
PermissionInfo permInfo = mPm.getPermissionInfo(mGroupName, 0);
|
||||
if ((permInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
|
||||
!= PermissionInfo.PROTECTION_DANGEROUS) {
|
||||
Log.w(LOG_TAG, mGroupName + " is not a runtime permission");
|
||||
return;
|
||||
}
|
||||
info = permInfo;
|
||||
} catch (NameNotFoundException reallyNotFound) {
|
||||
Log.w(LOG_TAG, "Can't find permission: " + mGroupName, reallyNotFound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mLabel = info.loadLabel(mPm);
|
||||
if (info.icon != 0) {
|
||||
mIcon = info.loadUnbadgedIcon(mPm);
|
||||
} else {
|
||||
mIcon = mContext.getDrawable(R.drawable.ic_perm_device_info);
|
||||
}
|
||||
mIcon = Utils.applyTint(mContext, mIcon, android.R.attr.colorControlNormal);
|
||||
}
|
||||
|
||||
public static class PermissionApp implements Comparable<PermissionApp> {
|
||||
private final String mPackageName;
|
||||
private final AppPermissionGroup mAppPermissionGroup;
|
||||
private final String mLabel;
|
||||
private final Drawable mIcon;
|
||||
private final ApplicationInfo mInfo;
|
||||
|
||||
public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup,
|
||||
String label, Drawable icon, ApplicationInfo info) {
|
||||
mPackageName = packageName;
|
||||
mAppPermissionGroup = appPermissionGroup;
|
||||
mLabel = label;
|
||||
mIcon = icon;
|
||||
mInfo = info;
|
||||
}
|
||||
|
||||
public ApplicationInfo getAppInfo() {
|
||||
return mInfo;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return mPackageName + getUid();
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public Drawable getIcon() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
public boolean areRuntimePermissionsGranted() {
|
||||
return mAppPermissionGroup.areRuntimePermissionsGranted();
|
||||
}
|
||||
|
||||
public boolean isReviewRequired() {
|
||||
return mAppPermissionGroup.isReviewRequired();
|
||||
}
|
||||
|
||||
public void grantRuntimePermissions() {
|
||||
mAppPermissionGroup.grantRuntimePermissions(false);
|
||||
}
|
||||
|
||||
public void revokeRuntimePermissions() {
|
||||
mAppPermissionGroup.revokeRuntimePermissions(false);
|
||||
}
|
||||
|
||||
public boolean isPolicyFixed() {
|
||||
return mAppPermissionGroup.isPolicyFixed();
|
||||
}
|
||||
|
||||
public boolean isSystemFixed() {
|
||||
return mAppPermissionGroup.isSystemFixed();
|
||||
}
|
||||
|
||||
public boolean hasGrantedByDefaultPermissions() {
|
||||
return mAppPermissionGroup.hasGrantedByDefaultPermission();
|
||||
}
|
||||
|
||||
public boolean doesSupportRuntimePermissions() {
|
||||
return mAppPermissionGroup.doesSupportRuntimePermissions();
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return mAppPermissionGroup.getUserId();
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public AppPermissionGroup getPermissionGroup() {
|
||||
return mAppPermissionGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PermissionApp another) {
|
||||
final int result = mLabel.compareTo(another.mLabel);
|
||||
if (result == 0) {
|
||||
// Unbadged before badged.
|
||||
return getKey().compareTo(another.getKey());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getUid() {
|
||||
return mAppPermissionGroup.getApp().applicationInfo.uid;
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionAppsLoader extends AsyncTask<Void, Void, List<PermissionApp>> {
|
||||
|
||||
@Override
|
||||
protected List<PermissionApp> doInBackground(Void... args) {
|
||||
return loadPermissionApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<PermissionApp> result) {
|
||||
mRefreshing = false;
|
||||
createMap(result);
|
||||
if (mCallback != null) {
|
||||
mCallback.onPermissionsLoaded(PermissionApps.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to reduce the number of calls to the package manager.
|
||||
* This caches app information so it should only be used across parallel PermissionApps
|
||||
* instances, and should not be retained across UI refresh.
|
||||
*/
|
||||
public static class PmCache {
|
||||
private final SparseArray<List<PackageInfo>> mPackageInfoCache = new SparseArray<>();
|
||||
private final PackageManager mPm;
|
||||
|
||||
public PmCache(PackageManager pm) {
|
||||
mPm = pm;
|
||||
}
|
||||
|
||||
public synchronized List<PackageInfo> getPackages(int userId) {
|
||||
List<PackageInfo> ret = mPackageInfoCache.get(userId);
|
||||
if (ret == null) {
|
||||
ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId);
|
||||
mPackageInfoCache.put(userId, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onPermissionsLoaded(PermissionApps permissionApps);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.model;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* A permission group with runtime permission as defined in an app's manifest as
|
||||
* {@code android:permission-group}.
|
||||
*
|
||||
* <p>For individual permissions that are not part of any group a {@link PermissionGroup} is created
|
||||
* dynamically with the name and icon of the individual permission.
|
||||
*/
|
||||
public final class PermissionGroup implements Comparable<PermissionGroup> {
|
||||
private final String mName;
|
||||
private final String mDeclaringPackage;
|
||||
private final CharSequence mLabel;
|
||||
private final Drawable mIcon;
|
||||
private final int mTotal;
|
||||
private final int mGranted;
|
||||
|
||||
PermissionGroup(String name, String declaringPackage, CharSequence label, Drawable icon,
|
||||
int total, int granted) {
|
||||
mDeclaringPackage = declaringPackage;
|
||||
mName = name;
|
||||
mLabel = label;
|
||||
mIcon = icon;
|
||||
mTotal = total;
|
||||
mGranted = granted;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public String getDeclaringPackage() {
|
||||
return mDeclaringPackage;
|
||||
}
|
||||
|
||||
public CharSequence getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public Drawable getIcon() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of apps that might request permissions of this group
|
||||
*/
|
||||
public int getTotal() {
|
||||
return mTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of apps that were granted permissions of this group
|
||||
*/
|
||||
public int getGranted() {
|
||||
return mGranted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PermissionGroup another) {
|
||||
return mLabel.toString().compareTo(another.mLabel.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PermissionGroup other = (PermissionGroup) obj;
|
||||
|
||||
if (mName == null) {
|
||||
if (other.mName != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!mName.equals(other.mName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mTotal != other.mTotal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mGranted != other.mGranted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mName != null ? mName.hashCode() + mTotal + mGranted : mTotal + mGranted;
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.model;
|
||||
|
||||
import static android.content.pm.PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE;
|
||||
import static android.content.pm.PackageItemInfo.SAFE_LABEL_FLAG_TRIM;
|
||||
|
||||
import android.app.LoaderManager;
|
||||
import android.app.LoaderManager.LoaderCallbacks;
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PermissionGroupInfo;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* All {@link PermissionGroup permission groups} defined by any app.
|
||||
*/
|
||||
public final class PermissionGroups implements LoaderCallbacks<List<PermissionGroup>> {
|
||||
private final ArrayList<PermissionGroup> mGroups = new ArrayList<>();
|
||||
private final Context mContext;
|
||||
private final LoaderManager mLoaderManager;
|
||||
private final PermissionsGroupsChangeCallback mCallback;
|
||||
|
||||
public interface PermissionsGroupsChangeCallback {
|
||||
public void onPermissionGroupsChanged();
|
||||
}
|
||||
|
||||
public PermissionGroups(Context context, LoaderManager loaderManager,
|
||||
PermissionsGroupsChangeCallback callback) {
|
||||
mContext = context;
|
||||
mLoaderManager = loaderManager;
|
||||
mCallback = callback;
|
||||
mLoaderManager.initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<List<PermissionGroup>> onCreateLoader(int id, Bundle args) {
|
||||
return new PermissionsLoader(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<PermissionGroup>> loader,
|
||||
List<PermissionGroup> groups) {
|
||||
if (mGroups.equals(groups)) {
|
||||
return;
|
||||
}
|
||||
mGroups.clear();
|
||||
mGroups.addAll(groups);
|
||||
mCallback.onPermissionGroupsChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<PermissionGroup>> loader) {
|
||||
mGroups.clear();
|
||||
mCallback.onPermissionGroupsChanged();
|
||||
}
|
||||
|
||||
public List<PermissionGroup> getGroups() {
|
||||
return mGroups;
|
||||
}
|
||||
|
||||
public PermissionGroup getGroup(String name) {
|
||||
for (PermissionGroup group : mGroups) {
|
||||
if (group.getName().equals(name)) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>>
|
||||
implements PackageManager.OnPermissionsChangedListener {
|
||||
|
||||
public PermissionsLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
getContext().getPackageManager().addOnPermissionsChangeListener(this);
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
getContext().getPackageManager().removeOnPermissionsChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PermissionGroup> loadInBackground() {
|
||||
ArraySet<String> launcherPkgs = Utils.getLauncherPackages(getContext());
|
||||
PermissionApps.PmCache pmCache = new PermissionApps.PmCache(
|
||||
getContext().getPackageManager());
|
||||
|
||||
List<PermissionGroup> groups = new ArrayList<>();
|
||||
Set<String> seenPermissions = new ArraySet<>();
|
||||
|
||||
PackageManager packageManager = getContext().getPackageManager();
|
||||
List<PermissionGroupInfo> groupInfos = packageManager.getAllPermissionGroups(0);
|
||||
|
||||
for (PermissionGroupInfo groupInfo : groupInfos) {
|
||||
// Mare sure we respond to cancellation.
|
||||
if (isLoadInBackgroundCanceled()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Get the permissions in this group.
|
||||
final List<PermissionInfo> groupPermissions;
|
||||
try {
|
||||
groupPermissions = packageManager.queryPermissionsByGroup(groupInfo.name, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean hasRuntimePermissions = false;
|
||||
|
||||
// Cache seen permissions and see if group has runtime permissions.
|
||||
for (PermissionInfo groupPermission : groupPermissions) {
|
||||
seenPermissions.add(groupPermission.name);
|
||||
if ((groupPermission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
|
||||
== PermissionInfo.PROTECTION_DANGEROUS
|
||||
&& (groupPermission.flags & PermissionInfo.FLAG_INSTALLED) != 0
|
||||
&& (groupPermission.flags & PermissionInfo.FLAG_REMOVED) == 0) {
|
||||
hasRuntimePermissions = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No runtime permissions - not interesting for us.
|
||||
if (!hasRuntimePermissions) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CharSequence label = loadItemInfoLabel(groupInfo);
|
||||
Drawable icon = loadItemInfoIcon(groupInfo);
|
||||
|
||||
PermissionApps permApps = new PermissionApps(getContext(), groupInfo.name, null,
|
||||
pmCache);
|
||||
permApps.refreshSync();
|
||||
|
||||
// Create the group and add to the list.
|
||||
PermissionGroup group = new PermissionGroup(groupInfo.name,
|
||||
groupInfo.packageName, label, icon, permApps.getTotalCount(launcherPkgs),
|
||||
permApps.getGrantedCount(launcherPkgs));
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
|
||||
// Make sure we add groups for lone runtime permissions.
|
||||
List<PackageInfo> installedPackages = getContext().getPackageManager()
|
||||
.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
|
||||
|
||||
// We will filter out permissions that no package requests.
|
||||
Set<String> requestedPermissions = new ArraySet<>();
|
||||
for (PackageInfo installedPackage : installedPackages) {
|
||||
if (installedPackage.requestedPermissions == null) {
|
||||
continue;
|
||||
}
|
||||
for (String requestedPermission : installedPackage.requestedPermissions) {
|
||||
requestedPermissions.add(requestedPermission);
|
||||
}
|
||||
}
|
||||
|
||||
for (PackageInfo installedPackage : installedPackages) {
|
||||
if (installedPackage.permissions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PermissionInfo permissionInfo : installedPackage.permissions) {
|
||||
// If we have handled this permission, no more work to do.
|
||||
if (!seenPermissions.add(permissionInfo.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We care only about installed runtime permissions.
|
||||
if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
|
||||
!= PermissionInfo.PROTECTION_DANGEROUS
|
||||
|| (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no app uses this permission,
|
||||
if (!requestedPermissions.contains(permissionInfo.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CharSequence label = loadItemInfoLabel(permissionInfo);
|
||||
Drawable icon = loadItemInfoIcon(permissionInfo);
|
||||
|
||||
PermissionApps permApps = new PermissionApps(getContext(), permissionInfo.name,
|
||||
null, pmCache);
|
||||
permApps.refreshSync();
|
||||
|
||||
// Create the group and add to the list.
|
||||
PermissionGroup group = new PermissionGroup(permissionInfo.name,
|
||||
permissionInfo.packageName, label, icon,
|
||||
permApps.getTotalCount(launcherPkgs),
|
||||
permApps.getGrantedCount(launcherPkgs));
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(groups);
|
||||
return groups;
|
||||
}
|
||||
|
||||
private CharSequence loadItemInfoLabel(PackageItemInfo itemInfo) {
|
||||
CharSequence label = itemInfo.loadSafeLabel(getContext().getPackageManager(), 0,
|
||||
SAFE_LABEL_FLAG_FIRST_LINE | SAFE_LABEL_FLAG_TRIM);
|
||||
if (label == null) {
|
||||
label = itemInfo.name;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private Drawable loadItemInfoIcon(PackageItemInfo itemInfo) {
|
||||
Drawable icon = null;
|
||||
if (itemInfo.icon > 0) {
|
||||
icon = Utils.loadDrawable(getContext().getPackageManager(),
|
||||
itemInfo.packageName, itemInfo.icon);
|
||||
}
|
||||
if (icon == null) {
|
||||
icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsChanged(int uid) {
|
||||
forceLoad();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.packageinstaller.permission.service;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.permission.RuntimePermissionPresentationInfo;
|
||||
import android.permissionpresenterservice.RuntimePermissionPresenterService;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.packageinstaller.permission.model.AppPermissionGroup;
|
||||
import com.android.packageinstaller.permission.model.AppPermissions;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service that provides presentation information for runtime permissions.
|
||||
*/
|
||||
public final class RuntimePermissionPresenterServiceImpl extends RuntimePermissionPresenterService {
|
||||
private static final String LOG_TAG = "PermissionPresenter";
|
||||
|
||||
@Override
|
||||
public List<RuntimePermissionPresentationInfo> onGetAppPermissions(String packageName) {
|
||||
final PackageInfo packageInfo;
|
||||
try {
|
||||
packageInfo = getPackageManager().getPackageInfo(packageName,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Error getting package:" + packageName, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>();
|
||||
|
||||
AppPermissions appPermissions = new AppPermissions(this, packageInfo, false, null);
|
||||
for (AppPermissionGroup group : appPermissions.getPermissionGroups()) {
|
||||
if (Utils.shouldShowPermission(group)) {
|
||||
final boolean granted = group.areRuntimePermissionsGranted();
|
||||
final boolean standard = Utils.OS_PKG.equals(group.getDeclaringPackage());
|
||||
RuntimePermissionPresentationInfo permission =
|
||||
new RuntimePermissionPresentationInfo(group.getLabel(),
|
||||
granted, standard);
|
||||
permissions.add(permission);
|
||||
}
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevokeRuntimePermission(String packageName, String permissionName) {
|
||||
try {
|
||||
final PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
final AppPermissions appPermissions = new AppPermissions(this, packageInfo, false,
|
||||
null);
|
||||
|
||||
final AppPermissionGroup appPermissionGroup = appPermissions.getGroupForPermission(
|
||||
permissionName);
|
||||
|
||||
if (appPermissionGroup != null) {
|
||||
appPermissionGroup.revokeRuntimePermissions(false);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Error getting package:" + packageName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
/**
|
||||
* An extension of LinearLayout that automatically switches to vertical
|
||||
* orientation when it can't fit its child views horizontally.
|
||||
*/
|
||||
public class ButtonBarLayout extends LinearLayout {
|
||||
/** Whether the current configuration allows stacking. */
|
||||
private boolean mAllowStacking;
|
||||
|
||||
private int mLastWidthSize = -1;
|
||||
|
||||
public ButtonBarLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mAllowStacking = true;
|
||||
}
|
||||
|
||||
public void setAllowStacking(boolean allowStacking) {
|
||||
if (mAllowStacking != allowStacking) {
|
||||
mAllowStacking = allowStacking;
|
||||
if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
|
||||
setStacked(false);
|
||||
}
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
if (mAllowStacking) {
|
||||
if (widthSize > mLastWidthSize && isStacked()) {
|
||||
// We're being measured wider this time, try un-stacking.
|
||||
setStacked(false);
|
||||
}
|
||||
|
||||
mLastWidthSize = widthSize;
|
||||
}
|
||||
|
||||
boolean needsRemeasure = false;
|
||||
|
||||
// If we're not stacked, make sure the measure spec is AT_MOST rather
|
||||
// than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
|
||||
// know to stack the buttons.
|
||||
final int initialWidthMeasureSpec;
|
||||
if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
|
||||
initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
|
||||
|
||||
// We'll need to remeasure again to fill excess space.
|
||||
needsRemeasure = true;
|
||||
} else {
|
||||
initialWidthMeasureSpec = widthMeasureSpec;
|
||||
}
|
||||
|
||||
super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (mAllowStacking && !isStacked()) {
|
||||
final int measuredWidth = getMeasuredWidthAndState();
|
||||
final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
|
||||
if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
|
||||
setStacked(true);
|
||||
|
||||
// Measure again in the new orientation.
|
||||
needsRemeasure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsRemeasure) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStacked(boolean stacked) {
|
||||
setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
|
||||
setGravity(stacked ? Gravity.END : Gravity.BOTTOM);
|
||||
|
||||
final View spacer = findViewById(R.id.spacer);
|
||||
if (spacer != null) {
|
||||
spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStacked() {
|
||||
return getOrientation() == LinearLayout.VERTICAL;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
public final class ConfirmActionDialogFragment extends DialogFragment {
|
||||
public static final String ARG_MESSAGE = "MESSAGE";
|
||||
public static final String ARG_ACTION = "ACTION";
|
||||
|
||||
public interface OnActionConfirmedListener {
|
||||
void onActionConfirmed(String action);
|
||||
}
|
||||
|
||||
public static ConfirmActionDialogFragment newInstance(CharSequence message, String action) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putCharSequence(ARG_MESSAGE, message);
|
||||
arguments.putString(ARG_ACTION, action);
|
||||
ConfirmActionDialogFragment fragment = new ConfirmActionDialogFragment();
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle bundle) {
|
||||
return new AlertDialog.Builder(getContext())
|
||||
.setMessage(getArguments().getString(ARG_MESSAGE))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.grant_dialog_button_deny_anyway,
|
||||
(dialog, which) -> {
|
||||
Activity activity = getActivity();
|
||||
if (activity instanceof OnActionConfirmedListener) {
|
||||
String groupName = getArguments().getString(ARG_ACTION);
|
||||
((OnActionConfirmedListener) activity)
|
||||
.onActionConfirmed(groupName);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
}
|
||||
@@ -1,834 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import static android.content.pm.PackageManager.PERMISSION_DENIED;
|
||||
|
||||
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.DENIED;
|
||||
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler
|
||||
.DENIED_DO_NOT_ASK_AGAIN;
|
||||
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
|
||||
import static com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler
|
||||
.GRANTED_FOREGROUND_ONLY;
|
||||
import static com.android.packageinstaller.permission.utils.Utils.getRequestMessage;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
import com.android.packageinstaller.DeviceUtils;
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.model.AppPermissionGroup;
|
||||
import com.android.packageinstaller.permission.model.AppPermissions;
|
||||
import com.android.packageinstaller.permission.model.Permission;
|
||||
import com.android.packageinstaller.permission.ui.auto.GrantPermissionsAutoViewHandler;
|
||||
import com.android.packageinstaller.permission.utils.ArrayUtils;
|
||||
import com.android.packageinstaller.permission.utils.EventLogger;
|
||||
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class GrantPermissionsActivity extends OverlayTouchActivity
|
||||
implements GrantPermissionsViewHandler.ResultListener {
|
||||
|
||||
private static final String LOG_TAG = "GrantPermissionsActivity";
|
||||
|
||||
private String[] mRequestedPermissions;
|
||||
|
||||
private ArrayMap<Pair<String, Boolean>, GroupState> mRequestGrantPermissionGroups =
|
||||
new ArrayMap<>();
|
||||
|
||||
private GrantPermissionsViewHandler mViewHandler;
|
||||
private AppPermissions mAppPermissions;
|
||||
|
||||
boolean mResultSet;
|
||||
|
||||
private PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
|
||||
private PackageMonitor mPackageMonitor;
|
||||
|
||||
private String mCallingPackage;
|
||||
|
||||
private int getPermissionPolicy() {
|
||||
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
|
||||
return devicePolicyManager.getPermissionPolicy(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to add a single permission that is requested to be granted.
|
||||
*
|
||||
* <p>This does <u>not</u> expand the permissions into the {@link #computeAffectedPermissions
|
||||
* affected permissions}.
|
||||
*
|
||||
* @param group The group the permission belongs to (might be a background permission group)
|
||||
* @param permission The permission to add
|
||||
* @param isFirstInstance Is this the first time the groupStates get created
|
||||
*/
|
||||
private void addRequestedPermissions(AppPermissionGroup group, String permission,
|
||||
boolean isFirstInstance) {
|
||||
if (!group.isGrantingAllowed()) {
|
||||
// Skip showing groups that we know cannot be granted.
|
||||
return;
|
||||
}
|
||||
|
||||
// We allow the user to choose only non-fixed permissions. A permission
|
||||
// is fixed either by device policy or the user denying with prejudice.
|
||||
if (!group.isUserFixed() && !group.isPolicyFixed()) {
|
||||
Pair<String, Boolean> groupKey = new Pair<>(group.getName(),
|
||||
group.isBackgroundGroup());
|
||||
|
||||
GroupState state = mRequestGrantPermissionGroups.get(groupKey);
|
||||
if (state == null) {
|
||||
state = new GroupState(group);
|
||||
mRequestGrantPermissionGroups.put(groupKey, state);
|
||||
}
|
||||
state.affectedPermissions = ArrayUtils.appendString(
|
||||
state.affectedPermissions, permission);
|
||||
|
||||
boolean skipGroup = false;
|
||||
switch (getPermissionPolicy()) {
|
||||
case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: {
|
||||
if (!group.areRuntimePermissionsGranted()) {
|
||||
group.grantRuntimePermissions(false, new String[]{permission});
|
||||
}
|
||||
state.mState = GroupState.STATE_ALLOWED;
|
||||
group.setPolicyFixed();
|
||||
skipGroup = true;
|
||||
} break;
|
||||
|
||||
case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: {
|
||||
if (group.areRuntimePermissionsGranted()) {
|
||||
group.revokeRuntimePermissions(false, new String[]{permission});
|
||||
}
|
||||
state.mState = GroupState.STATE_DENIED;
|
||||
group.setPolicyFixed();
|
||||
skipGroup = true;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
if (group.areRuntimePermissionsGranted()) {
|
||||
group.grantRuntimePermissions(false, new String[]{permission});
|
||||
state.mState = GroupState.STATE_ALLOWED;
|
||||
skipGroup = true;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if (skipGroup && isFirstInstance) {
|
||||
// Only allow to skip groups when this is the first time the dialog was created.
|
||||
// Otherwise the number of groups changes between instances of the dialog.
|
||||
state.mState = GroupState.STATE_SKIPPED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
// Cache this as this can only read on onCreate, not later.
|
||||
mCallingPackage = getCallingPackage();
|
||||
|
||||
mPackageMonitor = new PackageMonitor() {
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
if (mCallingPackage.equals(packageName)) {
|
||||
Log.w(LOG_TAG, mCallingPackage + " was uninstalled");
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setFinishOnTouchOutside(false);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||
|
||||
setTitle(R.string.permission_request_title);
|
||||
|
||||
if (DeviceUtils.isTelevision(this)) {
|
||||
mViewHandler = new com.android.packageinstaller.permission.ui.television
|
||||
.GrantPermissionsViewHandlerImpl(this,
|
||||
mCallingPackage).setResultListener(this);
|
||||
} else if (DeviceUtils.isWear(this)) {
|
||||
mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
|
||||
} else if (DeviceUtils.isAuto(this)) {
|
||||
mViewHandler = new GrantPermissionsAutoViewHandler(this, mCallingPackage)
|
||||
.setResultListener(this);
|
||||
} else {
|
||||
mViewHandler = new com.android.packageinstaller.permission.ui.handheld
|
||||
.GrantPermissionsViewHandlerImpl(this, mCallingPackage)
|
||||
.setResultListener(this);
|
||||
}
|
||||
|
||||
mRequestedPermissions = getIntent().getStringArrayExtra(
|
||||
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
|
||||
if (mRequestedPermissions == null) {
|
||||
mRequestedPermissions = new String[0];
|
||||
}
|
||||
|
||||
final int requestedPermCount = mRequestedPermissions.length;
|
||||
|
||||
if (requestedPermCount == 0) {
|
||||
setResultAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mPermissionChangeListener = new PermissionChangeListener();
|
||||
} catch (NameNotFoundException e) {
|
||||
setResultAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
PackageInfo callingPackageInfo = getCallingPackageInfo();
|
||||
|
||||
if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null
|
||||
|| callingPackageInfo.requestedPermissions.length <= 0) {
|
||||
setResultAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow legacy apps to request runtime permissions.
|
||||
if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
|
||||
// Returning empty arrays means a cancellation.
|
||||
mRequestedPermissions = new String[0];
|
||||
setResultAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
mAppPermissions = new AppPermissions(this, callingPackageInfo, false,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setResultAndFinish();
|
||||
}
|
||||
});
|
||||
|
||||
for (String requestedPermission : mRequestedPermissions) {
|
||||
if (requestedPermission == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayList<String> affectedPermissions =
|
||||
computeAffectedPermissions(requestedPermission);
|
||||
|
||||
int numAffectedPermissions = affectedPermissions.size();
|
||||
for (int i = 0; i < numAffectedPermissions; i++) {
|
||||
AppPermissionGroup group =
|
||||
mAppPermissions.getGroupForPermission(affectedPermissions.get(i));
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addRequestedPermissions(group, affectedPermissions.get(i), icicle == null);
|
||||
}
|
||||
}
|
||||
|
||||
int numGroupStates = mRequestGrantPermissionGroups.size();
|
||||
for (int groupStateNum = 0; groupStateNum < numGroupStates; groupStateNum++) {
|
||||
GroupState groupState = mRequestGrantPermissionGroups.valueAt(groupStateNum);
|
||||
AppPermissionGroup group = groupState.mGroup;
|
||||
|
||||
// Restore permission group state after lifecycle events
|
||||
if (icicle != null) {
|
||||
groupState.mState = icicle.getInt(
|
||||
getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(groupStateNum)),
|
||||
groupState.mState);
|
||||
}
|
||||
|
||||
// Do not attempt to grant background access if foreground access is not either already
|
||||
// granted or requested
|
||||
if (group.isBackgroundGroup()) {
|
||||
// Check if a foreground permission is already granted
|
||||
boolean foregroundGroupAlreadyGranted = mAppPermissions.getPermissionGroup(
|
||||
group.getName()).areRuntimePermissionsGranted();
|
||||
boolean hasForegroundRequest = (getForegroundGroupState(group.getName()) != null);
|
||||
|
||||
if (!foregroundGroupAlreadyGranted && !hasForegroundRequest) {
|
||||
// The background permission cannot be granted at this time
|
||||
int numPermissions = groupState.affectedPermissions.length;
|
||||
for (int permissionNum = 0; permissionNum < numPermissions; permissionNum++) {
|
||||
Log.w(LOG_TAG,
|
||||
"Cannot grant " + groupState.affectedPermissions[permissionNum]
|
||||
+ " as the matching foreground permission is not already "
|
||||
+ "granted.");
|
||||
}
|
||||
|
||||
groupState.mState = GroupState.STATE_SKIPPED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(mViewHandler.createView());
|
||||
|
||||
Window window = getWindow();
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
mViewHandler.updateWindowAttributes(layoutParams);
|
||||
window.setAttributes(layoutParams);
|
||||
|
||||
// Restore UI state after lifecycle events. This has to be before
|
||||
// showNextPermissionGroupGrantRequest is called. showNextPermissionGroupGrantRequest might
|
||||
// update the UI and the UI behaves differently for updates and initial creations.
|
||||
if (icicle != null) {
|
||||
mViewHandler.loadInstanceState(icicle);
|
||||
}
|
||||
|
||||
if (!showNextPermissionGroupGrantRequest()) {
|
||||
setResultAndFinish();
|
||||
} else if (icicle == null) {
|
||||
int numRequestedPermissions = mRequestedPermissions.length;
|
||||
for (int permissionNum = 0; permissionNum < numRequestedPermissions; permissionNum++) {
|
||||
String permission = mRequestedPermissions[permissionNum];
|
||||
|
||||
EventLogger.logPermission(
|
||||
MetricsProto.MetricsEvent.ACTION_PERMISSION_REQUESTED, permission,
|
||||
mAppPermissions.getPackageInfo().packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link #mRequestedPermissions} if the system reports them as granted.
|
||||
*
|
||||
* <p>This also updates the {@link #mAppPermissions} state and switches to the next group grant
|
||||
* request if the current group becomes granted.
|
||||
*/
|
||||
private void updateIfPermissionsWereGranted() {
|
||||
PackageManager pm = getPackageManager();
|
||||
|
||||
boolean mightShowNextGroup = true;
|
||||
int numGroupStates = mRequestGrantPermissionGroups.size();
|
||||
for (int i = 0; i < numGroupStates; i++) {
|
||||
GroupState groupState = mRequestGrantPermissionGroups.valueAt(i);
|
||||
|
||||
if (groupState == null || groupState.mState != GroupState.STATE_UNKNOWN) {
|
||||
// Group has already been approved / denied via the UI by the user
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean allAffectedPermissionsOfThisGroupAreGranted = true;
|
||||
|
||||
if (groupState.affectedPermissions == null) {
|
||||
// It is not clear which permissions belong to this group, hence never skip this
|
||||
// view
|
||||
allAffectedPermissionsOfThisGroupAreGranted = false;
|
||||
} else {
|
||||
for (int permNum = 0; permNum < groupState.affectedPermissions.length;
|
||||
permNum++) {
|
||||
if (pm.checkPermission(groupState.affectedPermissions[permNum], mCallingPackage)
|
||||
== PERMISSION_DENIED) {
|
||||
allAffectedPermissionsOfThisGroupAreGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allAffectedPermissionsOfThisGroupAreGranted) {
|
||||
groupState.mState = GroupState.STATE_ALLOWED;
|
||||
|
||||
if (mightShowNextGroup) {
|
||||
// The UI currently displays the first group with
|
||||
// mState == STATE_UNKNOWN. So we are switching to next group until we
|
||||
// could not allow a group that was still unknown
|
||||
if (!showNextPermissionGroupGrantRequest()) {
|
||||
setResultAndFinish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mightShowNextGroup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
pm.addOnPermissionsChangeListener(mPermissionChangeListener);
|
||||
|
||||
// get notified when the package is removed
|
||||
mPackageMonitor.register(this, getMainLooper(), false);
|
||||
|
||||
// check if the package was removed while this activity was not started
|
||||
try {
|
||||
pm.getPackageInfo(mCallingPackage, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(LOG_TAG, mCallingPackage + " was uninstalled while this activity was stopped", e);
|
||||
finish();
|
||||
}
|
||||
|
||||
updateIfPermissionsWereGranted();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
mPackageMonitor.unregister();
|
||||
|
||||
getPackageManager().removeOnPermissionsChangeListener(mPermissionChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
View rootView = getWindow().getDecorView();
|
||||
if (rootView.getTop() != 0) {
|
||||
// We are animating the top view, need to compensate for that in motion events.
|
||||
ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a key that stores the GroupState.mState in the instance state.
|
||||
*
|
||||
* @param requestGrantPermissionGroupsKey The key of the permission group
|
||||
*
|
||||
* @return A unique key to be used in the instance state
|
||||
*/
|
||||
private static String getInstanceStateKey(
|
||||
Pair<String, Boolean> requestGrantPermissionGroupsKey) {
|
||||
return GrantPermissionsActivity.class.getName() + "_"
|
||||
+ requestGrantPermissionGroupsKey.first + "_"
|
||||
+ requestGrantPermissionGroupsKey.second;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
mViewHandler.saveInstanceState(outState);
|
||||
|
||||
int numGroups = mRequestGrantPermissionGroups.size();
|
||||
for (int i = 0; i < numGroups; i++) {
|
||||
int state = mRequestGrantPermissionGroups.valueAt(i).mState;
|
||||
|
||||
if (state != GroupState.STATE_UNKNOWN) {
|
||||
outState.putInt(getInstanceStateKey(mRequestGrantPermissionGroups.keyAt(i)), state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the background group state for the permission group with the {@code name}
|
||||
*/
|
||||
private GroupState getBackgroundGroupState(String name) {
|
||||
return mRequestGrantPermissionGroups.get(new Pair<>(name, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the foreground group state for the permission group with the {@code name}
|
||||
*/
|
||||
private GroupState getForegroundGroupState(String name) {
|
||||
return mRequestGrantPermissionGroups.get(new Pair<>(name, false));
|
||||
}
|
||||
|
||||
private boolean shouldShowRequestForGroupState(GroupState groupState) {
|
||||
if (groupState.mState == GroupState.STATE_SKIPPED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GroupState foregroundGroup = getForegroundGroupState(groupState.mGroup.getName());
|
||||
if (groupState.mGroup.isBackgroundGroup()
|
||||
&& (foregroundGroup != null && shouldShowRequestForGroupState(foregroundGroup))) {
|
||||
// If an app requests both foreground and background permissions of the same group,
|
||||
// we only show one request
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean showNextPermissionGroupGrantRequest() {
|
||||
int numGroupStates = mRequestGrantPermissionGroups.size();
|
||||
int numGrantRequests = 0;
|
||||
for (int i = 0; i < numGroupStates; i++) {
|
||||
if (shouldShowRequestForGroupState(mRequestGrantPermissionGroups.valueAt(i))) {
|
||||
numGrantRequests++;
|
||||
}
|
||||
}
|
||||
|
||||
int currentIndex = 0;
|
||||
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
|
||||
if (!shouldShowRequestForGroupState(groupState)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (groupState.mState == GroupState.STATE_UNKNOWN) {
|
||||
GroupState foregroundGroupState;
|
||||
GroupState backgroundGroupState;
|
||||
if (groupState.mGroup.isBackgroundGroup()) {
|
||||
backgroundGroupState = groupState;
|
||||
foregroundGroupState = getForegroundGroupState(groupState.mGroup.getName());
|
||||
} else {
|
||||
foregroundGroupState = groupState;
|
||||
backgroundGroupState = getBackgroundGroupState(groupState.mGroup.getName());
|
||||
}
|
||||
|
||||
CharSequence appLabel = mAppPermissions.getAppLabel();
|
||||
|
||||
// Set the new grant view
|
||||
// TODO: Use a real message for the action. We need group action APIs
|
||||
Resources resources;
|
||||
try {
|
||||
resources = getPackageManager().getResourcesForApplication(
|
||||
groupState.mGroup.getIconPkg());
|
||||
} catch (NameNotFoundException e) {
|
||||
// Fallback to system.
|
||||
resources = Resources.getSystem();
|
||||
}
|
||||
|
||||
Icon icon;
|
||||
try {
|
||||
icon = Icon.createWithResource(resources, groupState.mGroup.getIconResId());
|
||||
} catch (Resources.NotFoundException e) {
|
||||
Log.e(LOG_TAG, "Cannot load icon for group" + groupState.mGroup.getName(), e);
|
||||
icon = null;
|
||||
}
|
||||
|
||||
// If no background permissions are granted yet, we need to ask for background
|
||||
// permissions
|
||||
boolean needBackgroundPermission = false;
|
||||
boolean isBackgroundPermissionUserSet = false;
|
||||
if (backgroundGroupState != null) {
|
||||
if (!backgroundGroupState.mGroup.areRuntimePermissionsGranted()) {
|
||||
needBackgroundPermission = true;
|
||||
isBackgroundPermissionUserSet = backgroundGroupState.mGroup.isUserSet();
|
||||
}
|
||||
}
|
||||
|
||||
// If no foreground permissions are granted yet, we need to ask for foreground
|
||||
// permissions
|
||||
boolean needForegroundPermission = false;
|
||||
boolean isForegroundPermissionUserSet = false;
|
||||
if (foregroundGroupState != null) {
|
||||
if (!foregroundGroupState.mGroup.areRuntimePermissionsGranted()) {
|
||||
needForegroundPermission = true;
|
||||
isForegroundPermissionUserSet = foregroundGroupState.mGroup.isUserSet();
|
||||
}
|
||||
}
|
||||
|
||||
boolean showForegroundChooser = false;
|
||||
int messageId;
|
||||
int detailMessageId = 0;
|
||||
if (needForegroundPermission) {
|
||||
messageId = groupState.mGroup.getRequest();
|
||||
|
||||
if (needBackgroundPermission) {
|
||||
showForegroundChooser = true;
|
||||
} else {
|
||||
if (foregroundGroupState.mGroup.hasPermissionWithBackgroundMode()) {
|
||||
detailMessageId = groupState.mGroup.getRequestDetail();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (needBackgroundPermission) {
|
||||
messageId = groupState.mGroup.getBackgroundRequest();
|
||||
detailMessageId = groupState.mGroup.getBackgroundRequestDetail();
|
||||
} else {
|
||||
// Not reached as the permissions should be auto-granted
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CharSequence message = getRequestMessage(appLabel, groupState.mGroup, this,
|
||||
messageId);
|
||||
|
||||
Spanned detailMessage = null;
|
||||
if (detailMessageId != 0) {
|
||||
try {
|
||||
detailMessage = Html.fromHtml(
|
||||
getPackageManager().getResourcesForApplication(
|
||||
groupState.mGroup.getDeclaringPackage()).getString(
|
||||
detailMessageId), 0);
|
||||
} catch (NameNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// Set the permission message as the title so it can be announced.
|
||||
setTitle(message);
|
||||
|
||||
mViewHandler.updateUi(groupState.mGroup.getName(), numGrantRequests, currentIndex,
|
||||
icon, message, detailMessage, showForegroundChooser,
|
||||
isForegroundPermissionUserSet || isBackgroundPermissionUserSet);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (groupState.mState != GroupState.STATE_SKIPPED) {
|
||||
currentIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionGrantResult(String name,
|
||||
@GrantPermissionsViewHandler.Result int result) {
|
||||
GroupState foregroundGroupState = getForegroundGroupState(name);
|
||||
GroupState backgroundGroupState = getBackgroundGroupState(name);
|
||||
|
||||
switch (result) {
|
||||
case GRANTED_ALWAYS :
|
||||
if (foregroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(foregroundGroupState, true, false);
|
||||
}
|
||||
if (backgroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(backgroundGroupState, true, false);
|
||||
}
|
||||
break;
|
||||
case GRANTED_FOREGROUND_ONLY :
|
||||
if (foregroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(foregroundGroupState, true, false);
|
||||
}
|
||||
if (backgroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(backgroundGroupState, false, false);
|
||||
}
|
||||
break;
|
||||
case DENIED :
|
||||
if (foregroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(foregroundGroupState, false, false);
|
||||
}
|
||||
if (backgroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(backgroundGroupState, false, false);
|
||||
}
|
||||
break;
|
||||
case DENIED_DO_NOT_ASK_AGAIN :
|
||||
if (foregroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(foregroundGroupState, false, true);
|
||||
}
|
||||
if (backgroundGroupState != null) {
|
||||
onPermissionGrantResultSingleState(backgroundGroupState, false, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!showNextPermissionGroupGrantRequest()) {
|
||||
setResultAndFinish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants or revoked the affected permissions for a single {@link groupState}.
|
||||
*
|
||||
* @param groupState The group state with the permissions to grant/revoke
|
||||
* @param granted {@code true} if the permissions should be granted, {@code false} if they
|
||||
* should be revoked
|
||||
* @param doNotAskAgain if the permissions should be revoked should be app be allowed to ask
|
||||
* again for the same permissions?
|
||||
*/
|
||||
private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
|
||||
boolean doNotAskAgain) {
|
||||
if (groupState != null && groupState.mGroup != null) {
|
||||
if (granted) {
|
||||
groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
|
||||
groupState.affectedPermissions);
|
||||
groupState.mState = GroupState.STATE_ALLOWED;
|
||||
} else {
|
||||
groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
|
||||
groupState.affectedPermissions);
|
||||
groupState.mState = GroupState.STATE_DENIED;
|
||||
|
||||
int numRequestedPermissions = mRequestedPermissions.length;
|
||||
for (int i = 0; i < numRequestedPermissions; i++) {
|
||||
String permission = mRequestedPermissions[i];
|
||||
|
||||
if (groupState.mGroup.hasPermission(permission)) {
|
||||
EventLogger.logPermission(
|
||||
MetricsProto.MetricsEvent.ACTION_PERMISSION_DENIED, permission,
|
||||
mAppPermissions.getPackageInfo().packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// We do not allow backing out.
|
||||
return keyCode == KeyEvent.KEYCODE_BACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// We do not allow backing out.
|
||||
return keyCode == KeyEvent.KEYCODE_BACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
setResultIfNeeded(RESULT_CANCELED);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private PackageInfo getCallingPackageInfo() {
|
||||
try {
|
||||
return getPackageManager().getPackageInfo(mCallingPackage,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.i(LOG_TAG, "No package: " + mCallingPackage, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setResultIfNeeded(int resultCode) {
|
||||
if (!mResultSet) {
|
||||
mResultSet = true;
|
||||
logRequestedPermissionGroups();
|
||||
Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
|
||||
result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
|
||||
|
||||
PackageManager pm = getPackageManager();
|
||||
int numRequestedPermissions = mRequestedPermissions.length;
|
||||
int[] grantResults = new int[numRequestedPermissions];
|
||||
for (int i = 0; i < numRequestedPermissions; i++) {
|
||||
grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
|
||||
}
|
||||
|
||||
result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
|
||||
setResult(resultCode, result);
|
||||
}
|
||||
}
|
||||
|
||||
private void setResultAndFinish() {
|
||||
setResultIfNeeded(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void logRequestedPermissionGroups() {
|
||||
if (mRequestGrantPermissionGroups.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int groupCount = mRequestGrantPermissionGroups.size();
|
||||
List<AppPermissionGroup> groups = new ArrayList<>(groupCount);
|
||||
for (GroupState groupState : mRequestGrantPermissionGroups.values()) {
|
||||
groups.add(groupState.mGroup);
|
||||
}
|
||||
|
||||
SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actually requested permissions when a permission is requested.
|
||||
*
|
||||
* <p>>In some cases requesting to grant a single permission requires the system to grant
|
||||
* additional permissions. E.g. before N-MR1 a single permission of a group caused the whole
|
||||
* group to be granted. Another case are permissions that are split into two. For apps that
|
||||
* target an SDK before the split, this method automatically adds the split off permission.
|
||||
*
|
||||
* @param permission The requested permission
|
||||
*
|
||||
* @return The actually requested permissions
|
||||
*/
|
||||
private ArrayList<String> computeAffectedPermissions(String permission) {
|
||||
int requestingAppTargetSDK =
|
||||
mAppPermissions.getPackageInfo().applicationInfo.targetSdkVersion;
|
||||
|
||||
// If a permission is split, all permissions the original permission is split into are
|
||||
// affected
|
||||
ArrayList<String> splitPerms = new ArrayList<>();
|
||||
splitPerms.add(permission);
|
||||
for (PackageParser.SplitPermissionInfo splitPerm : PackageParser.SPLIT_PERMISSIONS) {
|
||||
if (requestingAppTargetSDK < splitPerm.targetSdk
|
||||
&& permission.equals(splitPerm.rootPerm)) {
|
||||
Collections.addAll(splitPerms, splitPerm.newPerms);
|
||||
}
|
||||
}
|
||||
|
||||
// For <= N_MR1 apps all permissions of the groups of the requested permissions are affected
|
||||
if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) {
|
||||
ArrayList<String> extendedPermissions = new ArrayList<>();
|
||||
|
||||
int numSplitPerms = splitPerms.size();
|
||||
for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
|
||||
AppPermissionGroup group = mAppPermissions.getGroupForPermission(
|
||||
splitPerms.get(splitPermNum));
|
||||
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArrayList<Permission> permissionsInGroup = group.getPermissions();
|
||||
int numPermissionsInGroup = permissionsInGroup.size();
|
||||
for (int permNum = 0; permNum < numPermissionsInGroup; permNum++) {
|
||||
extendedPermissions.add(permissionsInGroup.get(permNum).getName());
|
||||
}
|
||||
}
|
||||
|
||||
return extendedPermissions;
|
||||
} else {
|
||||
return splitPerms;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GroupState {
|
||||
static final int STATE_UNKNOWN = 0;
|
||||
static final int STATE_ALLOWED = 1;
|
||||
static final int STATE_DENIED = 2;
|
||||
static final int STATE_SKIPPED = 3;
|
||||
|
||||
final AppPermissionGroup mGroup;
|
||||
int mState = STATE_UNKNOWN;
|
||||
|
||||
/** Permissions of this group that need to be granted, null == no permissions of group */
|
||||
String[] affectedPermissions;
|
||||
|
||||
GroupState(AppPermissionGroup group) {
|
||||
mGroup = group;
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
|
||||
final int mCallingPackageUid;
|
||||
|
||||
PermissionChangeListener() throws NameNotFoundException {
|
||||
mCallingPackageUid = getPackageManager().getPackageUid(mCallingPackage, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsChanged(int uid) {
|
||||
if (uid == mCallingPackageUid) {
|
||||
updateIfPermissionsWereGranted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
/**
|
||||
* Class for managing the presentation and user interaction of the "grant
|
||||
* permissions" user interface.
|
||||
*/
|
||||
public interface GrantPermissionsViewHandler {
|
||||
@Retention(SOURCE)
|
||||
@IntDef({GRANTED_ALWAYS, GRANTED_FOREGROUND_ONLY, DENIED, DENIED_DO_NOT_ASK_AGAIN})
|
||||
@interface Result {}
|
||||
int GRANTED_ALWAYS = 0;
|
||||
int GRANTED_FOREGROUND_ONLY = 1;
|
||||
int DENIED = 2;
|
||||
int DENIED_DO_NOT_ASK_AGAIN = 3;
|
||||
|
||||
/**
|
||||
* Listener interface for getting notified when the user responds to a
|
||||
* permissions grant request.
|
||||
*/
|
||||
interface ResultListener {
|
||||
void onPermissionGrantResult(String groupName, @Result int result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the view hierarchy that is managed by this view
|
||||
* handler. This must be called before {@link #updateUi}.
|
||||
*/
|
||||
View createView();
|
||||
|
||||
/**
|
||||
* Updates the layout attributes of the current window. This is an optional
|
||||
* operation; implementations only need to do work in this method if they
|
||||
* need to alter the default styles provided by the activity's theme.
|
||||
*/
|
||||
void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams);
|
||||
|
||||
/**
|
||||
* Updates the view hierarchy to reflect the specified state.
|
||||
* <p>
|
||||
* Note that this must be called at least once before showing the UI to
|
||||
* the user to properly initialize the UI.
|
||||
*
|
||||
* @param groupName the name of the permission group
|
||||
* @param groupCount the total number of groups that are being requested
|
||||
* @param groupIndex the index of the current group being requested
|
||||
* @param icon the icon representation of the current group
|
||||
* @param message the message to display the user
|
||||
* @param detailMessage another message to display to the user. This clarifies "message" in more
|
||||
* detail
|
||||
* @param showForegroundChooser whether to show the "only in foreground / always" option
|
||||
* @param showDoNotAsk whether to show the "do not ask again" option
|
||||
*/
|
||||
void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
|
||||
CharSequence message, CharSequence detailMessage, boolean showForegroundChooser,
|
||||
boolean showDoNotAsk);
|
||||
|
||||
/**
|
||||
* Sets the result listener that will be notified when the user responds
|
||||
* to a permissions grant request.
|
||||
*/
|
||||
GrantPermissionsViewHandler setResultListener(ResultListener listener);
|
||||
|
||||
/**
|
||||
* Called by {@link GrantPermissionsActivity} to save the state of this
|
||||
* view handler to the specified bundle.
|
||||
*/
|
||||
void saveInstanceState(Bundle outState);
|
||||
|
||||
/**
|
||||
* Called by {@link GrantPermissionsActivity} to load the state of this
|
||||
* view handler from the specified bundle.
|
||||
*/
|
||||
void loadInstanceState(Bundle savedInstanceState);
|
||||
|
||||
/**
|
||||
* Gives a chance for handling the back key.
|
||||
*/
|
||||
void onBackPressed();
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.TextAppearanceSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Space;
|
||||
|
||||
import androidx.wear.ble.view.AcceptDenyDialog;
|
||||
import androidx.wear.ble.view.WearableDialogHelper;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
/**
|
||||
* Watch-specific view handler for the grant permissions activity.
|
||||
*/
|
||||
final class GrantPermissionsWatchViewHandler implements GrantPermissionsViewHandler,
|
||||
DialogInterface.OnClickListener {
|
||||
private static final String TAG = "GrantPermsWatchViewH";
|
||||
|
||||
private static final String WATCH_HANDLER_BUNDLE = "watch_handler_bundle";
|
||||
private static final String DIALOG_BUNDLE = "dialog_bundle";
|
||||
private static final String GROUP_NAME = "group_name";
|
||||
private static final String SHOW_DO_NOT_ASK = "show_do_not_ask";
|
||||
private static final String ICON = "icon";
|
||||
private static final String MESSAGE = "message";
|
||||
private static final String CURRENT_PAGE_TEXT = "current_page_text";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private ResultListener mResultListener;
|
||||
|
||||
private Dialog mDialog;
|
||||
|
||||
private String mGroupName;
|
||||
private boolean mShowDoNotAsk;
|
||||
|
||||
private CharSequence mMessage;
|
||||
private String mCurrentPageText;
|
||||
private Icon mIcon;
|
||||
|
||||
GrantPermissionsWatchViewHandler(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GrantPermissionsWatchViewHandler setResultListener(ResultListener listener) {
|
||||
mResultListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView() {
|
||||
return new Space(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams) {
|
||||
outLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
outLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
outLayoutParams.format = PixelFormat.OPAQUE;
|
||||
outLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
|
||||
outLayoutParams.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
|
||||
CharSequence message, CharSequence detailMessage, boolean showForegroundChooser,
|
||||
boolean showDoNotAsk) {
|
||||
// TODO: Handle detailMessage
|
||||
// TODO: Handle showForegroundChooser
|
||||
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "updateUi() - groupName: " + groupName
|
||||
+ ", groupCount: " + groupCount
|
||||
+ ", groupIndex: " + groupIndex
|
||||
+ ", icon: " + icon
|
||||
+ ", message: " + message
|
||||
+ ", showDoNotAsk: " + showDoNotAsk);
|
||||
}
|
||||
|
||||
mGroupName = groupName;
|
||||
mShowDoNotAsk = showDoNotAsk;
|
||||
mMessage = message;
|
||||
mIcon = icon;
|
||||
mCurrentPageText = groupCount > 1
|
||||
? mContext.getString(R.string.current_permission_template,
|
||||
groupIndex + 1, groupCount)
|
||||
: null;
|
||||
showDialog(null);
|
||||
}
|
||||
|
||||
private void showDialog(Bundle savedInstanceState) {
|
||||
TypedArray a = mContext.obtainStyledAttributes(
|
||||
new int[] { android.R.attr.textColorPrimary });
|
||||
int color = a.getColor(0, mContext.getColor(android.R.color.white));
|
||||
a.recycle();
|
||||
Drawable drawable = mIcon == null ? null : mIcon.setTint(color).loadDrawable(mContext);
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
if (!TextUtils.isEmpty(mCurrentPageText)) {
|
||||
ssb.append(mCurrentPageText, new TextAppearanceSpan(mContext, R.style.BreadcrumbText),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append('\n');
|
||||
}
|
||||
if (!TextUtils.isEmpty(mMessage)) {
|
||||
ssb.append(mMessage, new TextAppearanceSpan(mContext, R.style.TitleText),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
mDialog = null;
|
||||
}
|
||||
|
||||
if (mShowDoNotAsk) {
|
||||
AlertDialog alertDialog = new WearableDialogHelper.DialogBuilder(mContext)
|
||||
.setPositiveIcon(R.drawable.confirm_button)
|
||||
.setNeutralIcon(R.drawable.cancel_button)
|
||||
.setNegativeIcon(R.drawable.deny_button)
|
||||
.setTitle(ssb)
|
||||
.setIcon(drawable)
|
||||
.setPositiveButton(R.string.grant_dialog_button_allow, this)
|
||||
.setNeutralButton(R.string.grant_dialog_button_deny, this)
|
||||
.setNegativeButton(R.string.grant_dialog_button_deny_dont_ask_again, this)
|
||||
.show();
|
||||
alertDialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setId(R.id.permission_allow_button);
|
||||
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL)
|
||||
.setId(R.id.permission_deny_button);
|
||||
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
.setId(R.id.permission_deny_dont_ask_again_button);
|
||||
|
||||
mDialog = alertDialog;
|
||||
} else {
|
||||
AcceptDenyDialog acceptDenyDialog = new AcceptDenyDialog(mContext);
|
||||
acceptDenyDialog.setTitle(ssb);
|
||||
acceptDenyDialog.setIcon(drawable);
|
||||
acceptDenyDialog.setPositiveButton(this);
|
||||
acceptDenyDialog.setNegativeButton(this);
|
||||
acceptDenyDialog.show();
|
||||
acceptDenyDialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setId(R.id.permission_allow_button);
|
||||
acceptDenyDialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
.setId(R.id.permission_deny_button);
|
||||
|
||||
mDialog = acceptDenyDialog;
|
||||
}
|
||||
mDialog.setCancelable(false);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mDialog.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInstanceState(Bundle outState) {
|
||||
Bundle b = new Bundle();
|
||||
b.putByte(SHOW_DO_NOT_ASK, (byte) (mShowDoNotAsk ? 1 : 0));
|
||||
b.putString(GROUP_NAME, mGroupName);
|
||||
b.putBundle(DIALOG_BUNDLE, mDialog.onSaveInstanceState());
|
||||
|
||||
outState.putBundle(WATCH_HANDLER_BUNDLE, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInstanceState(Bundle savedInstanceState) {
|
||||
Bundle b = savedInstanceState.getBundle(WATCH_HANDLER_BUNDLE);
|
||||
mShowDoNotAsk = b.getByte(SHOW_DO_NOT_ASK) == 1;
|
||||
mGroupName = b.getString(GROUP_NAME);
|
||||
showDialog(b.getBundle(DIALOG_BUNDLE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
notifyListener(DENIED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
notifyListener(GRANTED_ALWAYS);
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
notifyListener(DENIED);
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
/* In AlertDialog, the negative button is also a don't ask again button. */
|
||||
if (dialog instanceof AlertDialog) {
|
||||
notifyListener(DENIED_DO_NOT_ASK_AGAIN);
|
||||
} else {
|
||||
notifyListener(DENIED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListener(@Result int result) {
|
||||
if (mResultListener != null) {
|
||||
mResultListener.onPermissionGrantResult(mGroupName, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.android.packageinstaller.DeviceUtils;
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.ui.handheld.ManageStandardPermissionsFragment;
|
||||
import com.android.packageinstaller.permission.ui.wear.AppPermissionsFragmentWear;
|
||||
|
||||
public final class ManagePermissionsActivity extends OverlayTouchActivity {
|
||||
private static final String LOG_TAG = "ManagePermissionsActivity";
|
||||
|
||||
public static final String EXTRA_ALL_PERMISSIONS =
|
||||
"com.android.packageinstaller.extra.ALL_PERMISSIONS";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if (DeviceUtils.isAuto(this)) {
|
||||
setTheme(R.style.CarSettingTheme);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Fragment fragment;
|
||||
String action = getIntent().getAction();
|
||||
|
||||
switch (action) {
|
||||
case Intent.ACTION_MANAGE_PERMISSIONS: {
|
||||
if (DeviceUtils.isTelevision(this)) {
|
||||
fragment = com.android.packageinstaller.permission.ui.television
|
||||
.ManagePermissionsFragment.newInstance();
|
||||
} else {
|
||||
fragment = ManageStandardPermissionsFragment.newInstance();
|
||||
}
|
||||
} break;
|
||||
|
||||
case Intent.ACTION_MANAGE_APP_PERMISSIONS: {
|
||||
String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
|
||||
if (packageName == null) {
|
||||
Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (DeviceUtils.isAuto(this)) {
|
||||
fragment = com.android.packageinstaller.permission.ui.auto
|
||||
.AppPermissionsFragment.newInstance(packageName);
|
||||
} else if (DeviceUtils.isWear(this)) {
|
||||
fragment = AppPermissionsFragmentWear.newInstance(packageName);
|
||||
} else if (DeviceUtils.isTelevision(this)) {
|
||||
fragment = com.android.packageinstaller.permission.ui.television
|
||||
.AppPermissionsFragment.newInstance(packageName);
|
||||
} else {
|
||||
final boolean allPermissions = getIntent().getBooleanExtra(
|
||||
EXTRA_ALL_PERMISSIONS, false);
|
||||
if (allPermissions) {
|
||||
fragment = com.android.packageinstaller.permission.ui.handheld
|
||||
.AllAppPermissionsFragment.newInstance(packageName);
|
||||
} else {
|
||||
fragment = com.android.packageinstaller.permission.ui.handheld
|
||||
.AppPermissionsFragment.newInstance(packageName);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Intent.ACTION_MANAGE_PERMISSION_APPS: {
|
||||
String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME);
|
||||
if (permissionName == null) {
|
||||
Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (DeviceUtils.isTelevision(this)) {
|
||||
fragment = com.android.packageinstaller.permission.ui.television
|
||||
.PermissionAppsFragment.newInstance(permissionName);
|
||||
} else {
|
||||
fragment = com.android.packageinstaller.permission.ui.handheld
|
||||
.PermissionAppsFragment.newInstance(permissionName);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: {
|
||||
Log.w(LOG_TAG, "Unrecognized action " + action);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// in automotive mode, there's no system wide back button, so need to add that
|
||||
if (DeviceUtils.isAuto(this)) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class ManualLayoutFrame extends ViewGroup {
|
||||
private int mContentBottom;
|
||||
private int mWidth;
|
||||
|
||||
public ManualLayoutFrame(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void onConfigurationChanged() {
|
||||
mContentBottom = 0;
|
||||
mWidth = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (mWidth != 0) {
|
||||
int newWidth = mWidth;
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
switch (widthMode) {
|
||||
case MeasureSpec.AT_MOST: {
|
||||
newWidth = Math.min(mWidth, MeasureSpec.getSize(widthMeasureSpec));
|
||||
} break;
|
||||
case MeasureSpec.EXACTLY: {
|
||||
newWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
} break;
|
||||
}
|
||||
if (newWidth != mWidth) {
|
||||
mWidth = newWidth;
|
||||
}
|
||||
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY);
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mWidth == 0) {
|
||||
mWidth = getMeasuredWidth();
|
||||
}
|
||||
|
||||
measureChild(getChildAt(0), widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
// We want to keep the content bottom at the same place to avoid movement of the "Allow"
|
||||
// button.
|
||||
// Try to keep the content bottom at the same height. If this would move the dialog out of
|
||||
// the top of the screen move it down as much as possible, then keep it at that position for
|
||||
// the rest of the sequence of permission dialogs.
|
||||
View content = getChildAt(0);
|
||||
if (mContentBottom == 0 || content.getMeasuredHeight() > mContentBottom) {
|
||||
mContentBottom = (getMeasuredHeight() + content.getMeasuredHeight()) / 2;
|
||||
}
|
||||
final int contentLeft = (getMeasuredWidth() - content.getMeasuredWidth()) / 2;
|
||||
final int contentTop = mContentBottom - content.getMeasuredHeight();
|
||||
final int contentRight = contentLeft + content.getMeasuredWidth();
|
||||
content.layout(contentLeft, contentTop, contentRight, mContentBottom);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
|
||||
public class OverlayWarningDialog extends Activity implements OnClickListener, OnDismissListener {
|
||||
|
||||
private static final String TAG = "OverlayWarningDialog";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.screen_overlay_title)
|
||||
.setMessage(R.string.screen_overlay_message)
|
||||
.setPositiveButton(R.string.screen_overlay_button, this)
|
||||
.setOnDismissListener(this)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
try {
|
||||
startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "No manage overlay settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Extension of ImageView that correctly applies maxWidth and maxHeight.
|
||||
*/
|
||||
public class PreferenceImageView extends ImageView {
|
||||
|
||||
public PreferenceImageView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PreferenceImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int maxWidth = getMaxWidth();
|
||||
if (maxWidth != Integer.MAX_VALUE
|
||||
&& (maxWidth < widthSize || widthMode == MeasureSpec.UNSPECIFIED)) {
|
||||
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
|
||||
}
|
||||
}
|
||||
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
final int maxHeight = getMaxHeight();
|
||||
if (maxHeight != Integer.MAX_VALUE
|
||||
&& (maxHeight < heightSize || heightMode == MeasureSpec.UNSPECIFIED)) {
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
|
||||
}
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.packageinstaller.DeviceUtils;
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment
|
||||
.OnActionConfirmedListener;
|
||||
import com.android.packageinstaller.permission.ui.handheld.ReviewPermissionsFragment;
|
||||
import com.android.packageinstaller.permission.ui.wear.ReviewPermissionsWearFragment;
|
||||
|
||||
public final class ReviewPermissionsActivity extends FragmentActivity
|
||||
implements OnActionConfirmedListener {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
PackageInfo packageInfo = getTargetPackageInfo();
|
||||
if (packageInfo == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeviceUtils.isWear(this)) {
|
||||
Fragment fragment = ReviewPermissionsWearFragment.newInstance(packageInfo);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, fragment).commit();
|
||||
} else {
|
||||
setContentView(R.layout.review_permissions);
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.preferences_frame) == null) {
|
||||
getSupportFragmentManager().beginTransaction().add(R.id.preferences_frame,
|
||||
ReviewPermissionsFragment.newInstance(packageInfo)).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionConfirmed(String action) {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.preferences_frame);
|
||||
if (fragment instanceof OnActionConfirmedListener) {
|
||||
((OnActionConfirmedListener) fragment).onActionConfirmed(action);
|
||||
}
|
||||
}
|
||||
|
||||
private PackageInfo getTargetPackageInfo() {
|
||||
String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return getPackageManager().getPackageInfo(packageName,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2017 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.packageinstaller.permission.ui.auto;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.car.widget.ListItem;
|
||||
import androidx.car.widget.ListItemAdapter;
|
||||
import androidx.car.widget.ListItemProvider;
|
||||
import androidx.car.widget.PagedListView;
|
||||
import androidx.car.widget.TextListItem;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.model.AppPermissionGroup;
|
||||
import com.android.packageinstaller.permission.model.AppPermissions;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Contains all permissions in a list for a given application.
|
||||
*/
|
||||
public final class AppPermissionsFragment extends Fragment{
|
||||
|
||||
private static final String LOG_TAG = "ManagePermsFragment";
|
||||
public static final String EXTRA_LAYOUT = "extra_layout";
|
||||
|
||||
private AppPermissions mAppPermissions;
|
||||
|
||||
private String mPackageName;
|
||||
|
||||
protected PagedListView mListView;
|
||||
protected ListItemAdapter mPagedListAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param packageName the packageName of the application that we are listing the
|
||||
* permissions here.
|
||||
*/
|
||||
public static AppPermissionsFragment newInstance(String packageName) {
|
||||
AppPermissionsFragment fragment = new AppPermissionsFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putInt(EXTRA_LAYOUT, R.layout.car_app_permissions);
|
||||
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
getView().findViewById(R.id.action_bar_icon_container).setOnClickListener(
|
||||
v -> getActivity().onBackPressed());
|
||||
|
||||
mListView = (PagedListView) getView().findViewById(R.id.list);
|
||||
mPagedListAdapter = new ListItemAdapter(getContext(), getItemProvider());
|
||||
mListView.setAdapter(mPagedListAdapter);
|
||||
}
|
||||
|
||||
protected void notifyDataSetChanged() {
|
||||
mPagedListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(Intent.EXTRA_PACKAGE_NAME)) {
|
||||
mPackageName = savedInstanceState.getString(Intent.EXTRA_PACKAGE_NAME);
|
||||
} else if (getArguments() != null
|
||||
&& getArguments().containsKey(Intent.EXTRA_PACKAGE_NAME)) {
|
||||
mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
if (mPackageName == null) {
|
||||
Log.e(LOG_TAG, "package name is missing");
|
||||
return;
|
||||
}
|
||||
Activity activity = getActivity();
|
||||
PackageInfo packageInfo = getPackageInfo(activity, mPackageName);
|
||||
if (packageInfo == null) {
|
||||
Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
|
||||
activity.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mAppPermissions = new AppPermissions(activity, packageInfo, true, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(getArguments().getInt(EXTRA_LAYOUT), container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mAppPermissions.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of the LineItems to show up in the list
|
||||
*/
|
||||
public ListItemProvider getItemProvider() {
|
||||
ArrayList<ListItem> items = new ArrayList<>();
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
return new ListItemProvider.ListProvider(items);
|
||||
}
|
||||
|
||||
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
|
||||
if (!Utils.shouldShowPermission(group)) {
|
||||
continue;
|
||||
}
|
||||
items.add(new PermissionLineItem(group, context));
|
||||
}
|
||||
return new ListItemProvider.ListProvider(items);
|
||||
}
|
||||
|
||||
private static PackageInfo getPackageInfo(Activity activity, String packageName) {
|
||||
try {
|
||||
return activity.getPackageManager().getPackageInfo(
|
||||
packageName, PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (Log.isLoggable(LOG_TAG, Log.INFO)) {
|
||||
Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionLineItem extends TextListItem {
|
||||
|
||||
PermissionLineItem(AppPermissionGroup permissionGroup, Context context) {
|
||||
super(context);
|
||||
setTitle(permissionGroup.getLabel().toString());
|
||||
setPrimaryActionIcon(permissionGroup.getIconResId(), /* useLargeIcon= */ false);
|
||||
setSwitch(
|
||||
permissionGroup.areRuntimePermissionsGranted(),
|
||||
/* showDivider= */ false,
|
||||
(button, isChecked) -> {
|
||||
if (isChecked) {
|
||||
permissionGroup.grantRuntimePermissions(/* fixedByTheUser= */ false);
|
||||
return;
|
||||
}
|
||||
boolean grantedByDefault = permissionGroup.hasGrantedByDefaultPermission();
|
||||
if (!grantedByDefault && permissionGroup.doesSupportRuntimePermissions()) {
|
||||
permissionGroup.revokeRuntimePermissions(/* fixedByTheUser= */ false);
|
||||
return;
|
||||
}
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(grantedByDefault
|
||||
? R.string.system_warning
|
||||
: R.string.old_sdk_deny_warning)
|
||||
.setNegativeButton(R.string.cancel, /* listener= */ null)
|
||||
.setPositiveButton(R.string.grant_dialog_button_deny_anyway,
|
||||
(dialog, which) ->
|
||||
permissionGroup.revokeRuntimePermissions(
|
||||
/* fixedByTheUser= */ false))
|
||||
.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.packageinstaller.permission.ui.auto;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl;
|
||||
|
||||
/**
|
||||
* A {@link com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler} that is
|
||||
* specific for the auto use-case. In this case, the permissions dialog needs to be larger to make
|
||||
* clicking and reading safer in the car. Otherwise, the UI remains the same.
|
||||
*
|
||||
* <p>The reason this class extends {@link GrantPermissionsViewHandlerImpl} is so that it can
|
||||
* change the window params to allow the dialog's width to be larger.
|
||||
*/
|
||||
public class GrantPermissionsAutoViewHandler extends GrantPermissionsViewHandlerImpl {
|
||||
public GrantPermissionsAutoViewHandler(Activity activity, String appPackageName) {
|
||||
super(activity, appPackageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given {@link android.view.WindowManager.LayoutParams} to allow the dialog to take
|
||||
* up the entirety of the width.
|
||||
*/
|
||||
@Override
|
||||
public void updateWindowAttributes(WindowManager.LayoutParams outLayoutParams) {
|
||||
outLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
outLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui.handheld;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageItemInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PermissionGroupInfo;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.provider.Settings;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Switch;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.model.AppPermissionGroup;
|
||||
import com.android.packageinstaller.permission.model.Permission;
|
||||
import com.android.packageinstaller.permission.utils.ArrayUtils;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Show and manage individual permissions for an app.
|
||||
*
|
||||
* <p>Shows the list of individual runtime and non-runtime permissions the app has requested.
|
||||
*/
|
||||
public final class AllAppPermissionsFragment extends SettingsWithHeader {
|
||||
|
||||
private static final String LOG_TAG = "AllAppPermissionsFragment";
|
||||
|
||||
private static final String KEY_OTHER = "other_perms";
|
||||
|
||||
private static final String EXTRA_FILTER_GROUP =
|
||||
"com.android.packageinstaller.extra.FILTER_GROUP";
|
||||
|
||||
private List<AppPermissionGroup> mGroups;
|
||||
|
||||
public static AllAppPermissionsFragment newInstance(String packageName) {
|
||||
return newInstance(packageName, null);
|
||||
}
|
||||
|
||||
public static AllAppPermissionsFragment newInstance(String packageName, String filterGroup) {
|
||||
AllAppPermissionsFragment instance = new AllAppPermissionsFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
|
||||
arguments.putString(EXTRA_FILTER_GROUP, filterGroup);
|
||||
instance.setArguments(arguments);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
final ActionBar ab = getActivity().getActionBar();
|
||||
if (ab != null) {
|
||||
// If we target a group make this look like app permissions.
|
||||
if (getArguments().getString(EXTRA_FILTER_GROUP) == null) {
|
||||
ab.setTitle(R.string.all_permissions);
|
||||
} else {
|
||||
ab.setTitle(R.string.app_permissions);
|
||||
}
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
getFragmentManager().popBackStack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateUi() {
|
||||
if (getPreferenceScreen() != null) {
|
||||
getPreferenceScreen().removeAll();
|
||||
}
|
||||
addPreferencesFromResource(R.xml.all_permissions);
|
||||
PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER);
|
||||
ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting.
|
||||
prefs.add(otherGroup);
|
||||
String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
|
||||
String filterGroup = getArguments().getString(EXTRA_FILTER_GROUP);
|
||||
otherGroup.removeAll();
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
|
||||
try {
|
||||
PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
|
||||
|
||||
ApplicationInfo appInfo = info.applicationInfo;
|
||||
final Drawable icon =
|
||||
IconDrawableFactory.newInstance(getContext()).getBadgedIcon(appInfo);
|
||||
final CharSequence label = appInfo.loadLabel(pm);
|
||||
Intent infoIntent = null;
|
||||
if (!getActivity().getIntent().getBooleanExtra(
|
||||
AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) {
|
||||
infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", pkg, null));
|
||||
}
|
||||
setHeader(icon, label, infoIntent);
|
||||
|
||||
if (info.requestedPermissions != null) {
|
||||
for (int i = 0; i < info.requestedPermissions.length; i++) {
|
||||
PermissionInfo perm;
|
||||
try {
|
||||
perm = pm.getPermissionInfo(info.requestedPermissions[i], 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(LOG_TAG,
|
||||
"Can't get permission info for " + info.requestedPermissions[i], e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0
|
||||
|| (perm.flags & PermissionInfo.FLAG_REMOVED) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appInfo.isInstantApp()
|
||||
&& (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT)
|
||||
== 0) {
|
||||
continue;
|
||||
}
|
||||
if (appInfo.targetSdkVersion < Build.VERSION_CODES.M
|
||||
&& (perm.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
|
||||
!= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
|
||||
== PermissionInfo.PROTECTION_DANGEROUS) {
|
||||
PackageItemInfo group = getGroup(perm.group, pm);
|
||||
if (group == null) {
|
||||
group = perm;
|
||||
}
|
||||
// If we show a targeted group, then ignore everything else.
|
||||
if (filterGroup != null && !group.name.equals(filterGroup)) {
|
||||
continue;
|
||||
}
|
||||
PreferenceGroup pref = findOrCreate(group, pm, prefs);
|
||||
pref.addPreference(getPreference(info, perm, group, pm));
|
||||
} else if (filterGroup == null) {
|
||||
if ((perm.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
|
||||
== PermissionInfo.PROTECTION_NORMAL) {
|
||||
PermissionGroupInfo group = getGroup(perm.group, pm);
|
||||
otherGroup.addPreference(getPreference(info,
|
||||
perm, group, pm));
|
||||
}
|
||||
}
|
||||
|
||||
// If we show a targeted group, then don't show 'other' permissions.
|
||||
if (filterGroup != null) {
|
||||
getPreferenceScreen().removePreference(otherGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.e(LOG_TAG, "Problem getting package info for " + pkg, e);
|
||||
}
|
||||
// Sort an ArrayList of the groups and then set the order from the sorting.
|
||||
Collections.sort(prefs, new Comparator<Preference>() {
|
||||
@Override
|
||||
public int compare(Preference lhs, Preference rhs) {
|
||||
String lKey = lhs.getKey();
|
||||
String rKey = rhs.getKey();
|
||||
if (lKey.equals(KEY_OTHER)) {
|
||||
return 1;
|
||||
} else if (rKey.equals(KEY_OTHER)) {
|
||||
return -1;
|
||||
} else if (Utils.isModernPermissionGroup(lKey)
|
||||
!= Utils.isModernPermissionGroup(rKey)) {
|
||||
return Utils.isModernPermissionGroup(lKey) ? -1 : 1;
|
||||
}
|
||||
return lhs.getTitle().toString().compareTo(rhs.getTitle().toString());
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < prefs.size(); i++) {
|
||||
prefs.get(i).setOrder(i);
|
||||
}
|
||||
}
|
||||
|
||||
private PermissionGroupInfo getGroup(String group, PackageManager pm) {
|
||||
try {
|
||||
return pm.getPermissionGroupInfo(group, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PreferenceGroup findOrCreate(PackageItemInfo group, PackageManager pm,
|
||||
ArrayList<Preference> prefs) {
|
||||
PreferenceGroup pref = (PreferenceGroup) findPreference(group.name);
|
||||
if (pref == null) {
|
||||
pref = new PreferenceCategory(getContext());
|
||||
pref.setKey(group.name);
|
||||
pref.setTitle(group.loadLabel(pm));
|
||||
prefs.add(pref);
|
||||
getPreferenceScreen().addPreference(pref);
|
||||
}
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference getPreference(PackageInfo packageInfo, PermissionInfo perm,
|
||||
PackageItemInfo group, PackageManager pm) {
|
||||
final Preference pref;
|
||||
|
||||
// We allow individual permission control for some permissions if review enabled
|
||||
final boolean mutable = Utils.isPermissionIndividuallyControlled(getContext(), perm.name);
|
||||
if (mutable) {
|
||||
pref = new MyMultiTargetSwitchPreference(getContext(), perm.name,
|
||||
getPermissionForegroundGroup(packageInfo, perm.name));
|
||||
} else {
|
||||
pref = new Preference(getContext());
|
||||
}
|
||||
|
||||
Drawable icon = null;
|
||||
if (perm.icon != 0) {
|
||||
icon = perm.loadIcon(pm);
|
||||
} else if (group != null && group.icon != 0) {
|
||||
icon = group.loadIcon(pm);
|
||||
} else {
|
||||
icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
|
||||
}
|
||||
pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal));
|
||||
pref.setTitle(perm.loadSafeLabel(pm, 20000, PackageItemInfo.SAFE_LABEL_FLAG_TRIM));
|
||||
pref.setSingleLineTitle(false);
|
||||
final CharSequence desc = perm.loadDescription(pm);
|
||||
|
||||
pref.setOnPreferenceClickListener((Preference preference) -> {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(desc)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
return mutable;
|
||||
});
|
||||
|
||||
return pref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (foreground-) {@link AppPermissionGroup group} a permission belongs to.
|
||||
*
|
||||
* <p>For foreground or non background-foreground permissions this returns the group
|
||||
* {@link AppPermissionGroup} the permission is in. For background permisisons this returns
|
||||
* the group the matching foreground
|
||||
*
|
||||
* @param packageInfo Package information about the app
|
||||
* @param permission The permission that belongs to a group
|
||||
*
|
||||
* @return the group the permissions belongs to
|
||||
*/
|
||||
private AppPermissionGroup getPermissionForegroundGroup(PackageInfo packageInfo,
|
||||
String permission) {
|
||||
AppPermissionGroup appPermissionGroup = null;
|
||||
if (mGroups != null) {
|
||||
final int groupCount = mGroups.size();
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
AppPermissionGroup currentPermissionGroup = mGroups.get(i);
|
||||
if (currentPermissionGroup.hasPermission(permission)) {
|
||||
appPermissionGroup = currentPermissionGroup;
|
||||
break;
|
||||
}
|
||||
if (currentPermissionGroup.getBackgroundPermissions() != null
|
||||
&& currentPermissionGroup.getBackgroundPermissions().hasPermission(
|
||||
permission)) {
|
||||
appPermissionGroup = currentPermissionGroup.getBackgroundPermissions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appPermissionGroup == null) {
|
||||
appPermissionGroup = AppPermissionGroup.create(
|
||||
getContext(), packageInfo, permission);
|
||||
if (mGroups == null) {
|
||||
mGroups = new ArrayList<>();
|
||||
}
|
||||
mGroups.add(appPermissionGroup);
|
||||
}
|
||||
return appPermissionGroup;
|
||||
}
|
||||
|
||||
private static final class MyMultiTargetSwitchPreference extends MultiTargetSwitchPreference {
|
||||
MyMultiTargetSwitchPreference(Context context, String permission,
|
||||
AppPermissionGroup appPermissionGroup) {
|
||||
super(context);
|
||||
|
||||
setChecked(appPermissionGroup.areRuntimePermissionsGranted(
|
||||
new String[] {permission}));
|
||||
|
||||
setSwitchOnClickListener(v -> {
|
||||
Switch switchView = (Switch) v;
|
||||
if (switchView.isChecked()) {
|
||||
appPermissionGroup.grantRuntimePermissions(false,
|
||||
new String[]{permission});
|
||||
// We are granting a permission from a group but since this is an
|
||||
// individual permission control other permissions in the group may
|
||||
// be revoked, hence we need to mark them user fixed to prevent the
|
||||
// app from requesting a non-granted permission and it being granted
|
||||
// because another permission in the group is granted. This applies
|
||||
// only to apps that support runtime permissions.
|
||||
if (appPermissionGroup.doesSupportRuntimePermissions()) {
|
||||
int grantedCount = 0;
|
||||
String[] revokedPermissionsToFix = null;
|
||||
final int permissionCount = appPermissionGroup.getPermissions().size();
|
||||
for (int i = 0; i < permissionCount; i++) {
|
||||
Permission current = appPermissionGroup.getPermissions().get(i);
|
||||
if (!current.isGranted()) {
|
||||
if (!current.isUserFixed()) {
|
||||
revokedPermissionsToFix = ArrayUtils.appendString(
|
||||
revokedPermissionsToFix, current.getName());
|
||||
}
|
||||
} else {
|
||||
grantedCount++;
|
||||
}
|
||||
}
|
||||
if (revokedPermissionsToFix != null) {
|
||||
// If some permissions were not granted then they should be fixed.
|
||||
appPermissionGroup.revokeRuntimePermissions(true,
|
||||
revokedPermissionsToFix);
|
||||
} else if (appPermissionGroup.getPermissions().size() == grantedCount) {
|
||||
// If all permissions are granted then they should not be fixed.
|
||||
appPermissionGroup.grantRuntimePermissions(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appPermissionGroup.revokeRuntimePermissions(true,
|
||||
new String[]{permission});
|
||||
// If we just revoked the last permission we need to clear
|
||||
// the user fixed state as now the app should be able to
|
||||
// request them at runtime if supported.
|
||||
if (appPermissionGroup.doesSupportRuntimePermissions()
|
||||
&& !appPermissionGroup.areRuntimePermissionsGranted()) {
|
||||
appPermissionGroup.revokeRuntimePermissions(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui.handheld;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.provider.Settings;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.model.AppPermissionGroup;
|
||||
import com.android.packageinstaller.permission.model.AppPermissions;
|
||||
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
|
||||
import com.android.packageinstaller.permission.utils.Utils;
|
||||
import com.android.settingslib.HelpUtils;
|
||||
|
||||
/**
|
||||
* Show and manage permission groups for an app.
|
||||
*
|
||||
* <p>Shows the list of permission groups the app has requested at one permission for.
|
||||
*/
|
||||
public final class AppPermissionsFragment extends SettingsWithHeader
|
||||
implements PermissionPreference.PermissionPreferenceChangeListener,
|
||||
PermissionPreference.PermissionPreferenceOwnerFragment {
|
||||
|
||||
private static final String LOG_TAG = "ManagePermsFragment";
|
||||
|
||||
static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
|
||||
|
||||
private static final int MENU_ALL_PERMS = 0;
|
||||
|
||||
private ArraySet<AppPermissionGroup> mToggledGroups;
|
||||
private AppPermissions mAppPermissions;
|
||||
private PreferenceScreen mExtraScreen;
|
||||
|
||||
private boolean mHasConfirmedRevoke;
|
||||
|
||||
public static AppPermissionsFragment newInstance(String packageName) {
|
||||
return setPackageName(new AppPermissionsFragment(), packageName);
|
||||
}
|
||||
|
||||
private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setLoading(true /* loading */, false /* animate */);
|
||||
setHasOptionsMenu(true);
|
||||
final ActionBar ab = getActivity().getActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
|
||||
Activity activity = getActivity();
|
||||
PackageInfo packageInfo = getPackageInfo(activity, packageName);
|
||||
if (packageInfo == null) {
|
||||
Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
|
||||
activity.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mAppPermissions = new AppPermissions(activity, packageInfo, true, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mAppPermissions.refresh();
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
getActivity().finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
case MENU_ALL_PERMS: {
|
||||
showAllPermissions(null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (mAppPermissions != null) {
|
||||
bindUi(this, mAppPermissions.getPackageInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
menu.add(Menu.NONE, MENU_ALL_PERMS, Menu.NONE, R.string.all_permissions);
|
||||
HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
|
||||
getClass().getName());
|
||||
}
|
||||
|
||||
private void showAllPermissions(String filterGroup) {
|
||||
Fragment frag = AllAppPermissionsFragment.newInstance(
|
||||
getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
|
||||
filterGroup);
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, frag)
|
||||
.addToBackStack("AllPerms")
|
||||
.commit();
|
||||
}
|
||||
|
||||
private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
|
||||
Activity activity = fragment.getActivity();
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
ApplicationInfo appInfo = packageInfo.applicationInfo;
|
||||
Intent infoIntent = null;
|
||||
if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
|
||||
infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", packageInfo.packageName, null));
|
||||
}
|
||||
|
||||
Drawable icon = IconDrawableFactory.newInstance(activity).getBadgedIcon(appInfo);
|
||||
CharSequence label = appInfo.loadLabel(pm);
|
||||
fragment.setHeader(icon, label, infoIntent);
|
||||
|
||||
ActionBar ab = activity.getActionBar();
|
||||
if (ab != null) {
|
||||
ab.setTitle(R.string.app_permissions);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePreferences() {
|
||||
Context context = getActivity();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreferenceScreen screen = getPreferenceScreen();
|
||||
if (screen == null) {
|
||||
screen = getPreferenceManager().createPreferenceScreen(getActivity());
|
||||
setPreferenceScreen(screen);
|
||||
}
|
||||
|
||||
screen.removeAll();
|
||||
|
||||
if (mExtraScreen != null) {
|
||||
mExtraScreen.removeAll();
|
||||
}
|
||||
|
||||
final Preference extraPerms = new Preference(context);
|
||||
extraPerms.setIcon(R.drawable.ic_toc);
|
||||
extraPerms.setTitle(R.string.additional_permissions);
|
||||
|
||||
for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
|
||||
if (!Utils.shouldShowPermission(group)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
|
||||
|
||||
PermissionPreference preference = new PermissionPreference(this, group, this);
|
||||
preference.setKey(group.getName());
|
||||
Drawable icon = Utils.loadDrawable(context.getPackageManager(),
|
||||
group.getIconPkg(), group.getIconResId());
|
||||
preference.setIcon(Utils.applyTint(getContext(), icon,
|
||||
android.R.attr.colorControlNormal));
|
||||
preference.setTitle(group.getLabel());
|
||||
|
||||
if (isPlatform) {
|
||||
screen.addPreference(preference);
|
||||
} else {
|
||||
if (mExtraScreen == null) {
|
||||
mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
|
||||
}
|
||||
mExtraScreen.addPreference(preference);
|
||||
}
|
||||
}
|
||||
|
||||
if (mExtraScreen != null) {
|
||||
extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
|
||||
setPackageName(frag, getArguments().getString(Intent.EXTRA_PACKAGE_NAME));
|
||||
frag.setTargetFragment(AppPermissionsFragment.this, 0);
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, frag)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
int count = mExtraScreen.getPreferenceCount();
|
||||
extraPerms.setSummary(getResources().getQuantityString(
|
||||
R.plurals.additional_permissions_more, count, count));
|
||||
screen.addPreference(extraPerms);
|
||||
}
|
||||
|
||||
setLoading(false /* loading */, true /* animate */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreferenceChanged(String key) {
|
||||
if (mToggledGroups == null) {
|
||||
mToggledGroups = new ArraySet<>();
|
||||
}
|
||||
mToggledGroups.add(mAppPermissions.getPermissionGroup(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
logToggledGroups();
|
||||
}
|
||||
|
||||
private void logToggledGroups() {
|
||||
if (mToggledGroups != null) {
|
||||
SafetyNetLogger.logPermissionsToggled(mToggledGroups);
|
||||
mToggledGroups = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static PackageInfo getPackageInfo(Activity activity, String packageName) {
|
||||
try {
|
||||
return activity.getPackageManager().getPackageInfo(
|
||||
packageName, PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldConfirmDefaultPermissionRevoke() {
|
||||
return !mHasConfirmedRevoke;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hasConfirmDefaultPermissionRevoke() {
|
||||
mHasConfirmedRevoke = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackgroundAccessChosen(String key, int chosenItem) {
|
||||
((PermissionPreference) getPreferenceScreen().findPreference(key))
|
||||
.onBackgroundAccessChosen(chosenItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenyAnyWay(String key, @PermissionPreference.ChangeTarget int changeTarget) {
|
||||
((PermissionPreference) getPreferenceScreen().findPreference(key)).onDenyAnyWay(
|
||||
changeTarget);
|
||||
}
|
||||
|
||||
public static class AdditionalPermissionsFragment extends SettingsWithHeader {
|
||||
AppPermissionsFragment mOuterFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mOuterFragment = (AppPermissionsFragment) getTargetFragment();
|
||||
super.onCreate(savedInstanceState);
|
||||
setHeader(mOuterFragment.mIcon, mOuterFragment.mLabel, mOuterFragment.mInfoIntent);
|
||||
setHasOptionsMenu(true);
|
||||
setPreferenceScreen(mOuterFragment.mExtraScreen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
|
||||
bindUi(this, getPackageInfo(getActivity(), packageName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
getFragmentManager().popBackStack();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.packageinstaller.permission.ui.handheld;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.transition.TransitionValues;
|
||||
import android.transition.Visibility;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.packageinstaller.R;
|
||||
import com.android.packageinstaller.permission.ui.ButtonBarLayout;
|
||||
import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler;
|
||||
import com.android.packageinstaller.permission.ui.ManagePermissionsActivity;
|
||||
import com.android.packageinstaller.permission.ui.ManualLayoutFrame;
|
||||
|
||||
public class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler,
|
||||
OnClickListener, RadioGroup.OnCheckedChangeListener {
|
||||
|
||||
public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
|
||||
public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT";
|
||||
public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX";
|
||||
public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON";
|
||||
public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE";
|
||||
private static final String ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE";
|
||||
|
||||
public static final String ARG_GROUP_SHOW_DO_NOT_ASK = "ARG_GROUP_SHOW_DO_NOT_ASK";
|
||||
private static final String ARG_GROUP_SHOW_FOREGOUND_CHOOSER =
|
||||
"ARG_GROUP_SHOW_FOREGOUND_CHOOSER";
|
||||
|
||||
public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED";
|
||||
private static final String ARG_GROUP_ALWAYS_OPTION_CHECKED = "ARG_GROUP_ALWAYS_OPTION_CHECKED";
|
||||
|
||||
// Animation parameters.
|
||||
private static final long SWITCH_TIME_MILLIS = 75;
|
||||
private static final long ANIMATION_DURATION_MILLIS = 200;
|
||||
|
||||
private final Activity mActivity;
|
||||
private final String mAppPackageName;
|
||||
private final boolean mPermissionsIndividuallyControlled;
|
||||
|
||||
private ResultListener mResultListener;
|
||||
|
||||
// Configuration of the current dialog
|
||||
private String mGroupName;
|
||||
private int mGroupCount;
|
||||
private int mGroupIndex;
|
||||
private Icon mGroupIcon;
|
||||
private CharSequence mGroupMessage;
|
||||
private CharSequence mDetailMessage;
|
||||
|
||||
private boolean mShowDonNotAsk;
|
||||
private boolean mShowForegroundChooser;
|
||||
|
||||
// Views
|
||||
private ImageView mIconView;
|
||||
private TextView mCurrentGroupView;
|
||||
private TextView mMessageView;
|
||||
private TextView mDetailMessageView;
|
||||
private CheckBox mDoNotAskCheckbox;
|
||||
private RadioGroup mForegroundChooser;
|
||||
private RadioButton mForegroundOnlyOption;
|
||||
private RadioButton mAlwaysOption;
|
||||
private RadioButton mDenyAndDontAskAgainOption;
|
||||
private Button mAllowButton;
|
||||
private Button mMoreInfoButton;
|
||||
private ManualLayoutFrame mRootView;
|
||||
private ViewGroup mContentContainer;
|
||||
private Space mSpacer;
|
||||
|
||||
public GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName) {
|
||||
mActivity = activity;
|
||||
mAppPackageName = appPackageName;
|
||||
mPermissionsIndividuallyControlled =
|
||||
activity.getPackageManager().arePermissionsIndividuallyControlled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
|
||||
mResultListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInstanceState(Bundle arguments) {
|
||||
arguments.putString(ARG_GROUP_NAME, mGroupName);
|
||||
arguments.putInt(ARG_GROUP_COUNT, mGroupCount);
|
||||
arguments.putInt(ARG_GROUP_INDEX, mGroupIndex);
|
||||
arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon);
|
||||
arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage);
|
||||
arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, mDetailMessage);
|
||||
|
||||
arguments.putBoolean(ARG_GROUP_SHOW_DO_NOT_ASK, mShowDonNotAsk);
|
||||
arguments.putBoolean(ARG_GROUP_SHOW_FOREGOUND_CHOOSER, mShowForegroundChooser);
|
||||
|
||||
arguments.putBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED, isDoNotAskAgainChecked());
|
||||
arguments.putBoolean(ARG_GROUP_ALWAYS_OPTION_CHECKED, mAlwaysOption.isSelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInstanceState(Bundle savedInstanceState) {
|
||||
mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
|
||||
mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE);
|
||||
mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON);
|
||||
mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT);
|
||||
mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX);
|
||||
mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE);
|
||||
|
||||
mShowDonNotAsk = savedInstanceState.getBoolean(ARG_GROUP_SHOW_DO_NOT_ASK);
|
||||
mShowForegroundChooser = savedInstanceState.getBoolean(ARG_GROUP_SHOW_FOREGOUND_CHOOSER);
|
||||
|
||||
updateAll(savedInstanceState.getBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED),
|
||||
savedInstanceState.getBoolean(ARG_GROUP_ALWAYS_OPTION_CHECKED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
|
||||
CharSequence message, CharSequence detailMessage, boolean showForegroundChooser,
|
||||
boolean showDonNotAsk) {
|
||||
boolean isNewGroup = mGroupIndex != groupIndex;
|
||||
boolean isDoNotAskAgainChecked = mDoNotAskCheckbox.isChecked();
|
||||
boolean isAlwaysOptionChecked = mAlwaysOption.isChecked();
|
||||
if (isNewGroup) {
|
||||
isDoNotAskAgainChecked = false;
|
||||
isAlwaysOptionChecked = false;
|
||||
}
|
||||
|
||||
mGroupName = groupName;
|
||||
mGroupCount = groupCount;
|
||||
mGroupIndex = groupIndex;
|
||||
mGroupIcon = icon;
|
||||
mGroupMessage = message;
|
||||
mShowDonNotAsk = showDonNotAsk;
|
||||
mDetailMessage = detailMessage;
|
||||
mShowForegroundChooser = showForegroundChooser;
|
||||
|
||||
// If this is a second (or later) permission and the views exist, then animate.
|
||||
if (mIconView != null) {
|
||||
if (isNewGroup) {
|
||||
animateToPermission(isDoNotAskAgainChecked, isAlwaysOptionChecked);
|
||||
} else {
|
||||
updateAll(isDoNotAskAgainChecked, isAlwaysOptionChecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAll(boolean isDoNotAskAgainChecked, boolean isAlwaysOptionChecked) {
|
||||
updateDescription();
|
||||
updateDetailDescription();
|
||||
updateGroup();
|
||||
updateDoNotAskCheckBoxAndForegroundOption(isDoNotAskAgainChecked, isAlwaysOptionChecked);
|
||||
}
|
||||
|
||||
private void animateToPermission(boolean isDoNotAskAgainChecked,
|
||||
boolean isAlwaysOptionChecked) {
|
||||
final View newContent = bindNewContent();
|
||||
|
||||
updateDescription();
|
||||
updateDetailDescription();
|
||||
updateDoNotAskCheckBoxAndForegroundOption(isDoNotAskAgainChecked, isAlwaysOptionChecked);
|
||||
// Update group when the content changes (in onAppear below)
|
||||
|
||||
final View oldView = mContentContainer.getChildAt(0);
|
||||
|
||||
// Grow or shrink the content container to size of new content
|
||||
ChangeBounds growShrinkToNewContentSize = new ChangeBounds();
|
||||
growShrinkToNewContentSize.setDuration(ANIMATION_DURATION_MILLIS);
|
||||
growShrinkToNewContentSize.setInterpolator(AnimationUtils.loadInterpolator(mActivity,
|
||||
android.R.interpolator.fast_out_slow_in));
|
||||
|
||||
// With a delay hide the old content and show the new content
|
||||
Visibility changeContent = new Visibility() {
|
||||
@Override
|
||||
public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
|
||||
TransitionValues endValues) {
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
|
||||
ValueAnimator v = ValueAnimator.ofFloat(0, 1);
|
||||
|
||||
v.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.VISIBLE);
|
||||
updateGroup();
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Animator onDisappear(ViewGroup sceneRoot, final View view,
|
||||
TransitionValues startValues, TransitionValues endValues) {
|
||||
ValueAnimator v = ValueAnimator.ofFloat(0, 1);
|
||||
|
||||
int[] location = new int[2];
|
||||
// The removed view is put into the overlay that is relative to the window. Hence
|
||||
// it does not get moved along with the changing parent view. This is done manually
|
||||
// here.
|
||||
v.addUpdateListener(animation -> {
|
||||
mContentContainer.getLocationInWindow(location);
|
||||
view.setTop(location[1]);
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
};
|
||||
changeContent.setDuration(SWITCH_TIME_MILLIS);
|
||||
|
||||
TransitionSet combinedAnimation = new TransitionSet();
|
||||
combinedAnimation.addTransition(growShrinkToNewContentSize);
|
||||
combinedAnimation.addTransition(changeContent);
|
||||
combinedAnimation.setOrdering(TransitionSet.ORDERING_TOGETHER);
|
||||
combinedAnimation.setMatchOrder(Transition.MATCH_INSTANCE);
|
||||
|
||||
TransitionManager.beginDelayedTransition(mRootView, combinedAnimation);
|
||||
mContentContainer.removeView(oldView);
|
||||
mContentContainer.addView(newContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this objects fields to point to the a content view. A content view encapsulates the
|
||||
* permission request message, the detail message, the always deny checkbox, and the foreground
|
||||
* chooser.
|
||||
*
|
||||
* @return The new content view
|
||||
*/
|
||||
private View bindNewContent() {
|
||||
ViewGroup content = (ViewGroup) LayoutInflater.from(mActivity)
|
||||
.inflate(R.layout.grant_permissions_content, mContentContainer, false);
|
||||
|
||||
mMessageView = content.requireViewById(R.id.permission_message);
|
||||
mDetailMessageView = content.requireViewById(R.id.detail_message);
|
||||
mIconView = content.requireViewById(R.id.permission_icon);
|
||||
mDoNotAskCheckbox = content.requireViewById(R.id.do_not_ask_checkbox);
|
||||
mSpacer = content.requireViewById(R.id.detail_message_do_not_ask_checkbox_space);
|
||||
mForegroundChooser = content.requireViewById(R.id.foreground_or_always_radiogroup);
|
||||
mForegroundOnlyOption = content.requireViewById(R.id.foreground_only_radio_button);
|
||||
mAlwaysOption = content.requireViewById(R.id.always_radio_button);
|
||||
mDenyAndDontAskAgainOption = content.requireViewById(R.id.deny_dont_ask_again_radio_button);
|
||||
|
||||
mDoNotAskCheckbox.setOnClickListener(this);
|
||||
mDenyAndDontAskAgainOption.setOnClickListener(this);
|
||||
mForegroundChooser.setOnCheckedChangeListener(this);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createView() {
|
||||
mRootView = (ManualLayoutFrame) LayoutInflater.from(mActivity)
|
||||
.inflate(R.layout.grant_permissions, null);
|
||||
mContentContainer = mRootView.requireViewById(R.id.content_container);
|
||||
mContentContainer.removeAllViews();
|
||||
mContentContainer.addView(bindNewContent());
|
||||
|
||||
mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text);
|
||||
mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button);
|
||||
mAllowButton.setOnClickListener(this);
|
||||
|
||||
if (mPermissionsIndividuallyControlled) {
|
||||
mMoreInfoButton = (Button) mRootView.findViewById(R.id.permission_more_info_button);
|
||||
mMoreInfoButton.setVisibility(View.VISIBLE);
|
||||
mMoreInfoButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this);
|
||||
|
||||
((ButtonBarLayout) mRootView.requireViewById(R.id.button_group)).setAllowStacking(true);
|
||||
|
||||
if (mGroupName != null) {
|
||||
updateAll(false, false);
|
||||
}
|
||||
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWindowAttributes(LayoutParams outLayoutParams) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
private void updateDescription() {
|
||||
if (mGroupIcon != null) {
|
||||
mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity));
|
||||
}
|
||||
mMessageView.setText(mGroupMessage);
|
||||
}
|
||||
|
||||
private void updateDetailDescription() {
|
||||
if (mDetailMessage == null) {
|
||||
mDetailMessageView.setVisibility(View.GONE);
|
||||
mSpacer.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (mShowDonNotAsk) {
|
||||
mSpacer.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSpacer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mDetailMessageView.setText(mDetailMessage);
|
||||
mDetailMessageView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGroup() {
|
||||
if (mGroupCount > 1) {
|
||||
mCurrentGroupView.setVisibility(View.VISIBLE);
|
||||
mCurrentGroupView.setText(mActivity.getString(R.string.current_permission_template,
|
||||
mGroupIndex + 1, mGroupCount));
|
||||
} else {
|
||||
mCurrentGroupView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDoNotAskCheckBoxAndForegroundOption(boolean isDoNotAskAgainChecked,
|
||||
boolean isAlwaysSelected) {
|
||||
if (mShowForegroundChooser) {
|
||||
mForegroundChooser.setVisibility(View.VISIBLE);
|
||||
mDoNotAskCheckbox.setVisibility(View.GONE);
|
||||
|
||||
if (isAlwaysSelected) {
|
||||
mAlwaysOption.setSelected(true);
|
||||
}
|
||||
|
||||
if (mShowDonNotAsk) {
|
||||
mDenyAndDontAskAgainOption.setSelected(isDoNotAskAgainChecked);
|
||||
mDenyAndDontAskAgainOption.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mDenyAndDontAskAgainOption.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
mForegroundChooser.setVisibility(View.GONE);
|
||||
if (mShowDonNotAsk) {
|
||||
mDoNotAskCheckbox.setVisibility(View.VISIBLE);
|
||||
mDoNotAskCheckbox.setChecked(isDoNotAskAgainChecked);
|
||||
} else {
|
||||
mDoNotAskCheckbox.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
mAllowButton.setEnabled(!isDoNotAskAgainChecked() && isOptionChosenIfNeeded());
|
||||
}
|
||||
|
||||
private boolean isDoNotAskAgainChecked() {
|
||||
return (mDoNotAskCheckbox.getVisibility() == View.VISIBLE
|
||||
&& mDoNotAskCheckbox.isChecked())
|
||||
|| (mDenyAndDontAskAgainOption.getVisibility() == View.VISIBLE
|
||||
&& mDenyAndDontAskAgainOption.isChecked());
|
||||
}
|
||||
|
||||
private boolean isOptionChosenIfNeeded() {
|
||||
return !mShowForegroundChooser
|
||||
|| (mForegroundOnlyOption.isChecked()
|
||||
|| (mDenyAndDontAskAgainOption.getVisibility() == View.VISIBLE
|
||||
&& mDenyAndDontAskAgainOption.isChecked())
|
||||
|| (mAlwaysOption.getVisibility() == View.VISIBLE && mAlwaysOption.isChecked()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.permission_allow_button:
|
||||
if (mResultListener != null) {
|
||||
view.performAccessibilityAction(
|
||||
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
|
||||
if (mShowForegroundChooser && mForegroundOnlyOption.isChecked()) {
|
||||
mResultListener.onPermissionGrantResult(mGroupName,
|
||||
GRANTED_FOREGROUND_ONLY);
|
||||
} else {
|
||||
mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.permission_deny_button:
|
||||
if (mResultListener != null) {
|
||||
view.performAccessibilityAction(
|
||||
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
|
||||
if (isDoNotAskAgainChecked()) {
|
||||
mResultListener.onPermissionGrantResult(mGroupName,
|
||||
DENIED_DO_NOT_ASK_AGAIN);
|
||||
} else {
|
||||
mResultListener.onPermissionGrantResult(mGroupName, DENIED);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.permission_more_info_button:
|
||||
Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPackageName);
|
||||
intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
|
||||
mActivity.startActivity(intent);
|
||||
break;
|
||||
}
|
||||
|
||||
mAllowButton.setEnabled(!isDoNotAskAgainChecked() && isOptionChosenIfNeeded());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mResultListener != null) {
|
||||
mResultListener.onPermissionGrantResult(mGroupName, DENIED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
||||
mAllowButton.setEnabled(!isDoNotAskAgainChecked() && isOptionChosenIfNeeded());
|
||||
}
|
||||
}
|
||||