WIP: Serialize bonjour peers correctly. Refactor mini-service APIs.
The reference to mini-services above are not full blown Android services. Rather, they are utility classes which can be started, stopped, and send broadcasts about their status. Made the list of apps to install better, with buttons for install or upgrade, and statuses for incompatible and installed.
This commit is contained in:
parent
22b072962e
commit
a3af6b8b9f
56
F-Droid/res/layout/swap_app_list_item.xml
Normal file
56
F-Droid/res/layout/swap_app_list_item.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingTop="2dip">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_alignParentStart="true"
|
||||
tools:src="@drawable/ic_launcher" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/button_or_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tint="@color/fdroid_green"
|
||||
android:textColor="@android:color/white"
|
||||
tools:text="Install"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/fdroid_green"
|
||||
tools:text="Installed" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_toEndOf="@android:id/icon"
|
||||
android:layout_toStartOf="@+id/button_or_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_marginBottom="6dip"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
tools:text="F-Droid" />
|
||||
|
||||
</RelativeLayout>
|
@ -7,196 +7,209 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/header"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="130dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<RelativeLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/swap_start_header"/>
|
||||
android:layout_height="130dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_info_white"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
/>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/swap_start_header"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:padding="20dp"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/swap_intro"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bluetooth_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:weightSum="1">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/swap_grey_icon"
|
||||
android:src="@drawable/ic_bluetooth_white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bluetooth_visible"
|
||||
android:paddingStart="15dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/swap_visible_bluetooth"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id_bluetooth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="SP-120"
|
||||
android:textColor="@color/swap_light_text"
|
||||
android:layout_weight="1.00"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp" />
|
||||
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:checked="true"
|
||||
android:id="@+id/switch_bluetooth" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:weightSum="1">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/swap_grey_icon"
|
||||
android:src="@drawable/ic_network_wifi_white" />
|
||||
|
||||
<LinearLayout
|
||||
android:paddingStart="15dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_info_white"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/swap_not_visible_wifi"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:padding="20dp"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/swap_intro"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_network"
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bluetooth_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/swap_grey_icon"
|
||||
android:src="@drawable/ic_bluetooth_white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="15dp"
|
||||
android:layout_weight="1.00">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bluetooth_visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/swap_visible_bluetooth"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id_bluetooth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="SP-120"
|
||||
android:textColor="@color/swap_light_text" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="wifi network name"
|
||||
android:textColor="@color/swap_bright_blue"
|
||||
android:textSize="16sp" />
|
||||
tools:checked="true"
|
||||
android:id="@+id/switch_bluetooth" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id_wifi"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_wifi_device_name"
|
||||
android:textColor="@color/swap_light_text"
|
||||
android:layout_weight="1.00"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp" />
|
||||
android:padding="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/swap_grey_icon"
|
||||
android:src="@drawable/ic_network_wifi_white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="15dp"
|
||||
android:layout_weight="1.00">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/swap_not_visible_wifi"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id_wifi"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_wifi_device_name"
|
||||
android:textColor="@color/swap_light_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="wifi network name"
|
||||
android:textColor="@color/swap_bright_blue"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:checked="false"
|
||||
android:id="@+id/switch_wifi" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:checked="false"
|
||||
android:id="@+id/switch_wifi" />
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/text_people_nearby"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_people_nearby"
|
||||
android:textColor="@color/swap_light_text"
|
||||
android:layout_weight="1.00"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:paddingTop="20dp">
|
||||
<ProgressBar
|
||||
android:id="@+id/searching_people_nearby"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_people_nearby"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</ListView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_people_nearby"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_people_nearby"
|
||||
android:textColor="@color/swap_light_text"
|
||||
android:layout_weight="1.00"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/searching_people_nearby"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list_people_nearby"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</ListView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_cant_find_peers"
|
||||
android:paddingStart="20dp"
|
||||
android:textColor="@color/swap_light_text" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_send_fdroid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_fdroid_grey"
|
||||
android:text="@string/swap_send_fdroid"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="25dp"
|
||||
android:background="@android:color/transparent" />
|
||||
android:text="@string/swap_cant_find_peers"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:textColor="@color/swap_light_text" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_qr_scanner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_qr_grey"
|
||||
android:text="@string/swap_scan_qr_code"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="25dp"
|
||||
android:background="@android:color/transparent" />
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_send_fdroid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_fdroid_grey"
|
||||
android:text="@string/swap_send_fdroid"
|
||||
android:drawablePadding="10dp"
|
||||
android:paddingStart="25dp"
|
||||
android:paddingEnd="25dp"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_qr_scanner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_qr_grey"
|
||||
android:text="@string/swap_scan_qr_code"
|
||||
android:drawablePadding="10dp"
|
||||
android:paddingStart="25dp"
|
||||
android:paddingEnd="25dp"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
<org.fdroid.fdroid.views.swap.ConfirmReceive
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
@ -43,31 +43,34 @@
|
||||
<!-- 46px * 0.56 = 25.76sp -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="45dp">
|
||||
<!-- 80px * 0.56 = 45dp -->
|
||||
|
||||
<!-- TODO: Remove associated style files style="@style/SwapTheme.Wizard.ReceiveSwap.Deny"-->
|
||||
<Button
|
||||
android:id="@+id/no_button"
|
||||
android:text="@string/no"
|
||||
style="@style/SwapTheme.Wizard.ReceiveSwap.Deny"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@color/swap_deny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
/>
|
||||
|
||||
|
||||
<!-- TODO: Remove associated style files style="@style/SwapTheme.Wizard.ReceiveSwap.Confirm" -->
|
||||
<Button
|
||||
android:id="@+id/yes_button"
|
||||
android:text="@string/yes"
|
||||
style="@style/SwapTheme.Wizard.ReceiveSwap.Confirm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"/>
|
||||
android:background="@color/swap_confirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</org.fdroid.fdroid.views.swap.ConfirmReceive>
|
||||
|
@ -3,15 +3,15 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="?attr/listPreferredItemHeight"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingBottom="2dip"
|
||||
android:paddingTop="2dip">
|
||||
android:minHeight="?attr/listPreferredItemHeight"
|
||||
android:paddingBottom="4dip"
|
||||
android:paddingTop="4dip">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
|
||||
android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<org.fdroid.fdroid.views.swap.SwapSuccessView
|
||||
<org.fdroid.fdroid.views.swap.SwapAppsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</org.fdroid.fdroid.views.swap.SwapSuccessView>
|
||||
</org.fdroid.fdroid.views.swap.SwapAppsView>
|
9
F-Droid/res/menu/swap_search.xml
Normal file
9
F-Droid/res/menu/swap_search.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search_white"
|
||||
android:title="@string/menu_search"
|
||||
android:titleCondensed="@string/menu_search"/>
|
||||
|
||||
</menu>
|
@ -54,6 +54,7 @@
|
||||
like
|
||||
this: https://f-droid.org/repo</string>
|
||||
|
||||
<string name="incompatible">Incompatible</string>
|
||||
<string name="inst">Installed</string>
|
||||
<string name="not_inst">Not Installed</string>
|
||||
<string name="inst_known_source">Installed (from %s)</string>
|
||||
@ -336,6 +337,8 @@
|
||||
<string name="swap_send_fdroid">SEND F-DROID</string>
|
||||
<string name="swap_scan_qr_code">SCAN QR CODE</string>
|
||||
<string name="swap_no_peers_nearby">Could not find people nearby to swap with.</string>
|
||||
<string name="swap_connecting">Connecting</string>
|
||||
<string name="swap_confirm">Confirm swap</string>
|
||||
|
||||
<!-- WiFi AP status for Swap flow -->
|
||||
<string name="wifi_ap_public">Public</string>
|
||||
@ -344,5 +347,4 @@
|
||||
<string name="wifi_warning_public">May work</string>
|
||||
<string name="wifi_warning_private">Promising</string>
|
||||
<string name="wifi_warning_personal">Best bet</string>
|
||||
<string name="swap_connecting">Connecting</string>
|
||||
</resources>
|
||||
|
100
F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java
Normal file
100
F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java
Normal file
@ -0,0 +1,100 @@
|
||||
package javax.jmdns.impl;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
/**
|
||||
* The ServiceInfo class needs to be serialized in order to be sent as an Android broadcast.
|
||||
* In order to make it Parcelable (or Serializable for that matter), there are some package-scope
|
||||
* methods which needed to be used. Thus, this class is in the javax.jmdns.impl package so that
|
||||
* it can access those methods. This is as an alternative to modifying the source code of JmDNS.
|
||||
*/
|
||||
public class FDroidServiceInfo extends ServiceInfoImpl implements Parcelable {
|
||||
|
||||
public FDroidServiceInfo(ServiceInfo info) {
|
||||
super(info);
|
||||
}
|
||||
|
||||
private static byte[] readBytes(Parcel in) {
|
||||
byte[] bytes = new byte[in.readInt()];
|
||||
in.readByteArray(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public FDroidServiceInfo(Parcel in) {
|
||||
super(
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
in.readString(),
|
||||
in.readInt(),
|
||||
in.readInt(),
|
||||
in.readInt(),
|
||||
in.readByte() != 0,
|
||||
readBytes(in)
|
||||
);
|
||||
|
||||
int addressCount = in.readInt();
|
||||
for (int i = 0; i < addressCount; i ++) {
|
||||
try {
|
||||
addAddress((Inet4Address)Inet4Address.getByAddress(readBytes(in)));
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
addressCount = in.readInt();
|
||||
for (int i = 0; i < addressCount; i ++) {
|
||||
try {
|
||||
addAddress((Inet6Address)Inet6Address.getByAddress(readBytes(in)));
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(getType());
|
||||
dest.writeString(getName());
|
||||
dest.writeString(getSubtype());
|
||||
dest.writeInt(getPort());
|
||||
dest.writeInt(getWeight());
|
||||
dest.writeInt(getPriority());
|
||||
dest.writeByte(isPersistent() ? (byte) 1 : (byte) 0);
|
||||
dest.writeInt(getTextBytes().length);
|
||||
dest.writeByteArray(getTextBytes());
|
||||
dest.writeInt(getInet4Addresses().length);
|
||||
for (int i = 0; i < getInet4Addresses().length; i ++) {
|
||||
Inet4Address address = getInet4Addresses()[i];
|
||||
dest.writeInt(address.getAddress().length);
|
||||
dest.writeByteArray(address.getAddress());
|
||||
}
|
||||
dest.writeInt(getInet6Addresses().length);
|
||||
for (int i = 0; i < getInet6Addresses().length; i ++) {
|
||||
Inet6Address address = getInet6Addresses()[i];
|
||||
dest.writeInt(address.getAddress().length);
|
||||
dest.writeByteArray(address.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<FDroidServiceInfo> CREATOR = new Parcelable.Creator<FDroidServiceInfo>() {
|
||||
public FDroidServiceInfo createFromParcel(Parcel source) {
|
||||
return new FDroidServiceInfo(source);
|
||||
}
|
||||
|
||||
public FDroidServiceInfo[] newArray(int size) {
|
||||
return new FDroidServiceInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
@ -40,6 +40,7 @@ import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.text.TextUtils;
|
||||
@ -110,13 +111,14 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
// update in response to a user request.
|
||||
public static class UpdateReceiver extends ResultReceiver {
|
||||
|
||||
private Context context;
|
||||
private final Context context;
|
||||
private ProgressDialog dialog;
|
||||
private ProgressListener listener;
|
||||
private String lastShownMessage = null;
|
||||
|
||||
public UpdateReceiver(Handler handler) {
|
||||
public UpdateReceiver(@NonNull Context context, Handler handler) {
|
||||
super(handler);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public UpdateReceiver setDialog(ProgressDialog dialog) {
|
||||
@ -145,8 +147,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateReceiver showDialog(Context context) {
|
||||
this.context = context;
|
||||
public UpdateReceiver showDialog() {
|
||||
ensureDialog();
|
||||
dialog.show();
|
||||
return this;
|
||||
@ -227,9 +228,9 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
|
||||
public static UpdateReceiver updateRepoNow(String address, Context context, boolean showDialog) {
|
||||
Intent intent = new Intent(context, UpdateService.class);
|
||||
UpdateReceiver receiver = new UpdateReceiver(new Handler());
|
||||
UpdateReceiver receiver = new UpdateReceiver(context, new Handler());
|
||||
if (showDialog) {
|
||||
receiver.showDialog(context);
|
||||
receiver.showDialog();
|
||||
}
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
if (!TextUtils.isEmpty(address)) {
|
||||
|
@ -193,7 +193,7 @@ public class AppProvider extends FDroidProvider {
|
||||
}
|
||||
|
||||
String[] ALL = {
|
||||
IS_COMPATIBLE, APP_ID, NAME, SUMMARY, ICON, DESCRIPTION,
|
||||
_ID, IS_COMPATIBLE, APP_ID, NAME, SUMMARY, ICON, DESCRIPTION,
|
||||
LICENSE, WEB_URL, TRACKER_URL, SOURCE_URL, DONATE_URL,
|
||||
BITCOIN_ADDR, LITECOIN_ADDR, DOGECOIN_ADDR, FLATTR_ID,
|
||||
UPSTREAM_VERSION, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
|
||||
@ -396,6 +396,7 @@ public class AppProvider extends FDroidProvider {
|
||||
private static final String PATH_INSTALLED = "installed";
|
||||
private static final String PATH_CAN_UPDATE = "canUpdate";
|
||||
private static final String PATH_SEARCH = "search";
|
||||
private static final String PATH_SEARCH_REPO = "searchRepo";
|
||||
private static final String PATH_NO_APKS = "noApks";
|
||||
private static final String PATH_APPS = "apps";
|
||||
private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
|
||||
@ -416,6 +417,7 @@ public class AppProvider extends FDroidProvider {
|
||||
private static final int IGNORED = CATEGORY + 1;
|
||||
private static final int CALC_APP_DETAILS_FROM_INDEX = IGNORED + 1;
|
||||
private static final int REPO = CALC_APP_DETAILS_FROM_INDEX + 1;
|
||||
private static final int SEARCH_REPO = REPO + 1;
|
||||
|
||||
static {
|
||||
matcher.addURI(getAuthority(), null, CODE_LIST);
|
||||
@ -425,6 +427,7 @@ public class AppProvider extends FDroidProvider {
|
||||
matcher.addURI(getAuthority(), PATH_NEWLY_ADDED, NEWLY_ADDED);
|
||||
matcher.addURI(getAuthority(), PATH_CATEGORY + "/*", CATEGORY);
|
||||
matcher.addURI(getAuthority(), PATH_SEARCH + "/*", SEARCH);
|
||||
matcher.addURI(getAuthority(), PATH_SEARCH_REPO + "/*/*", SEARCH_REPO);
|
||||
matcher.addURI(getAuthority(), PATH_REPO + "/#", REPO);
|
||||
matcher.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
|
||||
matcher.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
|
||||
@ -508,6 +511,14 @@ public class AppProvider extends FDroidProvider {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri getSearchUri(Repo repo, String query) {
|
||||
return getContentUri().buildUpon()
|
||||
.appendPath(PATH_SEARCH_REPO)
|
||||
.appendPath(repo.id + "")
|
||||
.appendPath(query)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() { return DBHelper.TABLE_APP; }
|
||||
|
||||
@ -680,6 +691,11 @@ public class AppProvider extends FDroidProvider {
|
||||
includeSwap = false;
|
||||
break;
|
||||
|
||||
case SEARCH_REPO:
|
||||
selection = selection.add(querySearch(uri.getPathSegments().get(1)));
|
||||
selection = selection.add(queryRepo(Long.parseLong(uri.getPathSegments().get(0))));
|
||||
break;
|
||||
|
||||
case NO_APKS:
|
||||
selection = selection.add(queryNoApks());
|
||||
break;
|
||||
|
@ -1,13 +1,14 @@
|
||||
package org.fdroid.fdroid.localrepo;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -16,8 +17,10 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@ -116,12 +119,14 @@ public class SwapManager {
|
||||
// ("Step" refers to the current view being shown in the UI)
|
||||
// ==========================================================
|
||||
|
||||
public static final int STEP_INTRO = 1;
|
||||
public static final int STEP_SELECT_APPS = 2;
|
||||
public static final int STEP_JOIN_WIFI = 3;
|
||||
public static final int STEP_SHOW_NFC = 4;
|
||||
public static final int STEP_WIFI_QR = 5;
|
||||
public static final int STEP_CONNECTING = 6;
|
||||
public static final int STEP_INTRO = 1;
|
||||
public static final int STEP_SELECT_APPS = 2;
|
||||
public static final int STEP_JOIN_WIFI = 3;
|
||||
public static final int STEP_SHOW_NFC = 4;
|
||||
public static final int STEP_WIFI_QR = 5;
|
||||
public static final int STEP_CONNECTING = 6;
|
||||
public static final int STEP_SUCCESS = 7;
|
||||
public static final int STEP_CONFIRM_SWAP = 8;
|
||||
|
||||
private @SwapStep int step = STEP_INTRO;
|
||||
|
||||
@ -143,13 +148,108 @@ public class SwapManager {
|
||||
return appsToSwap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UpdateService.UpdateReceiver refreshSwap() {
|
||||
return this.peer != null ? connectTo(peer) : null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UpdateService.UpdateReceiver connectTo(@NonNull Peer peer) {
|
||||
if (peer != this.peer) {
|
||||
Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
|
||||
}
|
||||
|
||||
peerRepo = ensureRepoExists(peer);
|
||||
|
||||
// Only ask server to swap with us, if we are actually running a local repo service.
|
||||
// It is possible to have a swap initiated without first starting a swap, in which
|
||||
// case swapping back is pointless.
|
||||
/*if (!newRepoConfig.preventFurtherSwaps() && isEnabled()) {
|
||||
askServerToSwapWithUs();
|
||||
}*/
|
||||
|
||||
return UpdateService.updateRepoNow(peer.getRepoAddress(), context, false);
|
||||
}
|
||||
/*
|
||||
private void askServerToSwapWithUs() {
|
||||
if (!newRepoConfig.isValidRepo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... args) {
|
||||
Uri repoUri = newRepoConfig.getRepoUri();
|
||||
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
|
||||
|
||||
AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", ConnectSwapActivity.this);
|
||||
HttpPost request = new HttpPost("/request-swap");
|
||||
HttpHost host = new HttpHost(repoUri.getHost(), repoUri.getPort(), repoUri.getScheme());
|
||||
|
||||
try {
|
||||
Log.d(TAG, "Asking server at " + newRepoConfig.getRepoUriString() + " to swap with us in return (by POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\")...");
|
||||
populatePostParams(swapBackUri, request);
|
||||
client.execute(host, request);
|
||||
} catch (IOException e) {
|
||||
notifyOfErrorOnUiThread();
|
||||
Log.e(TAG, "Error while asking server to swap with us: " + e.getMessage());
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void populatePostParams(String swapBackUri, HttpPost request) throws UnsupportedEncodingException {
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
params.add(new BasicNameValuePair("repo", swapBackUri));
|
||||
UrlEncodedFormEntity encodedParams = new UrlEncodedFormEntity(params);
|
||||
request.setEntity(encodedParams);
|
||||
}
|
||||
|
||||
private void notifyOfErrorOnUiThread() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(
|
||||
ConnectSwapActivity.this,
|
||||
R.string.swap_reciprocate_failed,
|
||||
Toast.LENGTH_LONG
|
||||
).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}.execute();
|
||||
}*/
|
||||
private Repo ensureRepoExists(@NonNull Peer peer) {
|
||||
// TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
|
||||
// the repos address in the database. Not sure on best behaviour in this situation.
|
||||
Repo repo = RepoProvider.Helper.findByAddress(context, peer.getRepoAddress());
|
||||
if (repo == null) {
|
||||
ContentValues values = new ContentValues(6);
|
||||
|
||||
// TODO: i18n and think about most appropriate name. Although it wont be visible in
|
||||
// the "Manage repos" UI after being marked as a swap repo here...
|
||||
values.put(RepoProvider.DataColumns.NAME, peer.getName());
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, peer.getRepoAddress());
|
||||
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
|
||||
values.put(RepoProvider.DataColumns.FINGERPRINT, peer.getFingerprint());
|
||||
values.put(RepoProvider.DataColumns.IN_USE, true);
|
||||
values.put(RepoProvider.DataColumns.IS_SWAP, true);
|
||||
Uri uri = RepoProvider.Helper.insert(context, values);
|
||||
repo = RepoProvider.Helper.findByUri(context, uri);
|
||||
}
|
||||
|
||||
return repo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Repo getPeerRepo() {
|
||||
return peerRepo;
|
||||
}
|
||||
|
||||
public void install(@NonNull final App app) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that we don't get put into an incorrect state, by forcing people to pass valid
|
||||
@ -158,7 +258,7 @@ public class SwapManager {
|
||||
* This is the same as, e.g. {@link Context#getSystemService(String)}
|
||||
*/
|
||||
@IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR,
|
||||
STEP_CONNECTING})
|
||||
STEP_CONNECTING, STEP_SUCCESS, STEP_CONFIRM_SWAP})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SwapStep {}
|
||||
|
||||
@ -171,6 +271,9 @@ public class SwapManager {
|
||||
@Nullable
|
||||
private Peer peer;
|
||||
|
||||
@Nullable
|
||||
private Repo peerRepo;
|
||||
|
||||
public void swapWith(Peer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
@ -313,43 +416,19 @@ public class SwapManager {
|
||||
// Interacting with Bluetooth adapter
|
||||
// ==========================================
|
||||
|
||||
@Nullable /* Emulators tend not to have bluetooth adapters. */
|
||||
private final BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
public boolean isBluetoothDiscoverable() {
|
||||
return bluetooth != null &&
|
||||
bluetooth.getScanMode() == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
return service != null && service.getBluetooth().isConnected();
|
||||
}
|
||||
|
||||
public void ensureBluetoothDiscoverable() {
|
||||
if (bluetooth == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bluetooth.isEnabled()) {
|
||||
if (!bluetooth.enable()) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (bluetooth.isEnabled()) {
|
||||
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
|
||||
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
|
||||
|
||||
// TODO: Hmm, don't like the idea of a background service being able to do this :(
|
||||
discoverableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
context.startActivity(discoverableIntent);
|
||||
if (service != null) {
|
||||
service.getBluetooth().start();
|
||||
}
|
||||
}
|
||||
|
||||
public void makeBluetoothNonDiscoverable() {
|
||||
if (bluetooth == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBluetoothDiscoverable()) {
|
||||
// TODO: How to disable this?
|
||||
if (service != null) {
|
||||
service.getBluetooth().stop();
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,6 +440,18 @@ public class SwapManager {
|
||||
return isWifiConnected() && service != null && service.isEnabled();
|
||||
}
|
||||
|
||||
public void ensureBonjourDiscoverable() {
|
||||
if (!isBonjourDiscoverable()) {
|
||||
// TODO: Enable bonjour (currently it is enabled by default when the service starts)
|
||||
}
|
||||
}
|
||||
|
||||
public void makeBonjourNotDiscoverable() {
|
||||
if (service != null) {
|
||||
// TODO: Disable bonjour (currently it is enabled by default when the service starts)
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isScanningForPeers() {
|
||||
return service != null && service.isScanningForPeers();
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.localrepo.peers.BluetoothFinder;
|
||||
import org.fdroid.fdroid.localrepo.peers.BonjourFinder;
|
||||
import org.fdroid.fdroid.localrepo.type.BluetoothType;
|
||||
import org.fdroid.fdroid.localrepo.type.BonjourType;
|
||||
import org.fdroid.fdroid.localrepo.type.NfcType;
|
||||
import org.fdroid.fdroid.localrepo.type.SwapType;
|
||||
import org.fdroid.fdroid.localrepo.type.WebServerType;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
|
||||
@ -31,27 +32,26 @@ import java.util.TimerTask;
|
||||
* Central service which manages all of the different moving parts of swap which are required
|
||||
* to enable p2p swapping of apps. Currently manages WiFi and NFC. Will manage Bluetooth in
|
||||
* the future.
|
||||
*
|
||||
* TODO: Manage threading correctly.
|
||||
*/
|
||||
public class SwapService extends Service {
|
||||
|
||||
private static final String TAG = "SwapService";
|
||||
|
||||
public static final String BONJOUR_STATE_CHANGE = "org.fdroid.fdroid.BONJOUR_STATE_CHANGE";
|
||||
public static final String BLUETOOTH_STATE_CHANGE = "org.fdroid.fdroid.BLUETOOTH_STATE_CHANGE";
|
||||
public static final String EXTRA_STARTING = "STARTING";
|
||||
public static final String EXTRA_STARTED = "STARTED";
|
||||
public static final String EXTRA_STOPPED = "STOPPED";
|
||||
|
||||
private static final int NOTIFICATION = 1;
|
||||
|
||||
private final Binder binder = new Binder();
|
||||
private final BonjourType bonjourType;
|
||||
private final WebServerType webServerType;
|
||||
private SwapType bonjourType;
|
||||
private SwapType bluetoothType;
|
||||
private SwapType webServerType;
|
||||
|
||||
private final BonjourFinder bonjourFinder;
|
||||
private final BluetoothFinder bluetoothFinder;
|
||||
|
||||
|
||||
// TODO: The NFC type can't really be managed by the service, because it is intrinsically tied
|
||||
// to a specific _Activity_, and will only be active while that activity is shown. This service
|
||||
// knows nothing about activities.
|
||||
private final NfcType nfcType;
|
||||
private BonjourFinder bonjourFinder;
|
||||
private BluetoothFinder bluetoothFinder;
|
||||
|
||||
private final static int TIMEOUT = 900000; // 15 mins
|
||||
|
||||
@ -65,22 +65,29 @@ public class SwapService extends Service {
|
||||
return bonjourFinder.isScanning() || bluetoothFinder.isScanning();
|
||||
}
|
||||
|
||||
public SwapType getBluetooth() {
|
||||
return bluetoothType;
|
||||
}
|
||||
|
||||
public SwapType getBonjour() {
|
||||
return bluetoothType;
|
||||
}
|
||||
|
||||
public class Binder extends android.os.Binder {
|
||||
public SwapService getService() {
|
||||
return SwapService.this;
|
||||
}
|
||||
}
|
||||
|
||||
public SwapService() {
|
||||
nfcType = new NfcType(this);
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
bonjourType = new BonjourType(this);
|
||||
bluetoothType = BluetoothType.create(this);
|
||||
webServerType = new WebServerType(this);
|
||||
bonjourFinder = new BonjourFinder(this);
|
||||
bluetoothFinder = new BluetoothFinder(this);
|
||||
}
|
||||
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "Creating service, will register appropriate listeners.");
|
||||
Preferences.get().registerLocalRepoBonjourListeners(bonjourEnabledListener);
|
||||
Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener);
|
||||
@ -139,7 +146,7 @@ public class SwapService extends Service {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Log.d(TAG, "Started background task to enable swapping.");
|
||||
enableSwappingSynchronous();
|
||||
enableSwappingAsynchronous();
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -160,13 +167,13 @@ public class SwapService extends Service {
|
||||
|
||||
/**
|
||||
* The guts of this class - responsible for enabling the relevant services for swapping.
|
||||
* * Doesn't know anything about enabled/disabled.
|
||||
* * Runs synchronously on the thread it was called.
|
||||
* Doesn't know anything about enabled/disabled state, you should check that before invoking
|
||||
* this method so it doesn't start something that is already started.
|
||||
* Runs asynchronously on several background threads.
|
||||
*/
|
||||
private void enableSwappingSynchronous() {
|
||||
nfcType.start();
|
||||
webServerType.start();
|
||||
bonjourType.start();
|
||||
private void enableSwappingAsynchronous() {
|
||||
webServerType.startInBackground();
|
||||
bonjourType.startInBackground();
|
||||
}
|
||||
|
||||
public void disableSwapping() {
|
||||
@ -199,13 +206,12 @@ public class SwapService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SwapService#enableSwappingSynchronous()
|
||||
* @see SwapService#enableSwappingAsynchronous()
|
||||
*/
|
||||
private void disableSwappingSynchronous() {
|
||||
Log.d(TAG, "Disabling SwapService (bonjour, webserver, etc)");
|
||||
bonjourType.stop();
|
||||
webServerType.stop();
|
||||
nfcType.stop();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
@ -219,7 +225,7 @@ public class SwapService extends Service {
|
||||
protected Void doInBackground(Void... params) {
|
||||
Log.d(TAG, "Restarting swap services.");
|
||||
disableSwappingSynchronous();
|
||||
enableSwappingSynchronous();
|
||||
enableSwappingAsynchronous();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
@ -245,6 +251,7 @@ public class SwapService extends Service {
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local...
|
||||
// TODO: Remove this preference...
|
||||
private final Preferences.ChangeListener bonjourEnabledListener = new Preferences.ChangeListener() {
|
||||
@Override
|
||||
public void onPreferenceChange() {
|
||||
|
@ -30,7 +30,7 @@ public class BluetoothPeer implements Peer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Peer peer) {
|
||||
public boolean equals(Object peer) {
|
||||
return peer != null && peer instanceof BluetoothPeer && ((BluetoothPeer)peer).device.getAddress().equals(device.getAddress());
|
||||
}
|
||||
|
||||
@ -39,6 +39,12 @@ public class BluetoothPeer implements Peer {
|
||||
return "bluetooth://" + device.getAddress() + "/fdroid/repo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
// TODO: Get fingerprint somehow over bluetooth.
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
@ -5,12 +5,14 @@ import android.net.wifi.WifiManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceEvent;
|
||||
@ -24,7 +26,7 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
public static final String HTTP_SERVICE_TYPE = "_http._tcp.local.";
|
||||
public static final String HTTPS_SERVICE_TYPE = "_https._tcp.local.";
|
||||
|
||||
private JmDNS mJmdns;
|
||||
private JmDNS jmdns;
|
||||
private WifiManager wifiManager;
|
||||
private WifiManager.MulticastLock mMulticastLock;
|
||||
|
||||
@ -35,6 +37,8 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
@Override
|
||||
public void scan() {
|
||||
|
||||
Log.d(TAG, "Requested Bonjour (mDNS) scan for peers.");
|
||||
|
||||
if (wifiManager == null) {
|
||||
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
mMulticastLock = wifiManager.createMulticastLock(context.getPackageName());
|
||||
@ -42,7 +46,8 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
}
|
||||
|
||||
if (isScanning) {
|
||||
Log.d(TAG, "Requested Bonjour scan, but already scanning, so will ignore request.");
|
||||
Log.d(TAG, "Requested Bonjour scan, but already scanning. But we will still try to explicitly scan for services.");
|
||||
// listServices();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -53,16 +58,8 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
int ip = wifiManager.getConnectionInfo().getIpAddress();
|
||||
byte[] byteIp = {
|
||||
(byte) (ip & 0xff),
|
||||
(byte) (ip >> 8 & 0xff),
|
||||
(byte) (ip >> 16 & 0xff),
|
||||
(byte) (ip >> 24 & 0xff)
|
||||
};
|
||||
Log.d(TAG, "Searching for mDNS clients...");
|
||||
mJmdns = JmDNS.create(InetAddress.getByAddress(byteIp));
|
||||
Log.d(TAG, "Finished searching for mDNS clients.");
|
||||
Log.d(TAG, "Searching for Bonjour (mDNS) clients...");
|
||||
jmdns = JmDNS.create(InetAddress.getByName(FDroidApp.ipAddressString));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -71,16 +68,44 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
Log.d(TAG, "Adding mDNS service listeners.");
|
||||
if (mJmdns != null) {
|
||||
mJmdns.addServiceListener(HTTP_SERVICE_TYPE, BonjourFinder.this);
|
||||
mJmdns.addServiceListener(HTTPS_SERVICE_TYPE, BonjourFinder.this);
|
||||
if (jmdns != null) {
|
||||
Log.d(TAG, "Adding mDNS service listeners for " + HTTP_SERVICE_TYPE + " and " + HTTPS_SERVICE_TYPE);
|
||||
jmdns.addServiceListener(HTTP_SERVICE_TYPE, BonjourFinder.this);
|
||||
jmdns.addServiceListener(HTTPS_SERVICE_TYPE, BonjourFinder.this);
|
||||
listServices();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void listServices() {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Log.d(TAG, "Explicitly querying for services, in addition to waiting for notifications.");
|
||||
addFDroidServices(jmdns.list(HTTP_SERVICE_TYPE));
|
||||
addFDroidServices(jmdns.list(HTTPS_SERVICE_TYPE));
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Remove once stable, added here for testing because it is easier to see the
|
||||
// data being broadcast over mDNS.
|
||||
/*@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
Log.d(TAG, "Queuing up another poll in 2 secs.");
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Time to poll for services again.");
|
||||
listServices();
|
||||
}
|
||||
}, 2000);
|
||||
}*/
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serviceRemoved(ServiceEvent event) {
|
||||
}
|
||||
@ -95,11 +120,11 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
// so that more detailed info can be shown to the user.
|
||||
//
|
||||
// If so, when is the old one removed?
|
||||
addFDroidService(event);
|
||||
addFDroidService(event.getInfo());
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
mJmdns.requestServiceInfo(event.getType(), event.getName(), true);
|
||||
jmdns.requestServiceInfo(event.getType(), event.getName(), true);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
@ -107,7 +132,13 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
|
||||
@Override
|
||||
public void serviceResolved(ServiceEvent event) {
|
||||
addFDroidService(event);
|
||||
addFDroidService(event.getInfo());
|
||||
}
|
||||
|
||||
private void addFDroidServices(ServiceInfo[] services) {
|
||||
for (ServiceInfo info : services) {
|
||||
addFDroidService(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,14 +146,24 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
* Checks that the service is an F-Droid service, and also that it is not the F-Droid service
|
||||
* for this device (by comparing its signing fingerprint to our signing fingerprint).
|
||||
*/
|
||||
private void addFDroidService(ServiceEvent event) {
|
||||
final ServiceInfo serviceInfo = event.getInfo();
|
||||
private void addFDroidService(ServiceInfo serviceInfo) {
|
||||
final String type = serviceInfo.getPropertyString("type");
|
||||
final String fingerprint = serviceInfo.getPropertyString("fingerprint");
|
||||
final boolean isFDroid = type != null && type.startsWith("fdroidrepo");
|
||||
final boolean isSelf = FDroidApp.repo != null && fingerprint != null && fingerprint.equalsIgnoreCase(FDroidApp.repo.fingerprint);
|
||||
if (isFDroid && !isSelf) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Found F-Droid swap Bonjour service:\n" + serviceInfo);
|
||||
}
|
||||
foundPeer(new BonjourPeer(serviceInfo));
|
||||
} else {
|
||||
if (BuildConfig.DEBUG) {
|
||||
if (isSelf) {
|
||||
Log.d(TAG, "Ignoring Bonjour service because it belongs to this device:\n" + serviceInfo);
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring Bonjour service because it doesn't look like an F-Droid swap repo:\n" + serviceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,13 +173,14 @@ public class BonjourFinder extends PeerFinder<BonjourPeer> implements ServiceLis
|
||||
mMulticastLock.release();
|
||||
}
|
||||
|
||||
if (mJmdns == null)
|
||||
return;
|
||||
mJmdns.removeServiceListener(HTTP_SERVICE_TYPE, this);
|
||||
mJmdns.removeServiceListener(HTTPS_SERVICE_TYPE, this);
|
||||
mJmdns = null;
|
||||
isScanning = false;
|
||||
|
||||
if (jmdns == null)
|
||||
return;
|
||||
jmdns.removeServiceListener(HTTP_SERVICE_TYPE, this);
|
||||
jmdns.removeServiceListener(HTTPS_SERVICE_TYPE, this);
|
||||
jmdns = null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,14 +4,19 @@ import android.os.Parcel;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
|
||||
import javax.jmdns.impl.FDroidServiceInfo;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
import javax.jmdns.impl.ServiceInfoImpl;
|
||||
|
||||
public class BonjourPeer implements Peer {
|
||||
|
||||
private ServiceInfo serviceInfo;
|
||||
private FDroidServiceInfo serviceInfo;
|
||||
|
||||
public BonjourPeer(ServiceInfo serviceInfo) {
|
||||
this.serviceInfo = serviceInfo;
|
||||
this.serviceInfo = new FDroidServiceInfo(serviceInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -30,18 +35,22 @@ public class BonjourPeer implements Peer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Peer peer) {
|
||||
public boolean equals(Object peer) {
|
||||
if (peer != null && peer instanceof BonjourPeer) {
|
||||
BonjourPeer that = (BonjourPeer)peer;
|
||||
// TODO: Don't use "name" for comparing, but rather fingerprint of the swap repo.
|
||||
return that.serviceInfo.getName().equals(this.serviceInfo.getName());
|
||||
return this.getFingerprint().equals(that.getFingerprint());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepoAddress() {
|
||||
return serviceInfo.getURL();
|
||||
return serviceInfo.getURL(); // Automatically appends the "path" property if present, so no need to do it ourselves.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFingerprint() {
|
||||
return serviceInfo.getPropertyString("fingerprint");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,27 +60,11 @@ public class BonjourPeer implements Peer {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(serviceInfo.getType());
|
||||
dest.writeString(serviceInfo.getName());
|
||||
dest.writeString(serviceInfo.getSubtype());
|
||||
dest.writeInt(serviceInfo.getPort());
|
||||
dest.writeInt(serviceInfo.getWeight());
|
||||
dest.writeInt(serviceInfo.getPriority());
|
||||
dest.writeByte(serviceInfo.isPersistent() ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(serviceInfo.getTextString());
|
||||
dest.writeParcelable(serviceInfo, flags);
|
||||
}
|
||||
|
||||
protected BonjourPeer(Parcel in) {
|
||||
String type = in.readString();
|
||||
String name = in.readString();
|
||||
String subtype = in.readString();
|
||||
int port = in.readInt();
|
||||
int weight = in.readInt();
|
||||
int priority = in.readInt();
|
||||
boolean persistent = in.readByte() != 0;
|
||||
String text = in.readString();
|
||||
|
||||
this.serviceInfo = ServiceInfo.create(type, name, subtype, port, weight, priority, persistent, text);
|
||||
this.serviceInfo = in.readParcelable(FDroidServiceInfo.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<BonjourPeer> CREATOR = new Creator<BonjourPeer>() {
|
||||
|
@ -9,7 +9,9 @@ public interface Peer extends Parcelable {
|
||||
|
||||
@DrawableRes int getIcon();
|
||||
|
||||
boolean equals(Peer peer);
|
||||
boolean equals(Object peer);
|
||||
|
||||
String getRepoAddress();
|
||||
|
||||
String getFingerprint();
|
||||
}
|
||||
|
@ -37,4 +37,8 @@ public abstract class PeerFinder<T extends Peer> {
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
protected void removePeer(T peer) {
|
||||
// TODO: Broadcast messages when peers are removed too.
|
||||
}
|
||||
|
||||
}
|
||||
|
102
F-Droid/src/org/fdroid/fdroid/localrepo/type/BluetoothType.java
Normal file
102
F-Droid/src/org/fdroid/fdroid/localrepo/type/BluetoothType.java
Normal file
@ -0,0 +1,102 @@
|
||||
package org.fdroid.fdroid.localrepo.type;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.localrepo.SwapService;
|
||||
|
||||
public class BluetoothType extends SwapType {
|
||||
|
||||
private static final String TAG = "BluetoothBroadcastType";
|
||||
|
||||
@NonNull
|
||||
private final BluetoothAdapter adapter;
|
||||
|
||||
public static SwapType create(@NonNull Context context) {
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (adapter == null) {
|
||||
return new NoBluetoothType(context);
|
||||
} else {
|
||||
return new BluetoothType(context, adapter);
|
||||
}
|
||||
};
|
||||
|
||||
private BluetoothType(@NonNull Context context, @NonNull BluetoothAdapter adapter) {
|
||||
super(context);
|
||||
this.adapter = adapter;
|
||||
|
||||
context.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1)) {
|
||||
case BluetoothAdapter.SCAN_MODE_NONE:
|
||||
setConnected(false);
|
||||
break;
|
||||
|
||||
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
|
||||
setConnected(true);
|
||||
break;
|
||||
|
||||
// Only other is BluetoothAdapter.SCAN_MODE_CONNECTABLE. For now don't handle that.
|
||||
}
|
||||
}
|
||||
}, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
sendBroadcast(SwapService.EXTRA_STARTING);
|
||||
|
||||
if (!adapter.isEnabled()) {
|
||||
if (!adapter.enable()) {
|
||||
setConnected(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (adapter.isEnabled()) {
|
||||
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
|
||||
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
|
||||
|
||||
// TODO: Hmm, don't like the idea of a background service being able to do this :(
|
||||
discoverableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
// Can't get notified if they cancel this, because we are not an activity and thus
|
||||
// can't start an activity for a result :(
|
||||
context.startActivity(discoverableIntent);
|
||||
|
||||
// Don't setConnected(true) yet, wait for the broadcast to come from the BluetoothAdapter
|
||||
// saying its state has changed.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
Log.d(TAG, "Making bluetooth non-discoverable.");
|
||||
adapter.cancelDiscovery();
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBroadcastAction() {
|
||||
return SwapService.BLUETOOTH_STATE_CHANGE;
|
||||
}
|
||||
|
||||
private static class NoBluetoothType extends SwapType {
|
||||
|
||||
public NoBluetoothType(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {}
|
||||
|
||||
@Override
|
||||
public void stop() {}
|
||||
}
|
||||
}
|
@ -3,33 +3,44 @@ package org.fdroid.fdroid.localrepo.type;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.localrepo.SwapService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
public class BonjourType implements SwapType {
|
||||
/**
|
||||
* Sends a {@link SwapService#BONJOUR_STATE_CHANGE} broadcasts when starting, started or stopped.
|
||||
*/
|
||||
public class BonjourType extends SwapType {
|
||||
|
||||
private static final String TAG = "BonjourBroadcastType";
|
||||
private static final String TAG = "BonjourSwapService";
|
||||
|
||||
private JmDNS jmdns;
|
||||
private ServiceInfo pairService;
|
||||
private final Context context;
|
||||
|
||||
public BonjourType(Context context) {
|
||||
this.context = context;
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
if (!Preferences.get().isLocalRepoBonjourEnabled())
|
||||
Log.d(TAG, "Preparing to start Bonjour service.");
|
||||
if (!Preferences.get().isLocalRepoBonjourEnabled()) {
|
||||
// TODO: Remove this preference completely, it is now dealt with in the start swap screen.
|
||||
Log.i(TAG, "Not going to start Bonjour swap service because disabled via preferences.");
|
||||
return;
|
||||
}
|
||||
|
||||
sendBroadcast(SwapService.EXTRA_STARTING);
|
||||
|
||||
/*
|
||||
* a ServiceInfo can only be registered with a single instance
|
||||
@ -54,12 +65,14 @@ public class BonjourType implements SwapType {
|
||||
try {
|
||||
Log.d(TAG, "Starting bonjour service...");
|
||||
pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
|
||||
jmdns = JmDNS.create();
|
||||
jmdns = JmDNS.create(InetAddress.getByName(FDroidApp.ipAddressString));
|
||||
jmdns.registerService(pairService);
|
||||
setConnected(true);
|
||||
Log.d(TAG, "... Bounjour service started.");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error while registering jmdns service: " + e);
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
setConnected(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +80,7 @@ public class BonjourType implements SwapType {
|
||||
public void stop() {
|
||||
Log.d(TAG, "Unregistering MDNS service...");
|
||||
clearCurrentMDNSService();
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
private void clearCurrentMDNSService() {
|
||||
@ -82,8 +96,8 @@ public class BonjourType implements SwapType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
|
||||
public String getBroadcastAction() {
|
||||
return SwapService.BONJOUR_STATE_CHANGE;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
package org.fdroid.fdroid.localrepo.type;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class NfcType implements SwapType {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public NfcType(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,73 @@
|
||||
package org.fdroid.fdroid.localrepo.type;
|
||||
|
||||
public interface SwapType {
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
void start();
|
||||
import org.fdroid.fdroid.localrepo.SwapService;
|
||||
|
||||
void stop();
|
||||
/**
|
||||
* There is lots of common functionality, and a common API among different communication protocols
|
||||
* associated with the swap process. This includes Bluetooth visability, Bonjour visability,
|
||||
* and the web server which serves info for swapping. This class provides a common API for
|
||||
* starting and stopping these services. In addition, it helps with the process of sending broadcast
|
||||
* intents in response to the thing starting or stopping.
|
||||
*/
|
||||
public abstract class SwapType {
|
||||
|
||||
void restart();
|
||||
private boolean isConnected;
|
||||
|
||||
@NonNull
|
||||
protected final Context context;
|
||||
|
||||
public SwapType(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
abstract public void start();
|
||||
|
||||
abstract public void stop();
|
||||
|
||||
protected String getBroadcastAction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final void setConnected(boolean connected) {
|
||||
if (connected) {
|
||||
isConnected = true;
|
||||
sendBroadcast(SwapService.EXTRA_STARTED);
|
||||
} else {
|
||||
isConnected = false;
|
||||
sendBroadcast(SwapService.EXTRA_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends either a {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STARTING},
|
||||
* {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STARTED} or
|
||||
* {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STOPPED} broadcast.
|
||||
*/
|
||||
protected final void sendBroadcast(String extra) {
|
||||
if (getBroadcastAction() != null) {
|
||||
Intent intent = new Intent(getBroadcastAction());
|
||||
intent.putExtra(extra, true);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public void startInBackground() {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
start();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
@ -17,21 +17,21 @@ import java.io.IOException;
|
||||
import java.net.BindException;
|
||||
import java.util.Random;
|
||||
|
||||
public class WebServerType implements SwapType {
|
||||
public class WebServerType extends SwapType {
|
||||
|
||||
private static final String TAG = "WebServerType";
|
||||
|
||||
private Handler webServerThreadHandler = null;
|
||||
private LocalHTTPD localHttpd;
|
||||
private final Context context;
|
||||
|
||||
public WebServerType(Context context) {
|
||||
this.context = context;
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
Log.d(TAG, "Preparing swap webserver.");
|
||||
Runnable webServer = new Runnable() {
|
||||
// Tell Eclipse this is not a leak because of Looper use.
|
||||
@SuppressLint("HandlerLeak")
|
||||
@ -51,7 +51,9 @@ public class WebServerType implements SwapType {
|
||||
}
|
||||
};
|
||||
try {
|
||||
Log.d(TAG, "Starting swap webserver...");
|
||||
localHttpd.start();
|
||||
Log.d(TAG, "Swap webserver started.");
|
||||
} catch (BindException e) {
|
||||
int prev = FDroidApp.port;
|
||||
FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
|
||||
@ -79,9 +81,4 @@ public class WebServerType implements SwapType {
|
||||
webServerThreadHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
package org.fdroid.fdroid.localrepo.type;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class WifiType implements SwapType {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public WifiType(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -38,6 +38,7 @@ public class WifiStateChangeService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again.");
|
||||
FDroidApp.initWifiSettings();
|
||||
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
|
||||
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
||||
@ -66,6 +67,7 @@ public class WifiStateChangeService extends Service {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
Log.d(TAG, "Checking wifi state (in background thread).");
|
||||
WifiInfo wifiInfo = null;
|
||||
|
||||
wifiState = wifiManager.getWifiState();
|
||||
@ -85,9 +87,12 @@ public class WifiStateChangeService extends Service {
|
||||
} else { // a hotspot can be active during WIFI_STATE_UNKNOWN
|
||||
FDroidApp.ipAddressString = getIpAddressFromNetworkInterface();
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "waiting for an IP address...");
|
||||
|
||||
if (FDroidApp.ipAddressString == null) {
|
||||
Thread.sleep(1000);
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "waiting for an IP address...");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCancelled()) // can be canceled by a change via WifiStateChangeReceiver
|
||||
@ -95,6 +100,7 @@ public class WifiStateChangeService extends Service {
|
||||
|
||||
if (wifiInfo != null) {
|
||||
String ssid = wifiInfo.getSSID();
|
||||
Log.d(TAG, "Have wifi info, connected to " + ssid);
|
||||
if (ssid != null) {
|
||||
FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1");
|
||||
}
|
||||
@ -104,6 +110,7 @@ public class WifiStateChangeService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Can this be moved to the swap service instead?
|
||||
String scheme;
|
||||
if (Preferences.get().isLocalRepoHttpsEnabled())
|
||||
scheme = "https";
|
||||
|
@ -148,7 +148,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (updateHandler != null) {
|
||||
updateHandler.showDialog(this);
|
||||
updateHandler.showDialog();
|
||||
}
|
||||
/* let's see if someone is trying to send us a new repo */
|
||||
addRepoFromIntent(getIntent());
|
||||
|
@ -82,7 +82,7 @@ public class RepoDetailsFragment extends Fragment {
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if (updateHandler != null) {
|
||||
updateHandler.showDialog(getActivity());
|
||||
updateHandler.showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
79
F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java
Normal file
79
F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java
Normal file
@ -0,0 +1,79 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.localrepo.SwapManager;
|
||||
|
||||
public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivity.InnerView {
|
||||
|
||||
public ConfirmReceive(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ConfirmReceive(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ConfirmReceive(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public ConfirmReceive(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
private SwapWorkflowActivity getActivity() {
|
||||
return (SwapWorkflowActivity)getContext();
|
||||
}
|
||||
|
||||
private SwapManager getManager() {
|
||||
return getActivity().getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStep() {
|
||||
return SwapManager.STEP_CONFIRM_SWAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousStep() {
|
||||
return SwapManager.STEP_INTRO;
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
public int getToolbarColour() {
|
||||
return getResources().getColor(R.color.swap_blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolbarTitle() {
|
||||
return getResources().getString(R.string.swap_confirm);
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import android.os.Build;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@ -28,17 +29,20 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.localrepo.SwapManager;
|
||||
import org.fdroid.fdroid.localrepo.SwapService;
|
||||
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.InnerView {
|
||||
public class StartSwapView extends ScrollView implements SwapWorkflowActivity.InnerView {
|
||||
|
||||
private static final String TAG = "StartSwapView";
|
||||
|
||||
@ -111,12 +115,23 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
getManager().scanForPeers();
|
||||
|
||||
uiInitPeers();
|
||||
uiInitBluetooth();
|
||||
uiInitWifi();
|
||||
uiInitButtons();
|
||||
uiUpdatePeersInfo();
|
||||
|
||||
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
|
||||
new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
uiUpdateWifi();
|
||||
}
|
||||
},
|
||||
new IntentFilter(WifiStateChangeService.BROADCAST)
|
||||
);
|
||||
}
|
||||
|
||||
private void uiInitButtons() {
|
||||
@ -161,9 +176,13 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Peer peer = intent.getParcelableExtra(SwapManager.EXTRA_PEER);
|
||||
Log.d(TAG, "Found peer: " + peer + ", adding to list of peers in UI.");
|
||||
adapter.add(peer);
|
||||
uiUpdatePeersInfo();
|
||||
if (adapter.getPosition(peer) >= 0) {
|
||||
Log.d(TAG, "Found peer: " + peer + ", ignoring though, because it is already in our list.");
|
||||
} else {
|
||||
Log.d(TAG, "Found peer: " + peer + ", adding to list of peers in UI.");
|
||||
adapter.add(peer);
|
||||
uiUpdatePeersInfo();
|
||||
}
|
||||
}
|
||||
}, new IntentFilter(SwapManager.ACTION_PEER_FOUND));
|
||||
|
||||
@ -191,29 +210,51 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.
|
||||
|
||||
viewBluetoothId = (TextView)findViewById(R.id.device_id_bluetooth);
|
||||
viewBluetoothId.setText(bluetooth.getName());
|
||||
viewBluetoothId.setVisibility(bluetooth.isEnabled() ? View.VISIBLE : View.GONE);
|
||||
|
||||
int textResource = getManager().isBluetoothDiscoverable() ? R.string.swap_visible_bluetooth : R.string.swap_not_visible_bluetooth;
|
||||
textBluetoothVisible.setText(textResource);
|
||||
|
||||
Switch bluetoothSwitch = ((Switch) findViewById(R.id.switch_bluetooth));
|
||||
final Switch bluetoothSwitch = ((Switch) findViewById(R.id.switch_bluetooth));
|
||||
bluetoothSwitch.setChecked(getManager().isBluetoothDiscoverable());
|
||||
bluetoothSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
getManager().ensureBluetoothDiscoverable();
|
||||
getManager().scanForPeers();
|
||||
textBluetoothVisible.setText(R.string.swap_visible_bluetooth);
|
||||
viewBluetoothId.setVisibility(View.VISIBLE);
|
||||
uiUpdatePeersInfo();
|
||||
// TODO: When they deny the request for enabling bluetooth, we need to disable this switch...
|
||||
} else {
|
||||
getManager().cancelScanningForPeers();
|
||||
getManager().makeBluetoothNonDiscoverable();
|
||||
textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth);
|
||||
viewBluetoothId.setVisibility(View.GONE);
|
||||
uiUpdatePeersInfo();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.hasExtra(SwapService.EXTRA_STARTING)) {
|
||||
Log.d(TAG, "Bluetooth service is starting...");
|
||||
bluetoothSwitch.setEnabled(false);
|
||||
bluetoothSwitch.setChecked(true);
|
||||
} else {
|
||||
bluetoothSwitch.setEnabled(true);
|
||||
if (intent.hasExtra(SwapService.EXTRA_STARTED)) {
|
||||
Log.d(TAG, "Bluetooth service has started.");
|
||||
bluetoothSwitch.setChecked(true);
|
||||
} else {
|
||||
Log.d(TAG, "Bluetooth service has stopped.");
|
||||
bluetoothSwitch.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, new IntentFilter(SwapService.BLUETOOTH_STATE_CHANGE));
|
||||
|
||||
} else {
|
||||
findViewById(R.id.bluetooth_info).setVisibility(View.GONE);
|
||||
}
|
||||
@ -221,34 +262,56 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.
|
||||
|
||||
private void uiInitWifi() {
|
||||
|
||||
final TextView textWifiVisible = (TextView)findViewById(R.id.wifi_visible);
|
||||
|
||||
viewWifiId = (TextView)findViewById(R.id.device_id_wifi);
|
||||
viewWifiNetwork = (TextView)findViewById(R.id.wifi_network);
|
||||
|
||||
int textResource = getManager().isBonjourDiscoverable() ? R.string.swap_visible_wifi : R.string.swap_not_visible_wifi;
|
||||
textWifiVisible.setText(textResource);
|
||||
|
||||
Switch wifiSwitch = (Switch)findViewById(R.id.switch_wifi);
|
||||
final Switch wifiSwitch = (Switch)findViewById(R.id.switch_wifi);
|
||||
wifiSwitch.setChecked(getManager().isBonjourDiscoverable());
|
||||
wifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
textWifiVisible.setText(R.string.swap_visible_wifi);
|
||||
uiUpdatePeersInfo();
|
||||
getManager().ensureBonjourDiscoverable();
|
||||
} else {
|
||||
textWifiVisible.setText(R.string.swap_not_visible_wifi);
|
||||
uiUpdatePeersInfo();
|
||||
getManager().makeBonjourNotDiscoverable();
|
||||
}
|
||||
uiUpdatePeersInfo();
|
||||
uiUpdateWifi();
|
||||
}
|
||||
});
|
||||
|
||||
LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.hasExtra(SwapService.EXTRA_STARTING)) {
|
||||
Log.d(TAG, "Bonjour service is starting...");
|
||||
wifiSwitch.setEnabled(false);
|
||||
wifiSwitch.setChecked(true);
|
||||
} else {
|
||||
wifiSwitch.setEnabled(true);
|
||||
if (intent.hasExtra(SwapService.EXTRA_STARTED)) {
|
||||
Log.d(TAG, "Bonjour service has started.");
|
||||
wifiSwitch.setChecked(true);
|
||||
} else {
|
||||
Log.d(TAG, "Bonjour service has stopped.");
|
||||
wifiSwitch.setChecked(false);
|
||||
}
|
||||
}
|
||||
uiUpdateWifi();
|
||||
}
|
||||
}, new IntentFilter(SwapService.BONJOUR_STATE_CHANGE));
|
||||
|
||||
uiUpdateWifi();
|
||||
}
|
||||
|
||||
private void uiUpdateWifi() {
|
||||
|
||||
final TextView textWifiVisible = (TextView)findViewById(R.id.wifi_visible);
|
||||
int textResource = getManager().isBonjourDiscoverable() ? R.string.swap_visible_wifi : R.string.swap_not_visible_wifi;
|
||||
textWifiVisible.setText(textResource);
|
||||
|
||||
viewWifiId.setText(FDroidApp.ipAddressString);
|
||||
viewWifiId.setVisibility(TextUtils.isEmpty(FDroidApp.ipAddressString) ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (TextUtils.isEmpty(FDroidApp.bssid) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
|
||||
// empty bssid with an ipAddress means hotspot mode
|
||||
|
327
F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java
Normal file
327
F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java
Normal file
@ -0,0 +1,327 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.localrepo.SwapManager;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class SwapAppsView extends ListView implements
|
||||
SwapWorkflowActivity.InnerView,
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
public SwapAppsView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SwapAppsView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SwapAppsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public SwapAppsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
private SwapWorkflowActivity getActivity() {
|
||||
return (SwapWorkflowActivity)getContext();
|
||||
}
|
||||
|
||||
private SwapManager getState() {
|
||||
return getActivity().getState();
|
||||
}
|
||||
|
||||
private static final int LOADER_SWAPABLE_APPS = 759283741;
|
||||
private static final String TAG = "SwapAppsView";
|
||||
|
||||
private Repo repo;
|
||||
private AppListAdapter adapter;
|
||||
private String mCurrentFilterString;
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
repo = getActivity().getState().getPeerRepo();
|
||||
|
||||
if (repo == null) {
|
||||
// TODO: Uh oh, something stuffed up for this to happen.
|
||||
// TODO: What is the best course of action from here?
|
||||
}
|
||||
|
||||
adapter = new AppListAdapter(getContext(), getContext().getContentResolver().query(
|
||||
AppProvider.getRepoUri(repo), AppProvider.DataColumns.ALL, null, null, null));
|
||||
|
||||
setAdapter(adapter);
|
||||
|
||||
// either reconnect with an existing loader or start a new one
|
||||
getActivity().getSupportLoaderManager().initLoader(LOADER_SWAPABLE_APPS, null, this);
|
||||
|
||||
setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
showAppDetails(position);
|
||||
}
|
||||
});
|
||||
|
||||
schedulePollForUpdates();
|
||||
}
|
||||
|
||||
private void pollForUpdates() {
|
||||
if (adapter.getCount() > 1 ||
|
||||
(adapter.getCount() == 1 && !new App((Cursor)adapter.getItem(0)).id.equals("org.fdroid.fdroid"))) {
|
||||
Log.d(TAG, "Not polling for new apps from swap repo, because we already have more than one.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Polling swap repo to see if it has any updates.");
|
||||
UpdateService.UpdateReceiver receiver = getState().refreshSwap();
|
||||
if (receiver != null) {
|
||||
receiver.setListener(new ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
switch (event.type) {
|
||||
case UpdateService.EVENT_COMPLETE_WITH_CHANGES:
|
||||
Log.d(TAG, "Swap repo has updates, notifying the list adapter.");
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case UpdateService.EVENT_ERROR:
|
||||
// TODO: Well, if we can't get the index, we probably can't swapp apps.
|
||||
// Tell the user somethign helpful?
|
||||
break;
|
||||
|
||||
case UpdateService.EVENT_COMPLETE_AND_SAME:
|
||||
schedulePollForUpdates();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void schedulePollForUpdates() {
|
||||
Log.d(TAG, "Scheduling poll for updated swap repo in 5 seconds.");
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
pollForUpdates();
|
||||
Looper.loop();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) {
|
||||
|
||||
inflater.inflate(R.menu.swap_search, menu);
|
||||
|
||||
SearchView searchView = new SearchView(getActivity());
|
||||
|
||||
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
|
||||
MenuItemCompat.setActionView(searchMenuItem, searchView);
|
||||
MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
|
||||
|
||||
searchView.setOnQueryTextListener(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStep() {
|
||||
return SwapManager.STEP_SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviousStep() {
|
||||
return SwapManager.STEP_INTRO;
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
public int getToolbarColour() {
|
||||
return getResources().getColor(R.color.swap_bright_blue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolbarTitle() {
|
||||
return getResources().getString(R.string.swap_success);
|
||||
}
|
||||
|
||||
private void showAppDetails(int position) {
|
||||
Cursor c = (Cursor) adapter.getItem(position);
|
||||
App app = new App(c);
|
||||
// TODO: Show app details screen.
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorLoader onCreateLoader(int id, Bundle args) {
|
||||
Uri uri = TextUtils.isEmpty(mCurrentFilterString)
|
||||
? AppProvider.getRepoUri(repo)
|
||||
: AppProvider.getSearchUri(repo, mCurrentFilterString);
|
||||
|
||||
return new CursorLoader(getActivity(), uri, AppProvider.DataColumns.ALL, null, null, AppProvider.DataColumns.NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
adapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
adapter.swapCursor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
|
||||
if (mCurrentFilterString == null && newFilter == null) {
|
||||
return true;
|
||||
}
|
||||
if (mCurrentFilterString != null && mCurrentFilterString.equals(newFilter)) {
|
||||
return true;
|
||||
}
|
||||
mCurrentFilterString = newFilter;
|
||||
getActivity().getSupportLoaderManager().restartLoader(LOADER_SWAPABLE_APPS, null, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
// this is not needed since we respond to every change in text
|
||||
return true;
|
||||
}
|
||||
|
||||
private class AppListAdapter extends CursorAdapter {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final String TAG = "AppListAdapter";
|
||||
|
||||
@Nullable
|
||||
private LayoutInflater inflater;
|
||||
|
||||
@Nullable
|
||||
private Drawable defaultAppIcon;
|
||||
|
||||
public AppListAdapter(@NonNull Context context, @Nullable Cursor c) {
|
||||
super(context, c, FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private LayoutInflater getInflater(Context context) {
|
||||
if (inflater == null) {
|
||||
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
return inflater;
|
||||
}
|
||||
|
||||
private Drawable getDefaultAppIcon(Context context) {
|
||||
if (defaultAppIcon == null) {
|
||||
defaultAppIcon = context.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
|
||||
}
|
||||
return defaultAppIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = getInflater(context).inflate(R.layout.swap_app_list_item, parent, false);
|
||||
bindView(view, context, cursor);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
|
||||
TextView nameView = (TextView)view.findViewById(R.id.name);
|
||||
ImageView iconView = (ImageView)view.findViewById(android.R.id.icon);
|
||||
Button button = (Button)view.findViewById(R.id.button);
|
||||
TextView status = (TextView)view.findViewById(R.id.status);
|
||||
|
||||
final App app = new App(cursor);
|
||||
|
||||
nameView.setText(app.name);
|
||||
iconView.setImageDrawable(getDefaultAppIcon(context)); // TODO: Load icon from repo properly using UIL.
|
||||
|
||||
if (app.hasUpdates()) {
|
||||
button.setText(R.string.menu_upgrade);
|
||||
button.setEnabled(true);
|
||||
button.setBackgroundColor(getResources().getColor(R.color.fdroid_blue));
|
||||
button.setVisibility(View.VISIBLE);
|
||||
status.setVisibility(View.GONE);
|
||||
} else if (app.isInstalled()) {
|
||||
status.setText(R.string.inst);
|
||||
status.setTextColor(getResources().getColor(R.color.fdroid_green));
|
||||
status.setVisibility(View.VISIBLE);
|
||||
button.setVisibility(View.GONE);
|
||||
} else if (!app.compatible) {
|
||||
status.setText(R.string.incompatible);
|
||||
status.setTextColor(getResources().getColor(R.color.swap_light_grey_icon));
|
||||
status.setVisibility(View.VISIBLE);
|
||||
button.setVisibility(View.GONE);
|
||||
} else {
|
||||
button.setText(R.string.menu_install);
|
||||
button.setEnabled(true);
|
||||
button.setBackgroundColor(getResources().getColor(R.color.fdroid_green));
|
||||
button.setVisibility(View.VISIBLE);
|
||||
status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (app.hasUpdates() || app.compatible) {
|
||||
getState().install(app);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,7 @@ import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
@ -24,6 +25,8 @@ import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.localrepo.SwapManager;
|
||||
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SwapConnecting extends LinearLayout implements SwapWorkflowActivity.InnerView {
|
||||
|
||||
private final static String TAG = "SwapConnecting";
|
||||
@ -77,6 +80,31 @@ public class SwapConnecting extends LinearLayout implements SwapWorkflowActivity
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
((TextView) findViewById(R.id.progress)).setText(event.data.getString(UpdateService.EXTRA_ADDRESS));
|
||||
boolean finished = false;
|
||||
boolean error = false;
|
||||
switch (event.type) {
|
||||
case UpdateService.EVENT_ERROR:
|
||||
finished = true;
|
||||
error = true;
|
||||
break;
|
||||
case UpdateService.EVENT_COMPLETE_WITH_CHANGES:
|
||||
finished = true;
|
||||
break;
|
||||
case UpdateService.EVENT_COMPLETE_AND_SAME:
|
||||
finished = true;
|
||||
break;
|
||||
case UpdateService.EVENT_INFO:
|
||||
break;
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
if (error) {
|
||||
// TODO: Feedback to user about error, suggest fixes.
|
||||
} else {
|
||||
getActivity().showSwapConnected();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -237,6 +237,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
inflateInnerView(R.layout.swap_wifi_qr);
|
||||
}
|
||||
|
||||
public void showSwapConnected() {
|
||||
inflateInnerView(R.layout.swap_success);
|
||||
}
|
||||
|
||||
private boolean attemptToShowNfc() {
|
||||
// TODO: What if NFC is disabled? Hook up with NfcNotEnabledActivity? Or maybe only if they
|
||||
// click a relevant button?
|
||||
@ -286,9 +290,9 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
super.onPostExecute(v);
|
||||
protected void onPreExecute() {
|
||||
state.enableSwapping();
|
||||
super.onPreExecute();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user