Add install confirm+perms screen to SystemInstaller
@ -292,6 +292,14 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".FDroid" />
|
android:value=".FDroid" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".installer.InstallConfirmActivity"
|
||||||
|
android:label=""
|
||||||
|
android:parentActivityName=".FDroid">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".FDroid" />
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".views.ManageReposActivity"
|
android:name=".views.ManageReposActivity"
|
||||||
android:label="@string/app_name"
|
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_may_work">#fbb040</color>
|
||||||
<color name="swap_wifi_likely_to_work">#00a14b</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_private">Promising</string>
|
||||||
<string name="wifi_warning_personal">Best bet</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_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>
|
</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;
|
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) {
|
public static boolean hasSystemPermissions(Context context, PackageManager pm) {
|
||||||
boolean hasInstallPermission =
|
boolean hasInstallPermission =
|
||||||
(pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName())
|
(pm.checkPermission(permission.INSTALL_PACKAGES, context.getPackageName())
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -19,8 +20,8 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.installer;
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@ -31,12 +32,12 @@ import android.net.Uri;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installer based on using internal hidden APIs of the Android OS, which are
|
* Installer based on using internal hidden APIs of the Android OS, which are
|
||||||
* protected by the permissions
|
* protected by the permissions
|
||||||
@ -65,14 +66,18 @@ public class SystemInstaller extends Installer {
|
|||||||
|
|
||||||
private static final String TAG = "SystemInstaller";
|
private static final String TAG = "SystemInstaller";
|
||||||
|
|
||||||
|
private Activity mActivity;
|
||||||
private final PackageInstallObserver mInstallObserver;
|
private final PackageInstallObserver mInstallObserver;
|
||||||
private final PackageDeleteObserver mDeleteObserver;
|
private final PackageDeleteObserver mDeleteObserver;
|
||||||
private Method mInstallMethod;
|
private Method mInstallMethod;
|
||||||
private Method mDeleteMethod;
|
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 {
|
InstallerCallback callback) throws AndroidNotCompatibleException {
|
||||||
super(context, pm, callback);
|
super(activity, pm, callback);
|
||||||
|
this.mActivity = activity;
|
||||||
|
|
||||||
// create internal callbacks
|
// create internal callbacks
|
||||||
mInstallObserver = new PackageInstallObserver();
|
mInstallObserver = new PackageInstallObserver();
|
||||||
@ -133,7 +138,12 @@ public class SystemInstaller extends Installer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void installPackageInternal(File apkFile) throws AndroidNotCompatibleException {
|
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 {
|
try {
|
||||||
mInstallMethod.invoke(mPm, packageURI, mInstallObserver,
|
mInstallMethod.invoke(mPm, packageURI, mInstallObserver,
|
||||||
INSTALL_REPLACE_EXISTING, null);
|
INSTALL_REPLACE_EXISTING, null);
|
||||||
@ -165,7 +175,7 @@ public class SystemInstaller extends Installer {
|
|||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
messageId = R.string.uninstall_update_confirm;
|
messageId = R.string.uninstall_update_confirm;
|
||||||
} else {
|
} else {
|
||||||
messageId = R.string.uninstall_application_confirm;
|
messageId = R.string.uninstall_confirm;
|
||||||
}
|
}
|
||||||
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||||
@ -207,8 +217,24 @@ public class SystemInstaller extends Installer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
// no need to handle onActivityResult
|
switch (requestCode) {
|
||||||
return false;
|
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
|
@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";
|
public static final String STOPPED = "org.fdroid.fdroid.category.LOCAL_REPO_STOPPED";
|
||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
private Notification notification;
|
|
||||||
// Unique Identification Number for the Notification.
|
// Unique Identification Number for the Notification.
|
||||||
// We use it on Notification start, and to cancel it.
|
// We use it on Notification start, and to cancel it.
|
||||||
private final int NOTIFICATION = R.string.local_repo_running;
|
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 intent = new Intent(this, SwapActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
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))
|
.setContentTitle(getText(R.string.local_repo_running))
|
||||||
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
||||||
.setSmallIcon(R.drawable.ic_swap)
|
.setSmallIcon(R.drawable.ic_swap)
|
||||||
|
@ -6,8 +6,8 @@ import android.content.Context;
|
|||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
|
||||||
import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
import org.fdroid.fdroid.RepoUpdater.UpdateException;
|
||||||
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|