Add install confirm+perms screen to SystemInstaller
@ -292,6 +292,14 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".installer.InstallConfirmActivity"
|
||||
android:label=""
|
||||
android:parentActivityName=".FDroid">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".views.ManageReposActivity"
|
||||
android:label="@string/app_name"
|
||||
|
BIN
F-Droid/res/drawable-hdpi/ic_bullet_key_permission.png
Normal file
After Width: | Height: | Size: 545 B |
BIN
F-Droid/res/drawable-hdpi/ic_coins_s.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
F-Droid/res/drawable-hdpi/tab_unselected_holo.9.png
Normal file
After Width: | Height: | Size: 153 B |
BIN
F-Droid/res/drawable-ldpi/ic_bullet_key_permission.png
Normal file
After Width: | Height: | Size: 611 B |
BIN
F-Droid/res/drawable-mdpi/ic_bullet_key_permission.png
Normal file
After Width: | Height: | Size: 453 B |
BIN
F-Droid/res/drawable-mdpi/ic_coins_s.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
F-Droid/res/drawable-mdpi/tab_unselected_holo.9.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
F-Droid/res/drawable-xhdpi/ic_bullet_key_permission.png
Normal file
After Width: | Height: | Size: 646 B |
BIN
F-Droid/res/drawable-xhdpi/ic_coins_s.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
F-Droid/res/drawable-xhdpi/tab_unselected_holo.9.png
Normal file
After Width: | Height: | Size: 166 B |
BIN
F-Droid/res/drawable-xxhdpi/ic_bullet_key_permission.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
F-Droid/res/drawable-xxhdpi/tab_unselected_holo.9.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
22
F-Droid/res/drawable/ic_text_dot.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?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.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:inset="10dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?android:attr/textColorSecondary" />
|
||||
<size android:width="4dp" android:height="4dp" />
|
||||
</shape>
|
||||
</inset>
|
50
F-Droid/res/layout/app_permission_item.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Defines the layout of a single permission item.
|
||||
-->
|
||||
|
||||
<view class="org.fdroid.fdroid.installer.AppSecurityPermissions$PermissionItemView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/perm_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/dividerVertical" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/perm_name"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|left" />
|
||||
|
||||
</view>
|
76
F-Droid/res/layout/app_permission_item_money.xml
Normal file
@ -0,0 +1,76 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Defines the layout of a single permission item that costs money.
|
||||
-->
|
||||
|
||||
<view class="org.fdroid.fdroid.installer.AppSecurityPermissions$PermissionItemView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/perm_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/dividerVertical" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp">
|
||||
<TextView
|
||||
android:id="@+id/perm_name"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="16sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true" />
|
||||
<ImageView
|
||||
android:id="@+id/perm_money_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignBottom="@+id/perm_money_label"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="@color/perms_costs_money"
|
||||
android:tintMode="src_in"
|
||||
android:src="@drawable/ic_coins_s" />
|
||||
<TextView
|
||||
android:id="@+id/perm_money_label"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/perms_costs_money"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/perm_money_icon"
|
||||
android:layout_below="@id/perm_name"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/perm_costs_money" />
|
||||
</RelativeLayout>
|
||||
|
||||
</view>
|
55
F-Droid/res/layout/app_permission_item_old.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Defines the layout of a single permission item.
|
||||
Contains the group name and a list of permission labels under the group.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/perm_icon"
|
||||
android:layout_width="24dip"
|
||||
android:layout_height="24dip"
|
||||
android:layout_alignParentStart="true"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permission_group"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textStyle="bold"
|
||||
android:paddingStart="6dip"
|
||||
android:layout_toEndOf="@id/perm_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permission_list"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:layout_marginTop="-4dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:paddingStart="6dip"
|
||||
android:layout_below="@id/permission_group"
|
||||
android:layout_toEndOf="@id/perm_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
43
F-Droid/res/layout/app_perms_summary.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- Describes permission item consisting of a group name and the list of permisisons under the group -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_permissions"
|
||||
android:text="@string/no_permissions"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<!-- Populated with all permissions. -->
|
||||
<LinearLayout
|
||||
android:id="@+id/perms_list"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
68
F-Droid/res/layout/install_app_details.xml
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Defines the layout of the application snippet that appears on top of the
|
||||
installation screens
|
||||
-->
|
||||
<!-- The snippet about the application - title, icon, description. -->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/app_snippet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dip"
|
||||
android:paddingEnd="16dip"
|
||||
android:paddingTop="24dip"
|
||||
>
|
||||
<ImageView android:id="@+id/app_icon"
|
||||
android:layout_width="32dip"
|
||||
android:layout_height="32dip"
|
||||
android:layout_marginStart="8dip"
|
||||
android:background="@color/transparent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:gravity="start"
|
||||
android:scaleType="centerCrop"/>
|
||||
<TextView android:id="@+id/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:shadowColor="@color/shadow"
|
||||
android:shadowRadius="2"
|
||||
android:layout_toEndOf="@id/app_icon"
|
||||
android:singleLine="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:paddingEnd="16dip"
|
||||
android:paddingTop="3dip"
|
||||
android:paddingStart="16dip"
|
||||
android:ellipsize="end"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/top_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dip"
|
||||
android:layout_below="@id/app_name">
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
149
F-Droid/res/layout/install_confirm.xml
Normal file
@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
Defines the layout of the splash screen that displays the security
|
||||
settings required for an application and requests the confirmation of the
|
||||
user before it is installed.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/install_confirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/install_confirm"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="4dip" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="?android:attr/dividerHorizontal"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/filler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone">
|
||||
</FrameLayout>
|
||||
|
||||
<TabHost
|
||||
android:id="@android:id/tabhost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView android:id="@+id/tabscontainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/tab_unselected_holo"
|
||||
android:fillViewport="true"
|
||||
android:scrollbars="none">
|
||||
<FrameLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TabWidget
|
||||
android:id="@android:id/tabs"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@android:id/tabcontent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="0"/>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
</TabHost>
|
||||
|
||||
<!-- OK confirm and cancel buttons. -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:showDividers="beginning">
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:measureWithLargestChild="true">
|
||||
|
||||
<LinearLayout android:id="@+id/leftSpacer"
|
||||
android:layout_weight="0.25"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button android:id="@+id/cancel_button"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/cancel"
|
||||
android:maxLines="2"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
<Button android:id="@+id/ok_button"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/next"
|
||||
android:maxLines="2"
|
||||
android:filterTouchesWhenObscured="true"
|
||||
style="?android:attr/buttonBarButtonStyle" />
|
||||
|
||||
<LinearLayout android:id="@+id/rightSpacer"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="0.25"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
37
F-Droid/res/layout/install_start.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
layout="@layout/install_app_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/app_snippet"/>
|
||||
|
||||
<include
|
||||
layout="@layout/install_confirm"
|
||||
android:id="@+id/install_confirm_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/app_snippet"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
</RelativeLayout>
|
||||
|
||||
|
20
F-Droid/res/layout/label.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?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" />
|
53
F-Droid/res/layout/permissions_list.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?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.
|
||||
-->
|
||||
<org.fdroid.fdroid.installer.CaffeinatedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scrollview"
|
||||
android:fillViewport="true">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout android:id="@+id/privacylist"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
style="?android:attr/listSeparatorTextViewStyle"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/privacyPerms" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:id="@+id/devicelist"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
<TextView
|
||||
style="?android:attr/listSeparatorTextViewStyle"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/devicePerms" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</org.fdroid.fdroid.installer.CaffeinatedScrollView>
|
@ -15,4 +15,9 @@
|
||||
<color name="swap_wifi_may_work">#fbb040</color>
|
||||
<color name="swap_wifi_likely_to_work">#00a14b</color>
|
||||
|
||||
</resources>
|
||||
<color name="shadow">#cc222222</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
|
||||
<color name="perms_costs_money">#fff4511e</color>
|
||||
|
||||
</resources>
|
||||
|
@ -342,6 +342,33 @@
|
||||
<string name="wifi_warning_private">Promising</string>
|
||||
<string name="wifi_warning_personal">Best bet</string>
|
||||
|
||||
<string name="install_confirm">Do you want to install this application?
|
||||
It will get access to:</string>
|
||||
<string name="install_confirm_no_perms">Do you want to install this application?
|
||||
It does not require any special access.</string>
|
||||
<string name="install_confirm_update">Do you want to install an update
|
||||
to this existing application? Your existing data will not
|
||||
be lost. The updated application will get access to:</string>
|
||||
<string name="install_confirm_update_system">Do you want to install an update
|
||||
to this built-in application? Your existing data will not
|
||||
be lost. The updated application will get access to:</string>
|
||||
<string name="install_confirm_update_no_perms">Do you want to install an update
|
||||
to this existing application? Your existing data will not
|
||||
be lost. It does not require any special access.</string>
|
||||
<string name="install_confirm_update_system_no_perms">Do you want to install an update
|
||||
to this built-in application? Your existing data will not
|
||||
be lost. It does not require any special access.</string>
|
||||
<!--<string name="no_permissions_required">No permissions required</string>-->
|
||||
<string name="newPerms">New</string>
|
||||
<string name="allPerms">All</string>
|
||||
<string name="privacyPerms">Privacy</string>
|
||||
<string name="devicePerms">Device Access</string>
|
||||
<string name="perm_costs_money">this may cost you money</string>
|
||||
<string name="uninstall_update_confirm">Do you want to replace this app with the factory version?</string>
|
||||
<string name="uninstall_application_confirm">Do you want to uninstall this app?</string>
|
||||
<string name="uninstall_confirm">Do you want to uninstall this app?</string>
|
||||
|
||||
<string name="no_new_perms">This update requires no new permissions.</string>
|
||||
|
||||
<string name="perms_new_perm_prefix"><font size="12" fgcolor="#ff33b5e5">NEW: </font></string>
|
||||
<string name="perms_description_app">Provided by %1$s.</string>
|
||||
</resources>
|
||||
|
@ -0,0 +1,525 @@
|
||||
/*
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
** Copyright 2015 Daniel Martí <mvdan@mvdan.cc>
|
||||
**
|
||||
** 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 org.fdroid.fdroid.installer;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PermissionGroupInfo;
|
||||
import android.content.pm.PermissionInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class contains the SecurityPermissions view implementation.
|
||||
* Initially the package's advanced or dangerous security permissions
|
||||
* are displayed under categorized
|
||||
* groups. Clicking on the additional permissions presents
|
||||
* extended information consisting of all groups and permissions.
|
||||
* To use this view define a LinearLayout or any ViewGroup and add this
|
||||
* view by instantiating AppSecurityPermissions and invoking getPermissionsView.
|
||||
*/
|
||||
public class AppSecurityPermissions {
|
||||
|
||||
public static final int WHICH_PERSONAL = 1<<0;
|
||||
public static final int WHICH_DEVICE = 1<<1;
|
||||
public static final int WHICH_NEW = 1<<2;
|
||||
|
||||
private final static String TAG = "AppSecurityPermissions";
|
||||
private final Context mContext;
|
||||
private final LayoutInflater mInflater;
|
||||
private final PackageManager mPm;
|
||||
private final Map<String, MyPermissionGroupInfo> mPermGroups = new HashMap<>();
|
||||
private final List<MyPermissionGroupInfo> mPermGroupsList = new ArrayList<>();
|
||||
private final PermissionGroupInfoComparator mPermGroupComparator = new PermissionGroupInfoComparator();
|
||||
private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator();
|
||||
private final CharSequence mNewPermPrefix;
|
||||
private String mPackageName;
|
||||
|
||||
static class MyPermissionGroupInfo extends PermissionGroupInfo {
|
||||
CharSequence mLabel;
|
||||
|
||||
final List<MyPermissionInfo> mNewPermissions = new ArrayList<>();
|
||||
final List<MyPermissionInfo> mPersonalPermissions = new ArrayList<>();
|
||||
final List<MyPermissionInfo> mDevicePermissions = new ArrayList<>();
|
||||
final List<MyPermissionInfo> mAllPermissions = new ArrayList<>();
|
||||
|
||||
MyPermissionGroupInfo(PermissionInfo perm) {
|
||||
name = perm.packageName;
|
||||
packageName = perm.packageName;
|
||||
}
|
||||
|
||||
MyPermissionGroupInfo(PermissionGroupInfo info) {
|
||||
super(info);
|
||||
}
|
||||
|
||||
public Drawable loadGroupIcon(PackageManager pm) {
|
||||
if (icon != 0) {
|
||||
//return loadUnbadgedIcon(pm);
|
||||
return loadIcon(pm);
|
||||
} else {
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
appInfo = pm.getApplicationInfo(packageName, 0);
|
||||
//return appInfo.loadUnbadgedIcon(pm);
|
||||
return appInfo.loadIcon(pm);
|
||||
} catch (NameNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyPermissionInfo extends PermissionInfo {
|
||||
CharSequence mLabel;
|
||||
|
||||
/**
|
||||
* PackageInfo.requestedPermissionsFlags for the new package being installed.
|
||||
*/
|
||||
int mNewReqFlags;
|
||||
|
||||
/**
|
||||
* PackageInfo.requestedPermissionsFlags for the currently installed
|
||||
* package, if it is installed.
|
||||
*/
|
||||
int mExistingReqFlags;
|
||||
|
||||
/**
|
||||
* True if this should be considered a new permission.
|
||||
*/
|
||||
boolean mNew;
|
||||
|
||||
MyPermissionInfo(PermissionInfo info) {
|
||||
super(info);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
|
||||
MyPermissionGroupInfo mGroup;
|
||||
MyPermissionInfo mPerm;
|
||||
AlertDialog mDialog;
|
||||
|
||||
public PermissionItemView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setClickable(true);
|
||||
}
|
||||
|
||||
public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
|
||||
boolean first, CharSequence newPermPrefix, String packageName) {
|
||||
mGroup = grp;
|
||||
mPerm = perm;
|
||||
|
||||
ImageView permGrpIcon = (ImageView) findViewById(R.id.perm_icon);
|
||||
TextView permNameView = (TextView) findViewById(R.id.perm_name);
|
||||
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
Drawable icon = null;
|
||||
if (first) {
|
||||
icon = grp.loadGroupIcon(pm);
|
||||
}
|
||||
CharSequence label = perm.mLabel;
|
||||
if (perm.mNew && newPermPrefix != null) {
|
||||
// If this is a new permission, format it appropriately.
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
Parcel parcel = Parcel.obtain();
|
||||
TextUtils.writeToParcel(newPermPrefix, parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
|
||||
parcel.recycle();
|
||||
builder.append(newStr);
|
||||
builder.append(label);
|
||||
label = builder;
|
||||
}
|
||||
|
||||
permGrpIcon.setImageDrawable(icon);
|
||||
permNameView.setText(label);
|
||||
setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mGroup != null && mPerm != null) {
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setTitle(mGroup.mLabel);
|
||||
if (mPerm.descriptionRes != 0) {
|
||||
builder.setMessage(mPerm.loadDescription(pm));
|
||||
} else {
|
||||
CharSequence appName;
|
||||
try {
|
||||
ApplicationInfo app = pm.getApplicationInfo(mPerm.packageName, 0);
|
||||
appName = app.loadLabel(pm);
|
||||
} catch (NameNotFoundException e) {
|
||||
appName = mPerm.packageName;
|
||||
}
|
||||
builder.setMessage(getContext().getString(
|
||||
R.string.perms_description_app, appName) + "\n\n" + mPerm.name);
|
||||
}
|
||||
builder.setCancelable(true);
|
||||
builder.setIcon(mGroup.loadGroupIcon(pm));
|
||||
//addRevokeUIIfNecessary(builder);
|
||||
mDialog = builder.show();
|
||||
mDialog.setCanceledOnTouchOutside(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AppSecurityPermissions(Context context) {
|
||||
mContext = context;
|
||||
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mPm = mContext.getPackageManager();
|
||||
// Pick up from framework resources instead.
|
||||
mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
|
||||
}
|
||||
|
||||
public AppSecurityPermissions(Context context, PackageInfo info) {
|
||||
this(context);
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
mPackageName = info.packageName;
|
||||
|
||||
final Set<MyPermissionInfo> permSet = new HashSet<>();
|
||||
PackageInfo installedPkgInfo = null;
|
||||
if (info.requestedPermissions != null) {
|
||||
try {
|
||||
installedPkgInfo = mPm.getPackageInfo(info.packageName,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
} catch (NameNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
extractPerms(info, permSet, installedPkgInfo);
|
||||
}
|
||||
setPermissions(new ArrayList<>(permSet));
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private int[] getRequestedPermissionFlags(PackageInfo info) {
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
return new int[info.requestedPermissions.length];
|
||||
}
|
||||
return info.requestedPermissionsFlags;
|
||||
}
|
||||
|
||||
private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
|
||||
PackageInfo installedPkgInfo) {
|
||||
|
||||
final String[] strList = info.requestedPermissions;
|
||||
if (strList == null || strList.length == 0) {
|
||||
return;
|
||||
}
|
||||
final int[] flagsList = getRequestedPermissionFlags(info);
|
||||
|
||||
for (int i=0; i<strList.length; i++) {
|
||||
String permName = strList[i];
|
||||
// If we are only looking at an existing app, then we only
|
||||
// care about permissions that have actually been granted to it.
|
||||
if (installedPkgInfo != null && info == installedPkgInfo) {
|
||||
if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try {
|
||||
PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
|
||||
if (tmpPermInfo == null) {
|
||||
continue;
|
||||
}
|
||||
int existingIndex = -1;
|
||||
if (installedPkgInfo != null && installedPkgInfo.requestedPermissions != null) {
|
||||
for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
|
||||
if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
|
||||
existingIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int existingFlags = 0;
|
||||
if (existingIndex >= 0) {
|
||||
final int[] instFlagsList = getRequestedPermissionFlags(installedPkgInfo);
|
||||
existingFlags = instFlagsList[existingIndex];
|
||||
}
|
||||
if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
|
||||
// This is not a permission that is interesting for the user
|
||||
// to see, so skip it.
|
||||
continue;
|
||||
}
|
||||
final String origGroupName = tmpPermInfo.group;
|
||||
String groupName = origGroupName;
|
||||
if (groupName == null) {
|
||||
groupName = tmpPermInfo.packageName;
|
||||
tmpPermInfo.group = groupName;
|
||||
}
|
||||
MyPermissionGroupInfo group = mPermGroups.get(groupName);
|
||||
if (group == null) {
|
||||
PermissionGroupInfo grp = null;
|
||||
if (origGroupName != null) {
|
||||
grp = mPm.getPermissionGroupInfo(origGroupName, 0);
|
||||
}
|
||||
if (grp != null) {
|
||||
group = new MyPermissionGroupInfo(grp);
|
||||
} else {
|
||||
// We could be here either because the permission
|
||||
// didn't originally specify a group or the group it
|
||||
// gave couldn't be found. In either case, we consider
|
||||
// its group to be the permission's package name.
|
||||
tmpPermInfo.group = tmpPermInfo.packageName;
|
||||
group = mPermGroups.get(tmpPermInfo.group);
|
||||
if (group == null) {
|
||||
group = new MyPermissionGroupInfo(tmpPermInfo);
|
||||
}
|
||||
group = new MyPermissionGroupInfo(tmpPermInfo);
|
||||
}
|
||||
mPermGroups.put(tmpPermInfo.group, group);
|
||||
}
|
||||
final boolean newPerm = installedPkgInfo != null
|
||||
&& (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
|
||||
MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
|
||||
myPerm.mNewReqFlags = flagsList[i];
|
||||
myPerm.mExistingReqFlags = existingFlags;
|
||||
// This is a new permission if the app is already installed and
|
||||
// doesn't currently hold this permission.
|
||||
myPerm.mNew = newPerm;
|
||||
permSet.add(myPerm);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.i(TAG, "Ignoring unknown permission:"+permName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) {
|
||||
switch (which) {
|
||||
case WHICH_NEW:
|
||||
return grp.mNewPermissions;
|
||||
case WHICH_PERSONAL:
|
||||
return grp.mPersonalPermissions;
|
||||
case WHICH_DEVICE:
|
||||
return grp.mDevicePermissions;
|
||||
default:
|
||||
return grp.mAllPermissions;
|
||||
}
|
||||
}
|
||||
|
||||
public int getPermissionCount(int which) {
|
||||
int N = 0;
|
||||
for (int i=0; i<mPermGroupsList.size(); i++) {
|
||||
N += getPermissionList(mPermGroupsList.get(i), which).size();
|
||||
}
|
||||
return N;
|
||||
}
|
||||
|
||||
public View getPermissionsView(int which) {
|
||||
LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
|
||||
LinearLayout displayList = (LinearLayout) permsView.findViewById(R.id.perms_list);
|
||||
View noPermsView = permsView.findViewById(R.id.no_permissions);
|
||||
|
||||
displayPermissions(mPermGroupsList, displayList, which);
|
||||
if (displayList.getChildCount() <= 0) {
|
||||
noPermsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
return permsView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that displays permissions from a map containing group name and
|
||||
* list of permission descriptions.
|
||||
*/
|
||||
private void displayPermissions(List<MyPermissionGroupInfo> groups,
|
||||
LinearLayout permListView, int which) {
|
||||
permListView.removeAllViews();
|
||||
|
||||
int spacing = (int) (8*mContext.getResources().getDisplayMetrics().density);
|
||||
|
||||
for (int i=0; i<groups.size(); i++) {
|
||||
MyPermissionGroupInfo grp = groups.get(i);
|
||||
final List<MyPermissionInfo> perms = getPermissionList(grp, which);
|
||||
for (int j=0; j<perms.size(); j++) {
|
||||
MyPermissionInfo perm = perms.get(j);
|
||||
View view = getPermissionItemView(grp, perm, j == 0,
|
||||
which != WHICH_NEW ? mNewPermPrefix : null);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
if (j == 0) {
|
||||
lp.topMargin = spacing;
|
||||
}
|
||||
if (j == grp.mAllPermissions.size()-1) {
|
||||
lp.bottomMargin = spacing;
|
||||
}
|
||||
if (permListView.getChildCount() == 0) {
|
||||
lp.topMargin *= 2;
|
||||
}
|
||||
permListView.addView(view, lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
|
||||
MyPermissionInfo perm, boolean first, CharSequence newPermPrefix) {
|
||||
PermissionItemView permView = (PermissionItemView) mInflater.inflate(
|
||||
(perm.flags & PermissionInfo.FLAG_COSTS_MONEY) != 0
|
||||
? R.layout.app_permission_item_money : R.layout.app_permission_item,
|
||||
null);
|
||||
permView.setPermission(grp, perm, first, newPermPrefix, mPackageName);
|
||||
return permView;
|
||||
}
|
||||
|
||||
private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
|
||||
int existingReqFlags) {
|
||||
final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
|
||||
final boolean isNormal = (base == PermissionInfo.PROTECTION_NORMAL);
|
||||
final boolean isDangerous = (base == PermissionInfo.PROTECTION_DANGEROUS);
|
||||
final boolean isRequired =
|
||||
((newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
|
||||
final boolean isDevelopment =
|
||||
((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
|
||||
final boolean wasGranted =
|
||||
((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
|
||||
final boolean isGranted =
|
||||
((newReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
|
||||
|
||||
// Dangerous and normal permissions are always shown to the user if the permission
|
||||
// is required, or it was previously granted
|
||||
if ((isNormal || isDangerous) && (isRequired || wasGranted || isGranted)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Development permissions are only shown to the user if they are already
|
||||
// granted to the app -- if we are installing an app and they are not
|
||||
// already granted, they will not be granted as part of the install.
|
||||
if (isDevelopment && wasGranted) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
|
||||
private final Collator sCollator = Collator.getInstance();
|
||||
PermissionGroupInfoComparator() {
|
||||
}
|
||||
public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) {
|
||||
if (((a.flags^b.flags)&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
|
||||
return ((a.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) ? -1 : 1;
|
||||
}
|
||||
if (a.priority != b.priority) {
|
||||
return a.priority > b.priority ? -1 : 1;
|
||||
}
|
||||
return sCollator.compare(a.mLabel, b.mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
|
||||
private final Collator sCollator = Collator.getInstance();
|
||||
PermissionInfoComparator() {
|
||||
}
|
||||
public final int compare(MyPermissionInfo a, MyPermissionInfo b) {
|
||||
return sCollator.compare(a.mLabel, b.mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPermToList(List<MyPermissionInfo> permList,
|
||||
MyPermissionInfo pInfo) {
|
||||
if (pInfo.mLabel == null) {
|
||||
pInfo.mLabel = pInfo.loadLabel(mPm);
|
||||
}
|
||||
int idx = Collections.binarySearch(permList, pInfo, mPermComparator);
|
||||
if (idx < 0) {
|
||||
idx = -idx-1;
|
||||
permList.add(idx, pInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void setPermissions(List<MyPermissionInfo> permList) {
|
||||
if (permList != null) {
|
||||
// First pass to group permissions
|
||||
for (MyPermissionInfo pInfo : permList) {
|
||||
if (!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
|
||||
continue;
|
||||
}
|
||||
MyPermissionGroupInfo group = mPermGroups.get(pInfo.group);
|
||||
if (group != null) {
|
||||
pInfo.mLabel = pInfo.loadLabel(mPm);
|
||||
addPermToList(group.mAllPermissions, pInfo);
|
||||
if (pInfo.mNew) {
|
||||
addPermToList(group.mNewPermissions, pInfo);
|
||||
}
|
||||
if ((group.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
|
||||
addPermToList(group.mPersonalPermissions, pInfo);
|
||||
} else {
|
||||
addPermToList(group.mDevicePermissions, pInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MyPermissionGroupInfo pgrp : mPermGroups.values()) {
|
||||
if (pgrp.labelRes != 0 || pgrp.nonLocalizedLabel != null) {
|
||||
pgrp.mLabel = pgrp.loadLabel(mPm);
|
||||
} else {
|
||||
try {
|
||||
ApplicationInfo app = mPm.getApplicationInfo(pgrp.packageName, 0);
|
||||
pgrp.mLabel = app.loadLabel(mPm);
|
||||
} catch (NameNotFoundException e) {
|
||||
pgrp.mLabel = pgrp.loadLabel(mPm);
|
||||
}
|
||||
}
|
||||
mPermGroupsList.add(pgrp);
|
||||
}
|
||||
Collections.sort(mPermGroupsList, mPermGroupComparator);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
**
|
||||
** Copyright 2012, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
/**
|
||||
* It's a ScrollView that knows how to stay awake.
|
||||
*/
|
||||
public class CaffeinatedScrollView extends ScrollView {
|
||||
private Runnable mFullScrollAction;
|
||||
private int mBottomSlop;
|
||||
|
||||
public CaffeinatedScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CaffeinatedScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this visible so we can call it
|
||||
*/
|
||||
@Override
|
||||
public boolean awakenScrollBars() {
|
||||
return super.awakenScrollBars();
|
||||
}
|
||||
|
||||
public void setFullScrollAction(Runnable action) {
|
||||
mFullScrollAction = action;
|
||||
mBottomSlop = (int) (4 * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
checkFullScrollAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
checkFullScrollAction();
|
||||
}
|
||||
|
||||
private void checkFullScrollAction() {
|
||||
if (mFullScrollAction != null) {
|
||||
int daBottom = getChildAt(0).getBottom();
|
||||
int screenBottom = getScrollY() + getHeight() - getPaddingBottom();
|
||||
if ((daBottom - screenBottom) < mBottomSlop) {
|
||||
mFullScrollAction.run();
|
||||
mFullScrollAction = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
** Copyright 2015 Daniel Martí <mvdan@mvdan.cc>
|
||||
**
|
||||
** 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 org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class InstallConfirmActivity extends Activity implements OnCancelListener, OnClickListener {
|
||||
|
||||
private Intent intent;
|
||||
|
||||
PackageManager mPm;
|
||||
PackageInfo mPkgInfo;
|
||||
|
||||
private ApplicationInfo mAppInfo = null;
|
||||
|
||||
// View for install progress
|
||||
View mInstallConfirm;
|
||||
// Buttons to indicate user acceptance
|
||||
private Button mOk;
|
||||
private Button mCancel;
|
||||
CaffeinatedScrollView mScrollView = null;
|
||||
private boolean mOkCanInstall = false;
|
||||
|
||||
private static final String TAB_ID_ALL = "all";
|
||||
private static final String TAB_ID_NEW = "new";
|
||||
|
||||
private void startInstallConfirm() {
|
||||
|
||||
final Drawable appIcon = mPkgInfo.applicationInfo.loadIcon(mPm);
|
||||
final String appLabel = (String) mPkgInfo.applicationInfo.loadLabel(mPm);
|
||||
|
||||
View appSnippet = findViewById(R.id.app_snippet);
|
||||
((ImageView) appSnippet.findViewById(R.id.app_icon)).setImageDrawable(appIcon);
|
||||
((TextView) appSnippet.findViewById(R.id.app_name)).setText(appLabel);
|
||||
|
||||
TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);
|
||||
tabHost.setup();
|
||||
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
|
||||
adapter.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
|
||||
@Override
|
||||
public void onTabChanged(String tabId) {
|
||||
}
|
||||
});
|
||||
|
||||
boolean permVisible = false;
|
||||
mScrollView = null;
|
||||
mOkCanInstall = false;
|
||||
int msg = 0;
|
||||
if (mPkgInfo != null) {
|
||||
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
|
||||
final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL);
|
||||
final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE);
|
||||
if (mAppInfo != null) {
|
||||
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
? R.string.install_confirm_update_system
|
||||
: R.string.install_confirm_update;
|
||||
mScrollView = new CaffeinatedScrollView(this);
|
||||
mScrollView.setFillViewport(true);
|
||||
final boolean newPermissionsFound =
|
||||
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
|
||||
if (newPermissionsFound) {
|
||||
permVisible = true;
|
||||
mScrollView.addView(perms.getPermissionsView(
|
||||
AppSecurityPermissions.WHICH_NEW));
|
||||
} else {
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
TextView label = (TextView) inflater.inflate(R.layout.label, null);
|
||||
label.setText(R.string.no_new_perms);
|
||||
mScrollView.addView(label);
|
||||
}
|
||||
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
|
||||
getText(R.string.newPerms)), mScrollView);
|
||||
} else {
|
||||
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
|
||||
findViewById(R.id.divider).setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (NP > 0 || ND > 0) {
|
||||
permVisible = true;
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.permissions_list, null);
|
||||
if (mScrollView == null) {
|
||||
mScrollView = (CaffeinatedScrollView) root.findViewById(R.id.scrollview);
|
||||
}
|
||||
final ViewGroup privacyList = (ViewGroup) root.findViewById(R.id.privacylist);
|
||||
if (NP > 0) {
|
||||
privacyList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL));
|
||||
} else {
|
||||
privacyList.setVisibility(View.GONE);
|
||||
}
|
||||
final ViewGroup deviceList = (ViewGroup) root.findViewById(R.id.devicelist);
|
||||
if (ND > 0) {
|
||||
deviceList.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE));
|
||||
} else {
|
||||
root.findViewById(R.id.devicelist).setVisibility(View.GONE);
|
||||
}
|
||||
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
|
||||
getText(R.string.allPerms)), root);
|
||||
}
|
||||
}
|
||||
if (!permVisible) {
|
||||
if (mAppInfo != null) {
|
||||
// This is an update to an application, but there are no
|
||||
// permissions at all.
|
||||
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
? R.string.install_confirm_update_system_no_perms
|
||||
: R.string.install_confirm_update_no_perms;
|
||||
} else {
|
||||
// This is a new application with no permissions.
|
||||
msg = R.string.install_confirm_no_perms;
|
||||
}
|
||||
tabHost.setVisibility(View.GONE);
|
||||
findViewById(R.id.filler).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.divider).setVisibility(View.GONE);
|
||||
mScrollView = null;
|
||||
}
|
||||
if (msg != 0) {
|
||||
((TextView) findViewById(R.id.install_confirm)).setText(msg);
|
||||
}
|
||||
mInstallConfirm.setVisibility(View.VISIBLE);
|
||||
mOk = (Button) findViewById(R.id.ok_button);
|
||||
mCancel = (Button) findViewById(R.id.cancel_button);
|
||||
mOk.setOnClickListener(this);
|
||||
mCancel.setOnClickListener(this);
|
||||
if (mScrollView == null) {
|
||||
// There is nothing to scroll view, so the ok button is immediately
|
||||
// set to install.
|
||||
mOk.setText(R.string.menu_install);
|
||||
mOkCanInstall = true;
|
||||
} else {
|
||||
mScrollView.setFullScrollAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mOk.setText(R.string.menu_install);
|
||||
mOkCanInstall = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void initiateInstall() {
|
||||
String pkgName = mPkgInfo.packageName;
|
||||
// Check if there is already a package on the device with this name
|
||||
// but it has been renamed to something else.
|
||||
final String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
|
||||
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
|
||||
pkgName = oldName[0];
|
||||
mPkgInfo.packageName = pkgName;
|
||||
mPkgInfo.applicationInfo.packageName = pkgName;
|
||||
}
|
||||
// Check if package is already installed. display confirmation dialog if replacing pkg
|
||||
try {
|
||||
// This is a little convoluted because we want to get all uninstalled
|
||||
// apps, but this may include apps with just data, and if it is just
|
||||
// data we still want to count it as "installed".
|
||||
mAppInfo = mPm.getApplicationInfo(pkgName,
|
||||
PackageManager.GET_UNINSTALLED_PACKAGES);
|
||||
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
mAppInfo = null;
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
mAppInfo = null;
|
||||
}
|
||||
|
||||
startInstallConfirm();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
mPm = getPackageManager();
|
||||
|
||||
intent = getIntent();
|
||||
Uri mPackageURI = intent.getData();
|
||||
final String pkgPath = mPackageURI.getPath();
|
||||
|
||||
mPkgInfo = mPm.getPackageArchiveInfo(pkgPath, PackageManager.GET_PERMISSIONS);
|
||||
mPkgInfo.applicationInfo.sourceDir = pkgPath;
|
||||
mPkgInfo.applicationInfo.publicSourceDir = pkgPath;
|
||||
|
||||
setContentView(R.layout.install_start);
|
||||
mInstallConfirm = findViewById(R.id.install_confirm_panel);
|
||||
mInstallConfirm.setVisibility(View.INVISIBLE);
|
||||
|
||||
initiateInstall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
// Generic handling when pressing back key
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
if (v == mOk) {
|
||||
if (mOkCanInstall || mScrollView == null) {
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
} else {
|
||||
mScrollView.pageScroll(View.FOCUS_DOWN);
|
||||
}
|
||||
} else if (v == mCancel) {
|
||||
setResult(RESULT_CANCELED, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -140,18 +140,6 @@ abstract public class Installer {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Installer getUnattendedInstaller(Context context, PackageManager pm,
|
||||
InstallerCallback callback) throws AndroidNotCompatibleException {
|
||||
|
||||
if (hasSystemPermissions(context, pm)) {
|
||||
// we have system permissions!
|
||||
return new SystemInstaller(context, pm, callback);
|
||||
} else {
|
||||
// nope!
|
||||
throw new AndroidNotCompatibleException();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasSystemPermissions(Context context, PackageManager pm) {
|
||||
boolean hasInstallPermission =
|
||||
(pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName())
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Daniel Martí <mvdan@mvdan.cc>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -19,8 +20,8 @@
|
||||
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
@ -31,12 +32,12 @@ import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
/**
|
||||
* Installer based on using internal hidden APIs of the Android OS, which are
|
||||
* protected by the permissions
|
||||
@ -65,14 +66,18 @@ public class SystemInstaller extends Installer {
|
||||
|
||||
private static final String TAG = "SystemInstaller";
|
||||
|
||||
private Activity mActivity;
|
||||
private final PackageInstallObserver mInstallObserver;
|
||||
private final PackageDeleteObserver mDeleteObserver;
|
||||
private Method mInstallMethod;
|
||||
private Method mDeleteMethod;
|
||||
|
||||
public SystemInstaller(Context context, PackageManager pm,
|
||||
public static final int REQUEST_CONFIRM_PERMS = 0;
|
||||
|
||||
public SystemInstaller(Activity activity, PackageManager pm,
|
||||
InstallerCallback callback) throws AndroidNotCompatibleException {
|
||||
super(context, pm, callback);
|
||||
super(activity, pm, callback);
|
||||
this.mActivity = activity;
|
||||
|
||||
// create internal callbacks
|
||||
mInstallObserver = new PackageInstallObserver();
|
||||
@ -133,7 +138,12 @@ public class SystemInstaller extends Installer {
|
||||
|
||||
@Override
|
||||
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
|
||||
Uri packageURI = Uri.fromFile(apkFile);
|
||||
Intent intent = new Intent(mContext, InstallConfirmActivity.class);
|
||||
intent.setData(Uri.fromFile(apkFile));
|
||||
mActivity.startActivityForResult(intent, REQUEST_CONFIRM_PERMS);
|
||||
}
|
||||
|
||||
private void doInstallPackageInternal(Uri packageURI) throws AndroidNotCompatibleException {
|
||||
try {
|
||||
mInstallMethod.invoke(mPm, packageURI, mInstallObserver,
|
||||
INSTALL_REPLACE_EXISTING, null);
|
||||
@ -165,7 +175,7 @@ public class SystemInstaller extends Installer {
|
||||
if (isUpdate) {
|
||||
messageId = R.string.uninstall_update_confirm;
|
||||
} else {
|
||||
messageId = R.string.uninstall_application_confirm;
|
||||
messageId = R.string.uninstall_confirm;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
@ -207,8 +217,24 @@ public class SystemInstaller extends Installer {
|
||||
|
||||
@Override
|
||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// no need to handle onActivityResult
|
||||
return false;
|
||||
switch (requestCode) {
|
||||
case REQUEST_CONFIRM_PERMS:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
final Uri packageURI = data.getData();
|
||||
try {
|
||||
doInstallPackageInternal(packageURI);
|
||||
} catch (AndroidNotCompatibleException e) {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_OTHER);
|
||||
}
|
||||
} else {
|
||||
mCallback.onError(InstallerCallback.OPERATION_INSTALL,
|
||||
InstallerCallback.ERROR_CODE_CANCELED);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
163
F-Droid/src/org/fdroid/fdroid/installer/TabsAdapter.java
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
**
|
||||
** Copyright 2013, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TabHost;
|
||||
import android.widget.TabWidget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This is a helper class that implements the management of tabs and all
|
||||
* details of connecting a ViewPager with associated TabHost. It relies on a
|
||||
* trick. Normally a tab host has a simple API for supplying a View or
|
||||
* Intent that each tab will show. This is not sufficient for switching
|
||||
* between pages. So instead we make the content part of the tab host
|
||||
* 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
|
||||
* view to show as the tab content. It listens to changes in tabs, and takes
|
||||
* care of switch to the correct paged in the ViewPager whenever the selected
|
||||
* tab changes.
|
||||
*/
|
||||
public class TabsAdapter extends PagerAdapter
|
||||
implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
|
||||
private final Context mContext;
|
||||
private final TabHost mTabHost;
|
||||
private final ViewPager mViewPager;
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<>();
|
||||
private final Rect mTempRect = new Rect();
|
||||
private TabHost.OnTabChangeListener mOnTabChangeListener;
|
||||
|
||||
static final class TabInfo {
|
||||
private final String tag;
|
||||
private final View view;
|
||||
|
||||
TabInfo(String _tag, View _view) {
|
||||
tag = _tag;
|
||||
view = _view;
|
||||
}
|
||||
}
|
||||
|
||||
static class DummyTabFactory implements TabHost.TabContentFactory {
|
||||
private final Context mContext;
|
||||
|
||||
public DummyTabFactory(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createTabContent(String tag) {
|
||||
View v = new View(mContext);
|
||||
v.setMinimumWidth(0);
|
||||
v.setMinimumHeight(0);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public TabsAdapter(Activity activity, TabHost tabHost, ViewPager pager) {
|
||||
mContext = activity;
|
||||
mTabHost = tabHost;
|
||||
mViewPager = pager;
|
||||
mTabHost.setOnTabChangedListener(this);
|
||||
mViewPager.setAdapter(this);
|
||||
mViewPager.setOnPageChangeListener(this);
|
||||
}
|
||||
|
||||
public void addTab(TabHost.TabSpec tabSpec, View view) {
|
||||
tabSpec.setContent(new DummyTabFactory(mContext));
|
||||
String tag = tabSpec.getTag();
|
||||
|
||||
TabInfo info = new TabInfo(tag, view);
|
||||
mTabs.add(info);
|
||||
mTabHost.addTab(tabSpec);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
View view = mTabs.get(position).view;
|
||||
container.addView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||
container.removeView((View)object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(View view, Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
public void setOnTabChangedListener(TabHost.OnTabChangeListener listener) {
|
||||
mOnTabChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(String tabId) {
|
||||
int position = mTabHost.getCurrentTab();
|
||||
mViewPager.setCurrentItem(position);
|
||||
if (mOnTabChangeListener != null) {
|
||||
mOnTabChangeListener.onTabChanged(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
// Unfortunately when TabHost changes the current tab, it kindly
|
||||
// also takes care of putting focus on it when not in touch mode.
|
||||
// The jerk.
|
||||
// This hack tries to prevent this from pulling focus out of our
|
||||
// ViewPager.
|
||||
TabWidget widget = mTabHost.getTabWidget();
|
||||
int oldFocusability = widget.getDescendantFocusability();
|
||||
widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||
mTabHost.setCurrentTab(position);
|
||||
widget.setDescendantFocusability(oldFocusability);
|
||||
|
||||
// Scroll the current tab into visibility if needed.
|
||||
View tab = widget.getChildTabViewAt(position);
|
||||
mTempRect.set(tab.getLeft(), tab.getTop(), tab.getRight(), tab.getBottom());
|
||||
widget.requestRectangleOnScreen(mTempRect, false);
|
||||
|
||||
// Make sure the scrollbars are visible for a moment after selection
|
||||
final View contentView = mTabs.get(position).view;
|
||||
if (contentView instanceof CaffeinatedScrollView) {
|
||||
((CaffeinatedScrollView) contentView).awakenScrollBars();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ public class LocalRepoService extends Service {
|
||||
public static final String STOPPED = "org.fdroid.fdroid.category.LOCAL_REPO_STOPPED";
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private Notification notification;
|
||||
// Unique Identification Number for the Notification.
|
||||
// We use it on Notification start, and to cancel it.
|
||||
private final int NOTIFICATION = R.string.local_repo_running;
|
||||
@ -139,7 +138,7 @@ public class LocalRepoService extends Service {
|
||||
Intent intent = new Intent(this, SwapActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
notification = new NotificationCompat.Builder(this)
|
||||
Notification notification = new NotificationCompat.Builder(this)
|
||||
.setContentTitle(getText(R.string.local_repo_running))
|
||||
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
||||
.setSmallIcon(R.drawable.ic_swap)
|
||||
|
@ -6,8 +6,8 @@ import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|