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; | ||||
|  | ||||
 Daniel Martí
						Daniel Martí