Merge branch 'feature/swap-ui-implementation' into 'master'
Initial "swap" UI implementation This is a major rejigging of the "Local Repo" user interface, to make it easier for lay people to use F-Droid to swap their apps with their peers. I wont mention too much here, because the individual commits have more detailed information. However, it is worth noting that major things that probably deserve testing are: * Going through the proccess of creating a swap * Scanning the QR code from another users F-Droid "swap" screen * Scanning the QR code from their barcode scanner * Entering the URL shown below the QR code into the browser and following the steps * Installing an app from the "Swap" success screen * Using NFC to add swap (I couldn't test this as only have 1 NFC device in the house) I've actually had trouble installing from the swap screen after upgrading to Android 4.3.1 (CM) on my Nexus 4. However that issue seems to be present on the current stable "Local Repo" implementation too, so will stop banging my head against the desk in the interest of getting this initial swap implementation out. Subsequent work will go towards refining edge cases. I'm cataloging these in my fork of fdroidclient on github. The type of things which I still need to work on include : * i18n all of the strings * Enabling NFC * Dealing with WIFI disconnects * Support WIFI AP * Adding back the "search" functionality in the "create swap" screen * ... and no doubt others will arise ... A major feature which is missing is Bluetooth, but some earlier work has been done on that which will make it less of a burden to implement. As this is getting reviewed for merging, I will start working through these other issues. See merge request !28
@ -105,32 +105,41 @@
|
||||
<data android:pathPrefix="/repository/browse" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value=".SearchResults" />
|
||||
</activity>
|
||||
<!--
|
||||
The title for ManageReposActivity is "F-Droid" here, but "Repositories"
|
||||
when viewing the Activity itself in the app.
|
||||
-->
|
||||
<activity
|
||||
android:name=".views.ManageReposActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:parentActivityName=".FDroid" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
<!--
|
||||
This intent serves two purposes: Swapping apps between devices and adding a
|
||||
repo from a website (e.g. https://guardianproject.info/fdroid/repo).
|
||||
We intercept both of these situations in the FDroid activity, and then redirect
|
||||
to the appropriate handler (swap handling, manage repos respectively) from there.
|
||||
|
||||
The reason for this is that the only differentiating factor is the presence
|
||||
of a "swap=1" in the query string, and intent-filter is unable to deal with
|
||||
query parameters. An alternative would be to do something like fdroidswap:// as
|
||||
a scheme, but then we. Need to copy/paste all of this intent-filter stuff and
|
||||
keep it up to date when it changes or a bug is found.
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<!--
|
||||
Android's scheme matcher is case-sensitive, so include
|
||||
ALL CAPS versions to support ALL CAPS URLs in QR Codes.
|
||||
QR Codes have a special ALL CAPS mode that uses a reduced
|
||||
character set, making for more compact QR Codes.
|
||||
-->
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="HTTP" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="HTTPS" />
|
||||
<data android:scheme="fdroidrepo" />
|
||||
<data android:scheme="FDROIDREPO" />
|
||||
<data android:scheme="fdroidrepos" />
|
||||
<data android:scheme="FDROIDREPOS" />
|
||||
|
||||
<data android:host="*" />
|
||||
|
||||
<!--
|
||||
The pattern matcher here is poorly implemented, in particular the * is
|
||||
non-greedy, so you have to do stupid tricks to match patterns that have
|
||||
@ -161,23 +170,29 @@
|
||||
<data android:path="/.*/FDROID/REPO" />
|
||||
<data android:path="/.*/.*/FDROID/REPO" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<!--
|
||||
Android's scheme matcher is case-sensitive, so include
|
||||
ALL CAPS versions to support ALL CAPS URLs in QR Codes.
|
||||
QR Codes have a special ALL CAPS mode that uses a reduced
|
||||
character set, making for more compact QR Codes.
|
||||
-->
|
||||
<data android:scheme="fdroidrepo" />
|
||||
<data android:scheme="FDROIDREPO" />
|
||||
<data android:scheme="fdroidrepos" />
|
||||
<data android:scheme="FDROIDREPOS" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value=".SearchResults" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".views.swap.ConnectSwapActivity"
|
||||
android:theme="@style/SwapTheme.Wizard.ReceiveSwap"
|
||||
android:label=""
|
||||
android:noHistory="true"
|
||||
android:parentActivityName=".FDroid">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".views.ManageReposActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:parentActivityName=".FDroid" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
<!-- Handle NFC tags detected from outside our application -->
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
@ -202,7 +217,7 @@
|
||||
<activity
|
||||
android:name=".NfcNotEnabledActivity"
|
||||
android:noHistory="true" />
|
||||
<activity android:name=".views.QrWizardDownloadActivity" />
|
||||
<!--<activity android:name=".views.QrWizardDownloadActivity" />
|
||||
<activity android:name=".views.QrWizardWifiNetworkActivity" />
|
||||
<activity
|
||||
android:name=".views.LocalRepoActivity"
|
||||
@ -222,7 +237,7 @@
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".views.LocalRepoActivity" />
|
||||
</activity>
|
||||
</activity>-->
|
||||
<activity
|
||||
android:name=".views.RepoDetailsActivity"
|
||||
android:label="@string/menu_manage"
|
||||
@ -230,7 +245,7 @@
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ManageRepo" />
|
||||
android:value=".views.ManageReposActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@ -281,6 +296,24 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:label="@string/menu_swap"
|
||||
android:name=".views.swap.SwapActivity"
|
||||
android:parentActivityName=".FDroid"
|
||||
android:theme="@style/SwapTheme.Wizard">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:label="@string/swap"
|
||||
android:name=".views.swap.SwapAppListActivity"
|
||||
android:parentActivityName=".FDroid"
|
||||
android:theme="@style/SwapTheme.AppList">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".FDroid" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SearchResults"
|
||||
android:label="@string/search_results"
|
||||
|
@ -1,65 +1,114 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{{REPO_URL}} local FDroid repo</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>F-Droid swap</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
|
||||
<style>
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Trebuchet MS", Helvetica, sans-serif;
|
||||
color: #444;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Roboto Light", "Roboto Lt", Helvetica, sans-serif;
|
||||
color: #fff;
|
||||
background-color: #1c6bbc;
|
||||
}
|
||||
|
||||
#swap-icon {
|
||||
display: block;
|
||||
margin: 1em auto 0 auto;
|
||||
width: 25%;
|
||||
max-width: 400px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tick {
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 auto;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
padding: 0 1.5em;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ol {
|
||||
counter-reset:li;
|
||||
margin-left:0;
|
||||
padding-left:0;
|
||||
counter-reset: li;
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
ol > li {
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
padding-top: 20px;
|
||||
border-bottom: solid 1px #333;
|
||||
height: 4em;
|
||||
box-sizing: border-box;
|
||||
ol > li, #download-from-web {
|
||||
padding: 1em 0;
|
||||
border-bottom: solid 1px rgba(245, 245, 245, 0.2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ol > li:first-child {
|
||||
border-top: solid 1px #333;
|
||||
border-top: solid 1px rgba(245, 245, 245, 0.2);
|
||||
}
|
||||
|
||||
ol > li:before {
|
||||
content: counter(li);
|
||||
counter-increment: li;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 10px;
|
||||
font: bold 2em Sans-Serif;
|
||||
content: counter(li);
|
||||
counter-increment: li;
|
||||
font: bold 1.4em Sans-Serif;
|
||||
margin-left: 1em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Kerplapp Bootstrap</h1>
|
||||
<ol>
|
||||
<li><del>Find a Kerplapp Repo</del></li>
|
||||
<li><a href="{{CLIENT_URL}}">Download F-Droid client</a></li>
|
||||
<li>Install F-Droid client</li>
|
||||
<li><a href="{{REPO_URL}}">Add Kerplapp Repo to F-Droid client</a></li>
|
||||
<li>Kerplapp an App!</li>
|
||||
</ol>
|
||||
</body>
|
||||
ol > li a {
|
||||
font-family: "Roboto", Helvetica, sans-serif;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#download-from-web {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<img id="swap-icon" src="swap-icon.png" />
|
||||
<h1>You're minutes away from having swap success!</h1>
|
||||
<ol>
|
||||
<li>
|
||||
Find a swap
|
||||
<img src="swap-tick-done.png" class="tick done" alt="Done"/>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{CLIENT_URL}}">Download F-Droid</a>
|
||||
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
||||
</li>
|
||||
<li>
|
||||
Install F-Droid
|
||||
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{REPO_URL}}">Add the swap to F-Droid</a>
|
||||
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
||||
</li>
|
||||
<li>
|
||||
Install the apps you want
|
||||
<img src="swap-tick-not-done.png" class="tick not-done" alt="Not done" />
|
||||
</li>
|
||||
</ol>
|
||||
<div id="download-from-web">
|
||||
<!--<a href="https://f-droid.org/repository/browse/">-->
|
||||
Or, download apps from the web
|
||||
<!--</a>-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
BIN
assets/swap-icon.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
48
assets/swap-icon.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="210.73421"
|
||||
height="146.02573"
|
||||
id="svg4443">
|
||||
<defs
|
||||
id="defs4445" />
|
||||
<metadata
|
||||
id="metadata4448">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-323.20433,-293.63503)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(1.0670994,0,0,-1.0670994,446.72215,312.16094)"
|
||||
id="g3936-3">
|
||||
<path
|
||||
d="M 0,0 C 21.057,0 41.224,-7.789 56.795,-21.933 L 68.468,-9.08 C 49.692,7.971 25.381,17.361 0,17.361 c -51.395,0 -93.991,-38.272 -100.862,-87.807 l -14.889,7.251 c 7.94,-11.334 14.688,-28.971 18.26,-42.987 3.422,6.887 7.947,14.579 12.955,21.692 l 0.045,0 c 0,0.022 0.006,0.045 0.006,0.07 4.396,6.236 9.159,12.027 13.896,16.413 l -12.839,-3.155 C -77.015,-30.885 -42.056,0 0,0"
|
||||
id="path3938-7"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(1.0670994,0,0,-1.0670994,406.74135,394.21193)"
|
||||
id="g3940-3">
|
||||
<path
|
||||
d="m 0,0 c 0.406,1.264 0.864,2.497 1.38,3.706 6.838,16.143 22.842,27.496 41.442,27.496 22.372,0 40.921,-16.423 44.364,-37.831 l -13.394,4.844 c 9.088,-10.425 17.673,-27.247 22.707,-40.806 5.021,13.559 13.605,30.381 22.7,40.809 L 104.8,-6.993 c -3.397,31.195 -29.887,55.558 -61.978,55.558 -26.999,0 -50.053,-17.26 -58.697,-41.334"
|
||||
id="path3942-2"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/swap-tick-done.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/swap-tick-not-done.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-hdpi/ic_swap.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-hdpi/nfc_touch.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
res/drawable-hdpi/swap_success.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
res/drawable-hdpi/wifi.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
res/drawable-hdpi/wifi_ap_personal.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
res/drawable-hdpi/wifi_ap_private.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-hdpi/wifi_ap_public.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-ldpi/ic_swap.png
Normal file
After Width: | Height: | Size: 475 B |
BIN
res/drawable-ldpi/nfc_touch.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-ldpi/swap_success.png
Normal file
After Width: | Height: | Size: 918 B |
BIN
res/drawable-ldpi/wifi.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-ldpi/wifi_ap_personal.png
Normal file
After Width: | Height: | Size: 601 B |
BIN
res/drawable-ldpi/wifi_ap_private.png
Normal file
After Width: | Height: | Size: 475 B |
BIN
res/drawable-ldpi/wifi_ap_public.png
Normal file
After Width: | Height: | Size: 550 B |
BIN
res/drawable-mdpi/ic_swap.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
res/drawable-mdpi/nfc_touch.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
res/drawable-mdpi/swap_success.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-mdpi/wifi.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-mdpi/wifi_ap_personal.png
Normal file
After Width: | Height: | Size: 915 B |
BIN
res/drawable-mdpi/wifi_ap_private.png
Normal file
After Width: | Height: | Size: 616 B |
BIN
res/drawable-mdpi/wifi_ap_public.png
Normal file
After Width: | Height: | Size: 776 B |
BIN
res/drawable-xhdpi/ic_swap.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xhdpi/nfc_touch.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
res/drawable-xhdpi/swap_success.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
res/drawable-xhdpi/wifi.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
res/drawable-xhdpi/wifi_ap_personal.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-xhdpi/wifi_ap_private.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-xhdpi/wifi_ap_public.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable/ic_swap.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable/nfc_touch.png
Normal file
After Width: | Height: | Size: 16 KiB |
28
res/drawable/swap_action_button_skin.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/swap_light_blue_pressed" />
|
||||
<padding
|
||||
android:left="26.9dp"
|
||||
android:top="17.9dp"
|
||||
android:right="26.9dp"
|
||||
android:bottom="17.9dp" />
|
||||
<!--
|
||||
Padding left/right = 48px * 90dpi / 160dpi = ~26.9dp
|
||||
Padding top/bottom = 32px * 90dpi / 160dpi = ~17.9dp
|
||||
-->
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_light_blue" />
|
||||
<padding
|
||||
android:left="26.9dp"
|
||||
android:top="17.9dp"
|
||||
android:right="26.9dp"
|
||||
android:bottom="17.9dp" />
|
||||
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
20
res/drawable/swap_button_normal.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/white" />
|
||||
<padding
|
||||
android:top="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_blue" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
20
res/drawable/swap_button_pressed.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/white" />
|
||||
<padding
|
||||
android:top="1dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_blue_pressed" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
5
res/drawable/swap_button_skin.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/swap_button_pressed" />
|
||||
<item android:drawable="@drawable/swap_button_normal" />
|
||||
</selector>
|
23
res/drawable/swap_confirm_button_skin.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/swap_confirm_pressed" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_confirm" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
23
res/drawable/swap_deny_button_skin.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/swap_deny_pressed" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_deny" />
|
||||
<padding
|
||||
android:left="10dp"
|
||||
android:top="10dp"
|
||||
android:right="10dp"
|
||||
android:bottom="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
BIN
res/drawable/swap_nfc_icon.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
res/drawable/swap_qr_example.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
8
res/drawable/swap_start_button_skin.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="@color/swap_light_blue" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
BIN
res/drawable/swap_success.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
5
res/drawable/swap_wifi_likely_to_work.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/swap_wifi_likely_to_work" />
|
||||
<size android:width="88dp" android:height="7dp" />
|
||||
</shape>
|
5
res/drawable/swap_wifi_may_work.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/swap_wifi_may_work"/>
|
||||
<size android:width="88dp" android:height="7dp" />
|
||||
</shape>
|
4
res/drawable/swap_window_background.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/swap_blue" />
|
||||
</shape>
|
BIN
res/drawable/wifi.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
res/drawable/wifi_ap_personal.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable/wifi_ap_private.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable/wifi_ap_public.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
@ -4,7 +4,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:baselineAligned="false" >
|
||||
android:baselineAligned="false"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<!-- Actual icon size is dependent on preferences and set in
|
||||
AppListAdapater.java:layoutIcon() -->
|
||||
@ -14,7 +15,8 @@
|
||||
android:layout_width="48dip"
|
||||
android:layout_height="48dip"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:scaleType="fitCenter" />
|
||||
android:scaleType="fitCenter"
|
||||
tools:src="@drawable/ic_launcher" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
@ -44,7 +46,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="start"
|
||||
android:textAlignment="viewStart" />
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="F-Droid" />
|
||||
|
||||
<TextView android:id="@+id/status"
|
||||
android:textSize="13sp"
|
||||
@ -58,6 +61,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd"
|
||||
tools:text="Installed"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -79,6 +83,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="start"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Application manager"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/license"
|
||||
@ -93,6 +98,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd"
|
||||
tools:text="GPLv3"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
66
res/layout/list_content.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
Ported from the android-19 source. This was necessary so that we could inflate
|
||||
the view ourself (and hence theme it), but also get references to the relevant
|
||||
widgets (e.g. empty text view, progress bar, list). The list_content view we
|
||||
ported this from uses hidden/internal id's that we don't get the privilege of
|
||||
using. In the process, we also:
|
||||
* Added tools: attributes.
|
||||
* Changed the names of an id or two so that I can reference them from Java.
|
||||
-->
|
||||
|
||||
<!--
|
||||
/* Copyright 2010, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".views.swap.SwapActivity">
|
||||
|
||||
<LinearLayout android:id="@+id/progressContainer"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:gravity="center">
|
||||
|
||||
<ProgressBar style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@android:id/progress"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout android:id="@+id/listContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawSelectorOnTop="false"
|
||||
tools:listitem="@layout/applistitem"
|
||||
tools:listheader="@layout/swap_create_header" />
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
24
res/layout/swap_blank.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_description"
|
||||
android:text="Your mobile device becomes an app store with Swap!"
|
||||
style="@style/SwapTheme.StartSwap.MainText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_start_swap"
|
||||
android:text="START A SWAP"
|
||||
style="@style/SwapTheme.StartSwap.StartButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
</RelativeLayout>
|
70
res/layout/swap_confirm_receive.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="18dp">
|
||||
<!-- Padding is 32px * 0.56 = 18dip -->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:src="@drawable/ic_launcher"
|
||||
android:contentDescription="@string/icon"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_width="117.6dp"
|
||||
android:layout_height="117.6dp"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
<!-- 210 * 0.56 = 117.6 -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
android:text="@string/swap_welcome"
|
||||
style="@style/SwapTheme.Wizard.ReceiveSwap.MainText"
|
||||
android:layout_below="@id/icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28sp"
|
||||
android:lines="1" />
|
||||
<!-- 60 * 0.56 = 33.6 -->
|
||||
<!-- Temporarily making it smaller than 33.6 until we figure out how to
|
||||
prevent line breaks on the hyphen in F-Droid. -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_description"
|
||||
android:text="@string/swap_confirm_connect"
|
||||
style="@style/SwapTheme.Wizard.Text"
|
||||
android:layout_below="@id/text_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="25.75sp"/>
|
||||
<!-- 46px * 0.56 = 25.76sp -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_marginTop="45dp">
|
||||
<!-- 80px * 0.56 = 45dp -->
|
||||
|
||||
<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_marginRight="8dp"/>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/yes_button"
|
||||
android:text="@string/yes"
|
||||
style="@style/SwapTheme.Wizard.ReceiveSwap.Confirm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
8
res/layout/swap_create_header.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/text_description"
|
||||
android:text="Tap to select the apps you want to swap."
|
||||
style="@style/SwapTheme.StartSwap.MainText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
60
res/layout/swap_join_wifi.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".views.swap.SwapActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/text_description"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
style="@style/SwapTheme.Wizard.MainText"
|
||||
android:text="@string/swap_join_same_wifi"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/wifi_icon"
|
||||
android:src="@drawable/wifi"
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_centerHorizontal="true" />
|
||||
|
||||
<!--
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:id="@+id/btn_bluetooth"
|
||||
android:text="@string/swap_use_bluetooth"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:text="@string/swap_wifi_help"
|
||||
android:layout_above="@id/btn_bluetooth"
|
||||
android:id="@+id/btn_learn_more_about_wifi"/>
|
||||
-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="No network yet"
|
||||
android:id="@+id/wifi_ssid"
|
||||
style="@style/SwapTheme.Wizard.WifiSSID"
|
||||
android:layout_below="@id/wifi_icon"
|
||||
android:layout_centerHorizontal="true" />
|
||||
<!--android:layout_above="@id/wifi_available_networks_prompt"-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/swap_view_available_networks"
|
||||
android:id="@+id/wifi_available_networks_prompt"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_below="@id/wifi_ssid"
|
||||
android:paddingBottom="20dp"/>
|
||||
<!-- android:layout_above="@id/btn_learn_more_about_wifi" -->
|
||||
|
||||
</RelativeLayout>
|
114
res/layout/swap_learn_about_wifi.xml
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:gravity="bottom"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingTop="20dp"
|
||||
tools:context=".views.swap.SwapActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginRight="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_icon_public"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/wifi_ap_public"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_label_public"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Public"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_warning_public"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="May work"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_status_public"
|
||||
android:src="@drawable/swap_wifi_may_work"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginLeft="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_icon_private"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/wifi_ap_private" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_label_private"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Private" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_warning_private"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Promising" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_status_private"
|
||||
android:src="@drawable/swap_wifi_likely_to_work"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginLeft="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_icon_personal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/wifi_ap_personal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_label_personal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hotspot" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wifi_warning_personal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Best bet" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/wifi_status_personal"
|
||||
android:src="@drawable/swap_wifi_likely_to_work"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
35
res/layout/swap_nfc.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:paddingTop="38.8dp"> <!-- 69px * 96dpi / 160dpi -->
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/icon_nfc"
|
||||
android:src="@drawable/nfc_touch"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_description"
|
||||
android:text="@string/swap_nfc_description"
|
||||
style="@style/SwapTheme.Wizard.MainText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/icon_nfc" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_dont_show"
|
||||
android:text="Don't show this again"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
</RelativeLayout>
|
30
res/layout/swap_success_header.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
android:text="Swap success!"
|
||||
style="@style/SwapTheme.AppList.SwapSuccess"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/swap_success"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_description"
|
||||
android:text="Tap an app for details and to install."
|
||||
style="@style/SwapTheme.AppList.SwapSuccessDetails"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
</LinearLayout>
|
53
res/layout/swap_wifi_qr.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ScrollView
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/textView"
|
||||
android:text="One person needs to scan the code, or type the URL of the other swapper into a browser."
|
||||
style="@style/SwapTheme.Wizard.MainText"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="250dp"
|
||||
android:maxHeight="20dp"
|
||||
android:id="@+id/wifi_qr_code"
|
||||
tools:src="@drawable/swap_qr_example"/>
|
||||
|
||||
<!--
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:id="@+id/btn_not_working"
|
||||
android:text="@string/swap_wifi_qr_not_working"
|
||||
android:layout_alignParentBottom="true" />
|
||||
-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/device_ip_address"
|
||||
tools:text="http://255.255.255.255:8888"
|
||||
style="@style/SwapTheme.Wizard.LocalIpAddress"/>
|
||||
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:text="@string/open_qr_code_scanner"
|
||||
android:layout_gravity="center"
|
||||
android:id="@+id/btn_qr_scanner"/>
|
||||
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:id="@+id/btn_cancel_swap"
|
||||
android:text="@string/cancel" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -13,8 +13,8 @@
|
||||
android:title="@string/menu_update_repo"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_local_repo"
|
||||
android:title="@string/local_repo"
|
||||
android:id="@+id/action_swap"
|
||||
android:title="@string/swap"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/action_manage_repos"
|
||||
|
11
res/menu/swap_next.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_next"
|
||||
android:title="Next"
|
||||
android:titleCondensed="Next"/>
|
||||
|
||||
<!-- Currently in a style, but that style probably wont work on 8 -> 11 devices -->
|
||||
<!--android:drawable="@drawable/swap_action_button_skin"-->
|
||||
|
||||
</menu>
|
11
res/menu/swap_skip.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_next"
|
||||
android:title="Skip"
|
||||
android:titleCondensed="Skip"/>
|
||||
|
||||
<!-- Currently in a style, but that style probably wont work on 8 -> 11 devices -->
|
||||
<!--android:drawable="@drawable/swap_action_button_skin"-->
|
||||
|
||||
</menu>
|
@ -3,4 +3,16 @@
|
||||
<color name="signed">#ffcccccc</color>
|
||||
<color name="unsigned">#ffCC0000</color>
|
||||
<color name="unverified">#ff999999</color>
|
||||
|
||||
<color name="swap_light_blue">#27aae1</color>
|
||||
<color name="swap_light_blue_pressed">#ff98cce1</color>
|
||||
<color name="swap_blue">#1c6bbc</color>
|
||||
<color name="swap_blue_pressed">#ff6ca8d5</color>
|
||||
<color name="swap_confirm">#ff27aae1</color>
|
||||
<color name="swap_confirm_pressed">#ff2ed7eb</color>
|
||||
<color name="swap_deny">#ff21488c</color>
|
||||
<color name="swap_deny_pressed">#ff3096b9</color>
|
||||
<color name="swap_wifi_may_work">#fbb040</color>
|
||||
<color name="swap_wifi_likely_to_work">#00a14b</color>
|
||||
|
||||
</resources>
|
@ -167,10 +167,10 @@
|
||||
<string name="local_repo">Local Repo</string>
|
||||
<string name="local_repos_title">Local FDroid Repos</string>
|
||||
<string name="local_repos_scanning">Discovering local FDroid repos…</string>
|
||||
<string name="local_repo_running">Your local FDroid repo is accessible.</string>
|
||||
<string name="local_repo_running">F-Droid is ready to swap</string>
|
||||
<string name="waiting_for_ipaddress">waiting for IP address…</string>
|
||||
<string name="setup_repo">Setup Local Repo</string>
|
||||
<string name="touch_to_configure_local_repo">Touch to setup your local repo.</string>
|
||||
<string name="touch_to_configure_local_repo">Touch to view details and allow others to swap your apps.</string>
|
||||
<string name="touch_to_turn_on_local_repo">Touch to turn on your local repo.</string>
|
||||
<string name="touch_to_turn_off_local_repo">Touch to turn off your local repo.</string>
|
||||
<string name="updating">Updating…</string>
|
||||
@ -192,6 +192,7 @@
|
||||
<string name="same_wifi_instructions">To connect to other people\'s devices, make sure both devices are on the same WiFi network. Then either type the URL above into F-Droid, or scan this QR Code:</string>
|
||||
<string name="qr_code">QR Code</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="qr_content_description">QR Code of repo URL</string>
|
||||
<string name="qr_wizard_wifi_network_instructions">Scan this QR Code to connect to the same WiFi network as this device.</string>
|
||||
<string name="qr_wizard_download_instructions">Scan this QR Code to connect to the website for getting started.</string>
|
||||
@ -295,4 +296,16 @@
|
||||
<string name="system_permission_denied_body">This option is only available when F-Droid is installed as a system-app.</string>
|
||||
|
||||
<string name="app_description">F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device.</string>
|
||||
<string name="swap_nfc_description">If your friend has <b>F-Droid and NFC turned on</b> touch your phones together.</string>
|
||||
<string name="swap_join_same_wifi">Join the same Wifi as your friend</string>
|
||||
<string name="swap_use_bluetooth">Use Bluetooth instead</string>
|
||||
<string name="swap_wifi_help">Learn more about Wifi</string>
|
||||
<string name="menu_swap">Swap apps</string>
|
||||
<string name="swap">Swap apps</string>
|
||||
<string name="swap_no_wifi_network">No network yet</string>
|
||||
<string name="swap_view_available_networks">(Tap to open available networks)</string>
|
||||
<string name="swap_wifi_qr_not_working">It\'s not working</string>
|
||||
<string name="open_qr_code_scanner">Open QR Code Scanner</string>
|
||||
<string name="swap_welcome">Welcome to F-Droid!</string>
|
||||
<string name="swap_confirm_connect">Do you ant to get apps from %1$s now?</string>
|
||||
</resources>
|
||||
|
@ -33,4 +33,146 @@
|
||||
<!-- customizations that are not API-level specific go here. -->
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard" parent="AppThemeDark">
|
||||
<item name="android:windowBackground">@drawable/swap_window_background</item>
|
||||
<item name="android:actionBarStyle">@style/Widget.AppCompat.ActionBar.Solid</item>
|
||||
<item name="android:actionButtonStyle">@style/SwapTheme.Wizard.ActionButton</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.StartSwap" parent="AppThemeLightWithDarkActionBar">
|
||||
<item name="android:background">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.AppList" parent="AppThemeLightWithDarkActionBar">
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.AppList.ListItem" parent="AppThemeLightWithDarkActionBar">
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.StartSwap.StartButton">
|
||||
<item name="android:layout_marginLeft">9dp</item> <!-- 16px * 96dpi / 160dpi -->
|
||||
<item name="android:layout_marginRight">9dp</item> <!-- 16px * 96dpi / 160dpi -->
|
||||
<item name="android:layout_height">63.3dp</item> <!-- 113px * 96dpi / 160dpi -->
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
|
||||
<item name="android:textSize">18.5sp</item> <!-- 33px * 96dpi / 160dpi -->
|
||||
<item name="android:background">@drawable/swap_start_button_skin</item>
|
||||
<item name="android:textColor">#fff</item>
|
||||
<item name="android:drawableLeft">@drawable/ic_swap</item>
|
||||
<item name="android:paddingLeft">10dp</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.AppList.SwapSuccess">
|
||||
<item name="android:textAlignment">center</item>
|
||||
<item name="android:textSize">25.7sp</item> <!-- 46px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingTop">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.AppList.SwapSuccessDetails">
|
||||
<item name="android:textAlignment">center</item>
|
||||
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingTop">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingBottom">20.1dp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.StartSwap.MainText">
|
||||
<item name="android:textAlignment">center</item>
|
||||
<item name="android:textSize">20.1sp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingLeft">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingRight">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingTop">28dp</item> <!-- 50px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingBottom">16.8dp</item> <!-- 30px * 96dpi / 160dpi -->
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.Text">
|
||||
<item name="android:textAlignment">center</item>
|
||||
<item name="android:textColor">#fff</item>
|
||||
<item name="android:textColorPrimary">#fff</item>
|
||||
<item name="android:textColorSecondary">#fff</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ActionButton" parent="Widget.AppCompat.ActionButton">
|
||||
<item name="android:textAppearance">@style/SwapTheme.Wizard.Text</item>
|
||||
<item name="android:paddingLeft">10dp</item>
|
||||
<item name="android:paddingRight">10dp</item>
|
||||
<item name="android:paddingTop">5dp</item>
|
||||
<item name="android:paddingBottom">5dp</item>
|
||||
<item name="android:background">@drawable/swap_action_button_skin</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.MainText" parent="@style/SwapTheme.Wizard.Text">
|
||||
<item name="android:paddingLeft">40dp</item>
|
||||
<item name="android:paddingRight">40dp</item>
|
||||
<item name="android:paddingTop">39.3dp</item> <!-- 70px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingBottom">29.25dp</item> <!-- 52px * 96dpi / 160dpi -->
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.LocalIpAddress" parent="@style/SwapTheme.Wizard.Text">
|
||||
<item name="android:textSize">26.5sp</item> <!-- 58px * 96dpi / 160dpi = 32.5sp (ended up making a bit smaller, because longer addresses didn't fit well) -->
|
||||
<item name="android:paddingLeft">40dp</item>
|
||||
<item name="android:paddingRight">40dp</item>
|
||||
<item name="android:paddingTop">22.5dp</item> <!-- 40px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingBottom">42.5dp</item> <!-- 75px * 96dpi / 160dpi -->
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.WifiSSID" parent="@style/SwapTheme.Wizard.Text">
|
||||
<item name="android:textSize">26sp</item> <!-- 46px * 96dpi / 160dpi -->
|
||||
<item name="android:paddingLeft">40dp</item>
|
||||
<item name="android:paddingRight">40dp</item>
|
||||
<item name="android:paddingTop">20dp</item>
|
||||
<item name="android:paddingBottom">5dp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ButtonBase">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:padding">19dp</item> <!-- 34px * 96dpi / 160dpi -->
|
||||
<item name="android:textSize">20.25sp</item> <!-- 36px * 96dpi / 160dpi -->
|
||||
<item name="android:textColor">#fff</item>
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
Buttons down the bottom of the screen, which prompt the user for further
|
||||
info, or to change the process somehow (e.g. Use Bluetooth instead of Wifi).
|
||||
-->
|
||||
<style name="SwapTheme.Wizard.OptionButton" parent="SwapTheme.Wizard.ButtonBase">
|
||||
<item name="android:background">@drawable/swap_button_skin</item>
|
||||
<item name="android:layout_margin">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ReceiveSwap" parent="SwapTheme.Wizard">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
Buttons used to ask the user to confirm they want to receive a swap repo from someone
|
||||
-->
|
||||
<style name="SwapTheme.Wizard.ReceiveSwap.ButtonBase" parent="SwapTheme.Wizard.ButtonBase">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ReceiveSwap.Confirm" parent="SwapTheme.Wizard.ReceiveSwap.ButtonBase">
|
||||
<item name="android:background">@drawable/swap_confirm_button_skin</item>
|
||||
<item name="android:layout_marginLeft">7dp</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ReceiveSwap.Deny" parent="SwapTheme.Wizard.ReceiveSwap.ButtonBase">
|
||||
<item name="android:background">@drawable/swap_deny_button_skin</item>
|
||||
<item name="android:layout_marginRight">7dp</item>
|
||||
</style>
|
||||
|
||||
<style name="SwapTheme.Wizard.ReceiveSwap.MainText" parent="SwapTheme.Wizard.MainText">
|
||||
<item name="android:textSize">33.6sp</item> <!-- 60 * 96dpi / 160dpi -->
|
||||
<item name="android:textStyle">italic</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
506
src/com/google/zxing/integration/android/IntentIntegrator.java
Normal file
@ -0,0 +1,506 @@
|
||||
/*
|
||||
* Copyright 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.integration.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
|
||||
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
|
||||
* project's source code.</p>
|
||||
*
|
||||
* <h2>Initiating a barcode scan</h2>
|
||||
*
|
||||
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
|
||||
* for the result in your app.</p>
|
||||
*
|
||||
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
|
||||
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
|
||||
*
|
||||
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
|
||||
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
* if (scanResult != null) {
|
||||
* // handle scan result
|
||||
* }
|
||||
* // else continue with any other code you need in the method
|
||||
* ...
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This is where you will handle a scan result.</p>
|
||||
*
|
||||
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
|
||||
* integrator.initiateScan();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
|
||||
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
|
||||
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
|
||||
* method.</p>
|
||||
*
|
||||
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
|
||||
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
|
||||
* yes/no button labels can be changed.</p>
|
||||
*
|
||||
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
|
||||
* to invoke the scanner. This can be used to set additional options not directly exposed by this
|
||||
* simplified API.</p>
|
||||
*
|
||||
* <p>By default, this will only allow applications that are known to respond to this intent correctly
|
||||
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
|
||||
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
|
||||
*
|
||||
* <h2>Sharing text via barcode</h2>
|
||||
*
|
||||
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
|
||||
*
|
||||
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
|
||||
*
|
||||
* <h2>Enabling experimental barcode formats</h2>
|
||||
*
|
||||
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
|
||||
* PDF417. Use {@link #initiateScan(java.util.Collection)} with
|
||||
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
|
||||
* formats.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author Fred Lin
|
||||
* @author Isaac Potoczny-Jones
|
||||
* @author Brad Drehmer
|
||||
* @author gcstang
|
||||
*/
|
||||
public class IntentIntegrator {
|
||||
|
||||
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
|
||||
private static final String TAG = IntentIntegrator.class.getSimpleName();
|
||||
|
||||
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
|
||||
public static final String DEFAULT_MESSAGE =
|
||||
"This application requires Barcode Scanner. Would you like to install it?";
|
||||
public static final String DEFAULT_YES = "Yes";
|
||||
public static final String DEFAULT_NO = "No";
|
||||
|
||||
private static final String BS_PACKAGE = "com.google.zxing.client.android";
|
||||
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
|
||||
|
||||
// supported barcode formats
|
||||
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
|
||||
public static final Collection<String> ONE_D_CODE_TYPES =
|
||||
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
|
||||
"ITF", "RSS_14", "RSS_EXPANDED");
|
||||
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
|
||||
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
|
||||
|
||||
public static final Collection<String> ALL_CODE_TYPES = null;
|
||||
|
||||
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
|
||||
public static final List<String> TARGET_ALL_KNOWN = list(
|
||||
BSPLUS_PACKAGE, // Barcode Scanner+
|
||||
BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
|
||||
BS_PACKAGE // Barcode Scanner
|
||||
// What else supports this intent?
|
||||
);
|
||||
|
||||
private final Activity activity;
|
||||
private final Fragment fragment;
|
||||
|
||||
private String title;
|
||||
private String message;
|
||||
private String buttonYes;
|
||||
private String buttonNo;
|
||||
private List<String> targetApplications;
|
||||
private final Map<String,Object> moreExtras = new HashMap<String,Object>(3);
|
||||
|
||||
/**
|
||||
* @param activity {@link Activity} invoking the integration
|
||||
*/
|
||||
public IntentIntegrator(Activity activity) {
|
||||
this.activity = activity;
|
||||
this.fragment = null;
|
||||
initializeConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fragment {@link Fragment} invoking the integration.
|
||||
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
|
||||
* of an {@link Activity}
|
||||
*/
|
||||
public IntentIntegrator(Fragment fragment) {
|
||||
this.activity = fragment.getActivity();
|
||||
this.fragment = fragment;
|
||||
initializeConfiguration();
|
||||
}
|
||||
|
||||
private void initializeConfiguration() {
|
||||
title = DEFAULT_TITLE;
|
||||
message = DEFAULT_MESSAGE;
|
||||
buttonYes = DEFAULT_YES;
|
||||
buttonNo = DEFAULT_NO;
|
||||
targetApplications = TARGET_ALL_KNOWN;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void setTitleByID(int titleID) {
|
||||
title = activity.getString(titleID);
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void setMessageByID(int messageID) {
|
||||
message = activity.getString(messageID);
|
||||
}
|
||||
|
||||
public String getButtonYes() {
|
||||
return buttonYes;
|
||||
}
|
||||
|
||||
public void setButtonYes(String buttonYes) {
|
||||
this.buttonYes = buttonYes;
|
||||
}
|
||||
|
||||
public void setButtonYesByID(int buttonYesID) {
|
||||
buttonYes = activity.getString(buttonYesID);
|
||||
}
|
||||
|
||||
public String getButtonNo() {
|
||||
return buttonNo;
|
||||
}
|
||||
|
||||
public void setButtonNo(String buttonNo) {
|
||||
this.buttonNo = buttonNo;
|
||||
}
|
||||
|
||||
public void setButtonNoByID(int buttonNoID) {
|
||||
buttonNo = activity.getString(buttonNoID);
|
||||
}
|
||||
|
||||
public Collection<String> getTargetApplications() {
|
||||
return targetApplications;
|
||||
}
|
||||
|
||||
public final void setTargetApplications(List<String> targetApplications) {
|
||||
if (targetApplications.isEmpty()) {
|
||||
throw new IllegalArgumentException("No target applications");
|
||||
}
|
||||
this.targetApplications = targetApplications;
|
||||
}
|
||||
|
||||
public void setSingleTargetApplication(String targetApplication) {
|
||||
this.targetApplications = Collections.singletonList(targetApplication);
|
||||
}
|
||||
|
||||
public Map<String,?> getMoreExtras() {
|
||||
return moreExtras;
|
||||
}
|
||||
|
||||
public final void addExtra(String key, Object value) {
|
||||
moreExtras.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan for all known barcode types with the default camera.
|
||||
*
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan() {
|
||||
return initiateScan(ALL_CODE_TYPES, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan for all known barcode types with the specified camera.
|
||||
*
|
||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan(int cameraId) {
|
||||
return initiateScan(ALL_CODE_TYPES, cameraId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
|
||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
||||
*
|
||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise.
|
||||
*/
|
||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
|
||||
return initiateScan(desiredBarcodeFormats, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
|
||||
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
|
||||
* like {@link #PRODUCT_CODE_TYPES} for example.
|
||||
*
|
||||
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
|
||||
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
*/
|
||||
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
|
||||
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
|
||||
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
|
||||
// check which types of codes to scan for
|
||||
if (desiredBarcodeFormats != null) {
|
||||
// set the desired barcode types
|
||||
StringBuilder joinedByComma = new StringBuilder();
|
||||
for (String format : desiredBarcodeFormats) {
|
||||
if (joinedByComma.length() > 0) {
|
||||
joinedByComma.append(',');
|
||||
}
|
||||
joinedByComma.append(format);
|
||||
}
|
||||
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
|
||||
}
|
||||
|
||||
// check requested camera ID
|
||||
if (cameraId >= 0) {
|
||||
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
|
||||
}
|
||||
|
||||
String targetAppPackage = findTargetAppPackage(intentScan);
|
||||
if (targetAppPackage == null) {
|
||||
return showDownloadDialog();
|
||||
}
|
||||
intentScan.setPackage(targetAppPackage);
|
||||
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
attachMoreExtras(intentScan);
|
||||
startActivityForResult(intentScan, REQUEST_CODE);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an activity. This method is defined to allow different methods of activity starting for
|
||||
* newer versions of Android and for compatibility library.
|
||||
*
|
||||
* @param intent Intent to start.
|
||||
* @param code Request code for the activity
|
||||
* @see android.app.Activity#startActivityForResult(Intent, int)
|
||||
* @see android.app.Fragment#startActivityForResult(Intent, int)
|
||||
*/
|
||||
protected void startActivityForResult(Intent intent, int code) {
|
||||
if (fragment == null) {
|
||||
activity.startActivityForResult(intent, code);
|
||||
} else {
|
||||
fragment.startActivityForResult(intent, code);
|
||||
}
|
||||
}
|
||||
|
||||
private String findTargetAppPackage(Intent intent) {
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (availableApps != null) {
|
||||
for (String targetApp : targetApplications) {
|
||||
if (contains(availableApps, targetApp)) {
|
||||
return targetApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
|
||||
for (ResolveInfo availableApp : availableApps) {
|
||||
String packageName = availableApp.activityInfo.packageName;
|
||||
if (targetApp.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AlertDialog showDownloadDialog() {
|
||||
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
|
||||
downloadDialog.setTitle(title);
|
||||
downloadDialog.setMessage(message);
|
||||
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
String packageName;
|
||||
if (targetApplications.contains(BS_PACKAGE)) {
|
||||
// Prefer to suggest download of BS if it's anywhere in the list
|
||||
packageName = BS_PACKAGE;
|
||||
} else {
|
||||
// Otherwise, first option:
|
||||
packageName = targetApplications.get(0);
|
||||
}
|
||||
Uri uri = Uri.parse("market://details?id=" + packageName);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
try {
|
||||
if (fragment == null) {
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
// Hmm, market is not installed
|
||||
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
downloadDialog.setNegativeButton(buttonNo, null);
|
||||
downloadDialog.setCancelable(true);
|
||||
return downloadDialog.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Call this from your {@link Activity}'s
|
||||
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
|
||||
*
|
||||
* @param requestCode request code from {@code onActivityResult()}
|
||||
* @param resultCode result code from {@code onActivityResult()}
|
||||
* @param intent {@link Intent} from {@code onActivityResult()}
|
||||
* @return null if the event handled here was not related to this class, or
|
||||
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
|
||||
* the fields will be null.
|
||||
*/
|
||||
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
if (requestCode == REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
String contents = intent.getStringExtra("SCAN_RESULT");
|
||||
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
|
||||
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
|
||||
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
|
||||
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
|
||||
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
|
||||
return new IntentResult(contents,
|
||||
formatName,
|
||||
rawBytes,
|
||||
orientation,
|
||||
errorCorrectionLevel);
|
||||
}
|
||||
return new IntentResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defaults to type "TEXT_TYPE".
|
||||
*
|
||||
* @param text the text string to encode as a barcode
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
* @see #shareText(CharSequence, CharSequence)
|
||||
*/
|
||||
public final AlertDialog shareText(CharSequence text) {
|
||||
return shareText(text, "TEXT_TYPE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the given text by encoding it as a barcode, such that another user can
|
||||
* scan the text off the screen of the device.
|
||||
*
|
||||
* @param text the text string to encode as a barcode
|
||||
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
|
||||
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
|
||||
* if a prompt was needed, or null otherwise
|
||||
*/
|
||||
public final AlertDialog shareText(CharSequence text, CharSequence type) {
|
||||
Intent intent = new Intent();
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.setAction(BS_PACKAGE + ".ENCODE");
|
||||
intent.putExtra("ENCODE_TYPE", type);
|
||||
intent.putExtra("ENCODE_DATA", text);
|
||||
String targetAppPackage = findTargetAppPackage(intent);
|
||||
if (targetAppPackage == null) {
|
||||
return showDownloadDialog();
|
||||
}
|
||||
intent.setPackage(targetAppPackage);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
attachMoreExtras(intent);
|
||||
if (fragment == null) {
|
||||
activity.startActivity(intent);
|
||||
} else {
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<String> list(String... values) {
|
||||
return Collections.unmodifiableList(Arrays.asList(values));
|
||||
}
|
||||
|
||||
private void attachMoreExtras(Intent intent) {
|
||||
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
// Kind of hacky
|
||||
if (value instanceof Integer) {
|
||||
intent.putExtra(key, (Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
intent.putExtra(key, (Long) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
intent.putExtra(key, (Boolean) value);
|
||||
} else if (value instanceof Double) {
|
||||
intent.putExtra(key, (Double) value);
|
||||
} else if (value instanceof Float) {
|
||||
intent.putExtra(key, (Float) value);
|
||||
} else if (value instanceof Bundle) {
|
||||
intent.putExtra(key, (Bundle) value);
|
||||
} else {
|
||||
intent.putExtra(key, value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
95
src/com/google/zxing/integration/android/IntentResult.java
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2009 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.integration.android;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class IntentResult {
|
||||
|
||||
private final String contents;
|
||||
private final String formatName;
|
||||
private final byte[] rawBytes;
|
||||
private final Integer orientation;
|
||||
private final String errorCorrectionLevel;
|
||||
|
||||
IntentResult() {
|
||||
this(null, null, null, null, null);
|
||||
}
|
||||
|
||||
IntentResult(String contents,
|
||||
String formatName,
|
||||
byte[] rawBytes,
|
||||
Integer orientation,
|
||||
String errorCorrectionLevel) {
|
||||
this.contents = contents;
|
||||
this.formatName = formatName;
|
||||
this.rawBytes = rawBytes;
|
||||
this.orientation = orientation;
|
||||
this.errorCorrectionLevel = errorCorrectionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw content of barcode
|
||||
*/
|
||||
public String getContents() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
|
||||
*/
|
||||
public String getFormatName() {
|
||||
return formatName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw bytes of the barcode content, if applicable, or null otherwise
|
||||
*/
|
||||
public byte[] getRawBytes() {
|
||||
return rawBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
|
||||
*/
|
||||
public Integer getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return name of the error correction level used in the barcode, if applicable
|
||||
*/
|
||||
public String getErrorCorrectionLevel() {
|
||||
return errorCorrectionLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder dialogText = new StringBuilder(100);
|
||||
dialogText.append("Format: ").append(formatName).append('\n');
|
||||
dialogText.append("Contents: ").append(contents).append('\n');
|
||||
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
|
||||
dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n");
|
||||
dialogText.append("Orientation: ").append(orientation).append('\n');
|
||||
dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n');
|
||||
return dialogText.toString();
|
||||
}
|
||||
|
||||
}
|
@ -1327,10 +1327,10 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
|
||||
TextView statusView = (TextView) view.findViewById(R.id.status);
|
||||
if (getApp().isInstalled()) {
|
||||
statusView.setText(getString(R.string.details_installed, getApp().installedVersionName));
|
||||
NfcBeamManager.setAndroidBeam(getActivity(), getApp().id);
|
||||
NfcHelper.setAndroidBeam(getActivity(), getApp().id);
|
||||
} else {
|
||||
statusView.setText(getString(R.string.details_notinstalled));
|
||||
NfcBeamManager.disableAndroidBeam(getActivity());
|
||||
NfcHelper.disableAndroidBeam(getActivity());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,12 +41,15 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import android.widget.Toast;
|
||||
import org.fdroid.fdroid.compat.TabManager;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.views.AppListFragmentPagerAdapter;
|
||||
import org.fdroid.fdroid.views.LocalRepoActivity;
|
||||
import org.fdroid.fdroid.views.ManageReposActivity;
|
||||
import org.fdroid.fdroid.views.swap.ConnectSwapActivity;
|
||||
import org.fdroid.fdroid.views.swap.SwapActivity;
|
||||
|
||||
public class FDroid extends ActionBarActivity {
|
||||
|
||||
@ -54,9 +57,12 @@ public class FDroid extends ActionBarActivity {
|
||||
public static final int REQUEST_MANAGEREPOS = 1;
|
||||
public static final int REQUEST_PREFS = 2;
|
||||
public static final int REQUEST_ENABLE_BLUETOOTH = 3;
|
||||
public static final int REQUEST_SWAP = 4;
|
||||
|
||||
public static final String EXTRA_TAB_UPDATE = "extraTab";
|
||||
|
||||
public static final String ACTION_ADD_REPO = "org.fdroid.fdroid.FDroid.ACTION_ADD_REPO";
|
||||
|
||||
private FDroidApp fdroidApp = null;
|
||||
|
||||
private ViewPager viewPager;
|
||||
@ -106,7 +112,26 @@ public class FDroid extends ActionBarActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// AppDetails and RepoDetailsActivity set different NFC actions, so reset here
|
||||
NfcBeamManager.setAndroidBeam(this, getApplication().getPackageName());
|
||||
NfcHelper.setAndroidBeam(this, getApplication().getPackageName());
|
||||
checkForAddRepoIntent();
|
||||
}
|
||||
|
||||
private void checkForAddRepoIntent() {
|
||||
// Don't handle the intent after coming back to this view (e.g. after hitting the back button)
|
||||
// http://stackoverflow.com/a/14820849
|
||||
if (!getIntent().hasExtra("handled")) {
|
||||
NewRepoConfig parser = new NewRepoConfig(this, getIntent());
|
||||
if (parser.isValidRepo()) {
|
||||
getIntent().putExtra("handled", true);
|
||||
if (parser.isFromSwap()) {
|
||||
startActivityForResult(new Intent(ACTION_ADD_REPO, getIntent().getData(), this, ConnectSwapActivity.class), REQUEST_SWAP);
|
||||
} else {
|
||||
startActivity(new Intent(ACTION_ADD_REPO, getIntent().getData(), this, ManageReposActivity.class));
|
||||
}
|
||||
} else if (parser.getErrorMessage() != null) {
|
||||
Toast.makeText(this, parser.getErrorMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -145,8 +170,8 @@ public class FDroid extends ActionBarActivity {
|
||||
startActivityForResult(prefs, REQUEST_PREFS);
|
||||
return true;
|
||||
|
||||
case R.id.action_local_repo:
|
||||
startActivity(new Intent(this, LocalRepoActivity.class));
|
||||
case R.id.action_swap:
|
||||
startActivity(new Intent(this, SwapActivity.class));
|
||||
return true;
|
||||
|
||||
case R.id.action_search:
|
||||
|
@ -41,13 +41,11 @@ import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
|
||||
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
import com.nostra13.universalimageloader.utils.StorageUtils;
|
||||
|
||||
import org.fdroid.fdroid.Preferences.ChangeListener;
|
||||
import org.fdroid.fdroid.compat.PRNGFixes;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
@ -57,6 +55,9 @@ import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||
import org.fdroid.fdroid.net.IconDownloader;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
@ -73,6 +74,8 @@ public class FDroidApp extends Application {
|
||||
private static Messenger localRepoServiceMessenger = null;
|
||||
private static boolean localRepoServiceIsBound = false;
|
||||
|
||||
private static final String TAG = "org.fdroid.fdroid.FDroidApp";
|
||||
|
||||
BluetoothAdapter bluetoothAdapter = null;
|
||||
|
||||
public static enum Theme {
|
||||
@ -266,8 +269,7 @@ public class FDroidApp extends Application {
|
||||
if (!localRepoServiceIsBound) {
|
||||
Context app = context.getApplicationContext();
|
||||
Intent service = new Intent(app, LocalRepoService.class);
|
||||
localRepoServiceIsBound = app.bindService(service, serviceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
localRepoServiceIsBound = app.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
if (localRepoServiceIsBound)
|
||||
app.startService(service);
|
||||
}
|
||||
@ -285,8 +287,7 @@ public class FDroidApp extends Application {
|
||||
public static void restartLocalRepoService() {
|
||||
if (localRepoServiceMessenger != null) {
|
||||
try {
|
||||
Message msg = Message.obtain(null,
|
||||
LocalRepoService.RESTART, LocalRepoService.RESTART, 0);
|
||||
Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0);
|
||||
localRepoServiceMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
@ -294,7 +295,7 @@ public class FDroidApp extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLocalRepoServiceRunnig() {
|
||||
public static boolean isLocalRepoServiceRunning() {
|
||||
return localRepoServiceIsBound;
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,44 @@ package org.fdroid.fdroid;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
|
||||
@TargetApi(16)
|
||||
public class NfcBeamManager {
|
||||
public class NfcHelper {
|
||||
|
||||
@TargetApi(14)
|
||||
private static NfcAdapter getAdapter(Context context) {
|
||||
if (Build.VERSION.SDK_INT < 14)
|
||||
return null;
|
||||
|
||||
return NfcAdapter.getDefaultAdapter(context.getApplicationContext());
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
public static boolean setPushMessage(Activity activity, Uri toShare) {
|
||||
NfcAdapter adapter = getAdapter(activity);
|
||||
if (adapter != null) {
|
||||
adapter.setNdefPushMessage(new NdefMessage(new NdefRecord[]{
|
||||
NdefRecord.createUri(toShare),
|
||||
}), activity);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
static void setAndroidBeam(Activity activity, String packageName) {
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
return;
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
|
||||
NfcAdapter nfcAdapter = getAdapter(activity);
|
||||
if (nfcAdapter != null) {
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
@ -32,10 +55,11 @@ public class NfcBeamManager {
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
static void disableAndroidBeam(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT < 16)
|
||||
return;
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
|
||||
NfcAdapter nfcAdapter = getAdapter(activity);
|
||||
if (nfcAdapter != null)
|
||||
nfcAdapter.setBeamPushUris(null, activity);
|
||||
}
|
@ -56,6 +56,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
public static final String PREF_ENABLE_PROXY = "enableProxy";
|
||||
public static final String PREF_PROXY_HOST = "proxyHost";
|
||||
public static final String PREF_PROXY_PORT = "proxyPort";
|
||||
public static final String PREF_SHOW_NFC_DURING_SWAP = "showNfcDuringSwap";
|
||||
|
||||
private static final boolean DEFAULT_COMPACT_LAYOUT = false;
|
||||
private static final boolean DEFAULT_ROOTED = true;
|
||||
@ -70,6 +71,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
private static final boolean DEFAULT_ENABLE_PROXY = false;
|
||||
public static final String DEFAULT_PROXY_HOST = "127.0.0.1";
|
||||
public static final int DEFAULT_PROXY_PORT = 8118;
|
||||
public static final boolean DEFAULT_SHOW_NFC_DURING_SWAP = true;
|
||||
|
||||
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
|
||||
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
|
||||
@ -115,6 +117,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
|
||||
return preferences.getBoolean(PREF_PERMISSIONS, DEFAULT_PERMISSIONS);
|
||||
}
|
||||
|
||||
public boolean showNfcDuringSwap() {
|
||||
return preferences.getBoolean(PREF_SHOW_NFC_DURING_SWAP, DEFAULT_SHOW_NFC_DURING_SWAP);
|
||||
}
|
||||
|
||||
public void setShowNfcDuringSwap(boolean show) {
|
||||
preferences.edit().putBoolean(PREF_SHOW_NFC_DURING_SWAP, show).commit();
|
||||
}
|
||||
|
||||
public boolean expertMode() {
|
||||
return preferences.getBoolean(PREF_EXPERT, DEFAULT_EXPERT);
|
||||
}
|
||||
|
@ -70,6 +70,11 @@ public class QrGenAsyncTask extends AsyncTask<String, Void, Void> {
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
ImageView qrCodeImageView = (ImageView) activity.findViewById(viewId);
|
||||
qrCodeImageView.setImageBitmap(qrBitmap);
|
||||
|
||||
// If the generation takes too long for whatever reason, then this view, and indeed the entire
|
||||
// activity may not be around any more.
|
||||
if (qrCodeImageView != null) {
|
||||
qrCodeImageView.setImageBitmap(qrBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,6 @@ import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import com.nostra13.universalimageloader.utils.StorageUtils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.xml.sax.XMLReader;
|
||||
@ -297,6 +293,7 @@ public final class Utils {
|
||||
return Uri.parse("http://wifi-not-enabled");
|
||||
Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo"));
|
||||
Uri.Builder b = uri.buildUpon();
|
||||
b.appendQueryParameter("swap", "1");
|
||||
if (!TextUtils.isEmpty(repo.fingerprint))
|
||||
b.appendQueryParameter("fingerprint", repo.fingerprint);
|
||||
if (!TextUtils.isEmpty(FDroidApp.bssid)) {
|
||||
|
@ -1,61 +0,0 @@
|
||||
package org.fdroid.fdroid.compat;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
public abstract class ContextCompat extends Compatibility {
|
||||
|
||||
public static ContextCompat create(Context context) {
|
||||
if (hasApi(8)) {
|
||||
return new FroyoContextCompatImpl(context);
|
||||
} else {
|
||||
return new OldContextCompatImpl(context);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Context context;
|
||||
|
||||
public ContextCompat(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see android.content.Context#getExternalCacheDir()
|
||||
*/
|
||||
public abstract File getExternalCacheDir();
|
||||
|
||||
}
|
||||
|
||||
class OldContextCompatImpl extends ContextCompat {
|
||||
|
||||
public OldContextCompatImpl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getExternalCacheDir() {
|
||||
File file = new File(Environment.getExternalStorageDirectory(),
|
||||
"Android/data/org.fdroid.fdroid/cache");
|
||||
if (!file.exists())
|
||||
file.mkdirs();
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(8)
|
||||
class FroyoContextCompatImpl extends ContextCompat {
|
||||
|
||||
public FroyoContextCompatImpl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getExternalCacheDir() {
|
||||
return context.getExternalCacheDir();
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,6 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -14,8 +13,10 @@ import java.util.Locale;
|
||||
|
||||
public class NewRepoConfig {
|
||||
|
||||
private static final String TAG = "org.fdroid.fdroid.data.NewRepoConfig";
|
||||
|
||||
private String errorMessage;
|
||||
private boolean isValidRepo;
|
||||
private boolean isValidRepo = false;
|
||||
|
||||
private String uriString;
|
||||
private Uri uri;
|
||||
@ -25,22 +26,32 @@ public class NewRepoConfig {
|
||||
private String fingerprint;
|
||||
private String bssid;
|
||||
private String ssid;
|
||||
private boolean fromSwap;
|
||||
|
||||
public NewRepoConfig(Context context, String uri) {
|
||||
init(context, uri != null ? Uri.parse(uri) : null);
|
||||
}
|
||||
|
||||
public NewRepoConfig(Context context, Intent intent) {
|
||||
init(context, intent.getData());
|
||||
}
|
||||
|
||||
private void init(Context context, Uri incomingUri) {
|
||||
/* an URL from a click, NFC, QRCode scan, etc */
|
||||
uri = intent.getData();
|
||||
uri = incomingUri;
|
||||
if (uri == null) {
|
||||
isValidRepo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Parsing incoming intent looking for repo: " + incomingUri);
|
||||
|
||||
// scheme and host should only ever be pure ASCII aka Locale.ENGLISH
|
||||
scheme = intent.getScheme();
|
||||
scheme = uri.getScheme();
|
||||
host = uri.getHost();
|
||||
port = uri.getPort();
|
||||
if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) {
|
||||
errorMessage = String.format(context.getString(R.string.malformed_repo_uri),
|
||||
uri);
|
||||
errorMessage = String.format(context.getString(R.string.malformed_repo_uri), uri);
|
||||
isValidRepo = false;
|
||||
return;
|
||||
}
|
||||
@ -69,13 +80,16 @@ public class NewRepoConfig {
|
||||
fingerprint = uri.getQueryParameter("fingerprint");
|
||||
bssid = uri.getQueryParameter("bssid");
|
||||
ssid = uri.getQueryParameter("ssid");
|
||||
fromSwap = uri.getQueryParameter("swap") != null;
|
||||
|
||||
Log.i("RepoListFragment", "onCreate " + fingerprint);
|
||||
if (Arrays.asList("fdroidrepos", "fdroidrepo", "https", "http").contains(scheme)) {
|
||||
uriString = sanitizeRepoUri(uri);
|
||||
if (!Arrays.asList("fdroidrepos", "fdroidrepo", "https", "http").contains(scheme)) {
|
||||
isValidRepo = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isValidRepo = true;
|
||||
uriString = sanitizeRepoUri(uri);
|
||||
isValidRepo = true;
|
||||
|
||||
}
|
||||
|
||||
public String getBssid() {
|
||||
@ -94,6 +108,10 @@ public class NewRepoConfig {
|
||||
return uriString;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
@ -110,6 +128,10 @@ public class NewRepoConfig {
|
||||
return isValidRepo;
|
||||
}
|
||||
|
||||
public boolean isFromSwap() {
|
||||
return fromSwap;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port starts out as 8888, but if there is a conflict, it will be
|
||||
* incremented until there is a free port found.
|
||||
|
@ -20,6 +20,12 @@ public class RepoProvider extends FDroidProvider {
|
||||
|
||||
private Helper() {}
|
||||
|
||||
public static Repo findByUri(Context context, Uri uri) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Cursor cursor = resolver.query(uri, DataColumns.ALL, null, null, null);
|
||||
return cursorToRepo(cursor);
|
||||
}
|
||||
|
||||
public static Repo findById(Context context, long repoId) {
|
||||
return findById(context, repoId, DataColumns.ALL);
|
||||
}
|
||||
@ -29,15 +35,7 @@ public class RepoProvider extends FDroidProvider {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = RepoProvider.getContentUri(repoId);
|
||||
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
Repo repo = null;
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
repo = new Repo(cursor);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return repo;
|
||||
return cursorToRepo(cursor);
|
||||
}
|
||||
|
||||
public static Repo findByAddress(Context context, String address) {
|
||||
@ -90,6 +88,18 @@ public class RepoProvider extends FDroidProvider {
|
||||
return repos;
|
||||
}
|
||||
|
||||
private static Repo cursorToRepo(Cursor cursor) {
|
||||
Repo repo = null;
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
repo = new Repo(cursor);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return repo;
|
||||
}
|
||||
|
||||
public static void update(Context context, Repo repo,
|
||||
ContentValues values) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
@ -152,11 +162,11 @@ public class RepoProvider extends FDroidProvider {
|
||||
* resolver, but I thought I'd put it here in the interests of having
|
||||
* each of the CRUD methods available in the helper class.
|
||||
*/
|
||||
public static void insert(Context context,
|
||||
public static Uri insert(Context context,
|
||||
ContentValues values) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = RepoProvider.getContentUri();
|
||||
resolver.insert(uri, values);
|
||||
return resolver.insert(uri, values);
|
||||
}
|
||||
|
||||
public static void remove(Context context, long repoId) {
|
||||
|
@ -28,6 +28,14 @@ import org.fdroid.fdroid.data.App;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
@ -50,15 +58,6 @@ import java.util.Map.Entry;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
public class LocalRepoManager {
|
||||
private static final String TAG = "LocalRepoManager";
|
||||
|
||||
@ -74,6 +73,12 @@ public class LocalRepoManager {
|
||||
private String ipAddressString = "UNSET";
|
||||
private String uriString = "UNSET";
|
||||
|
||||
private static String[] WEB_ROOT_ASSET_FILES = {
|
||||
"swap-icon.png",
|
||||
"swap-tick-done.png",
|
||||
"swap-tick-not-done.png"
|
||||
};
|
||||
|
||||
private Map<String, App> apps = new HashMap<String, App>();
|
||||
|
||||
public final File xmlIndex;
|
||||
@ -125,7 +130,7 @@ public class LocalRepoManager {
|
||||
this.uriString = uriString;
|
||||
}
|
||||
|
||||
private String writeFdroidApkToWebroot(String repoAddress) {
|
||||
private String writeFdroidApkToWebroot() {
|
||||
ApplicationInfo appInfo;
|
||||
String fdroidClientURL = "https://f-droid.org/FDroid.apk";
|
||||
|
||||
@ -143,7 +148,7 @@ public class LocalRepoManager {
|
||||
}
|
||||
|
||||
public void writeIndexPage(String repoAddress) {
|
||||
final String fdroidClientURL = writeFdroidApkToWebroot(repoAddress);
|
||||
final String fdroidClientURL = writeFdroidApkToWebroot();
|
||||
try {
|
||||
File indexHtml = new File(webRoot, "index.html");
|
||||
BufferedReader in = new BufferedReader(
|
||||
@ -159,30 +164,43 @@ public class LocalRepoManager {
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
|
||||
for (String file : WEB_ROOT_ASSET_FILES) {
|
||||
Utils.copy(assetManager.open(file), new FileOutputStream(new File(webRoot, file)));
|
||||
}
|
||||
|
||||
// make symlinks/copies in each subdir of the repo to make sure that
|
||||
// the user will always find the bootstrap page.
|
||||
File fdroidDirIndex = new File(fdroidDir, "index.html");
|
||||
fdroidDirIndex.delete();
|
||||
Utils.symlinkOrCopyFile(new File("../index.html"), fdroidDirIndex);
|
||||
File repoDirIndex = new File(repoDir, "index.html");
|
||||
repoDirIndex.delete();
|
||||
Utils.symlinkOrCopyFile(new File("../../index.html"), repoDirIndex);
|
||||
symlinkIndexPageElsewhere("../", fdroidDir);
|
||||
symlinkIndexPageElsewhere("../../", repoDir);
|
||||
|
||||
// add in /FDROID/REPO to support bad QR Scanner apps
|
||||
File fdroidCAPS = new File(fdroidDir.getParentFile(), "FDROID");
|
||||
fdroidCAPS.mkdir();
|
||||
|
||||
File repoCAPS = new File(fdroidCAPS, "REPO");
|
||||
repoCAPS.mkdir();
|
||||
File fdroidCAPSIndex = new File(fdroidCAPS, "index.html");
|
||||
fdroidCAPSIndex.delete();
|
||||
Utils.symlinkOrCopyFile(new File("../index.html"), fdroidCAPSIndex);
|
||||
File repoCAPSIndex = new File(repoCAPS, "index.html");
|
||||
repoCAPSIndex.delete();
|
||||
Utils.symlinkOrCopyFile(new File("../../index.html"), repoCAPSIndex);
|
||||
|
||||
symlinkIndexPageElsewhere("../", fdroidCAPS);
|
||||
symlinkIndexPageElsewhere("../../", repoCAPS);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void symlinkIndexPageElsewhere(String symlinkPrefix, File directory) {
|
||||
File index = new File(directory, "index.html");
|
||||
index.delete();
|
||||
Utils.symlinkOrCopyFile(new File(symlinkPrefix + "index.html"), index);
|
||||
|
||||
for(String fileName : WEB_ROOT_ASSET_FILES) {
|
||||
File file = new File(directory, fileName);
|
||||
file.delete();
|
||||
Utils.symlinkOrCopyFile(new File(symlinkPrefix + fileName), file);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteContents(File path) {
|
||||
if (path.exists()) {
|
||||
for (File file : path.listFiles()) {
|
||||
@ -225,7 +243,7 @@ public class LocalRepoManager {
|
||||
public void addApp(Context context, String packageName) {
|
||||
App app;
|
||||
try {
|
||||
app = new App(context, pm, packageName);
|
||||
app = new App(context.getApplicationContext(), pm, packageName);
|
||||
if (!app.isValid())
|
||||
return;
|
||||
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
|
||||
|
@ -2,27 +2,38 @@
|
||||
package org.fdroid.fdroid.localrepo;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.os.*;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.*;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.Preferences.ChangeListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
import org.fdroid.fdroid.views.LocalRepoActivity;
|
||||
import org.fdroid.fdroid.views.swap.SwapActivity;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.BindException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
public class LocalRepoService extends Service {
|
||||
private static final String TAG = "LocalRepoService";
|
||||
|
||||
@ -47,25 +58,35 @@ public class LocalRepoService extends Service {
|
||||
|
||||
final Messenger messenger = new Messenger(new StartStopHandler(this));
|
||||
|
||||
/**
|
||||
* This is most likely going to be created on the UI thread, hence all of
|
||||
* the message handling will take place on a new thread to prevent blocking
|
||||
* the UI.
|
||||
*/
|
||||
static class StartStopHandler extends Handler {
|
||||
private static LocalRepoService service;
|
||||
|
||||
private final LocalRepoService service;
|
||||
|
||||
public StartStopHandler(LocalRepoService service) {
|
||||
StartStopHandler.service = service;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.arg1 == START) {
|
||||
service.startNetworkServices();
|
||||
} else if (msg.arg1 == STOP) {
|
||||
service.stopNetworkServices();
|
||||
} else if (msg.arg1 == RESTART) {
|
||||
service.stopNetworkServices();
|
||||
service.startNetworkServices();
|
||||
} else {
|
||||
Log.e(TAG, "unsupported msg.arg1, ignored");
|
||||
}
|
||||
public void handleMessage(final Message msg) {
|
||||
new Thread() {
|
||||
public void run() {
|
||||
if (msg.arg1 == START) {
|
||||
service.startNetworkServices();
|
||||
} else if (msg.arg1 == STOP) {
|
||||
service.stopNetworkServices();
|
||||
} else if (msg.arg1 == RESTART) {
|
||||
service.stopNetworkServices();
|
||||
service.startNetworkServices();
|
||||
} else {
|
||||
Log.e(TAG, "Unsupported msg.arg1 (" + msg.arg1 + "), ignored");
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,21 +126,24 @@ public class LocalRepoService extends Service {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
private void showNotification() {
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
// launch LocalRepoActivity if the user selects this notification
|
||||
Intent intent = new Intent(this, LocalRepoActivity.class);
|
||||
Intent intent = new Intent(this, SwapActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
notification = new NotificationCompat.Builder(this)
|
||||
.setContentTitle(getText(R.string.local_repo_running))
|
||||
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setSmallIcon(R.drawable.ic_swap)
|
||||
.setContentIntent(contentIntent)
|
||||
.build();
|
||||
startForeground(NOTIFICATION, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
showNotification();
|
||||
startNetworkServices();
|
||||
Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
||||
|
||||
@ -136,7 +160,12 @@ public class LocalRepoService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopNetworkServices();
|
||||
new Thread() {
|
||||
public void run() {
|
||||
stopNetworkServices();
|
||||
}
|
||||
}.start();
|
||||
|
||||
notificationManager.cancel(NOTIFICATION);
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
||||
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
||||
@ -148,6 +177,7 @@ public class LocalRepoService extends Service {
|
||||
}
|
||||
|
||||
private void startNetworkServices() {
|
||||
Log.d(TAG, "Starting local repo network services");
|
||||
startWebServer();
|
||||
if (Preferences.get().isLocalRepoBonjourEnabled())
|
||||
registerMDNSService();
|
||||
@ -155,8 +185,13 @@ public class LocalRepoService extends Service {
|
||||
}
|
||||
|
||||
private void stopNetworkServices() {
|
||||
Log.d(TAG, "Stopping local repo network services");
|
||||
Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener);
|
||||
|
||||
Log.d(TAG, "Unregistering MDNS service...");
|
||||
unregisterMDNSService();
|
||||
|
||||
Log.d(TAG, "Stopping web server...");
|
||||
stopWebServer();
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ import java.util.jar.JarFile;
|
||||
|
||||
public class SignedRepoUpdater extends RepoUpdater {
|
||||
|
||||
private static final String TAG = "org.fdroid.fdroid.updater.SignedRepoUpdater";
|
||||
|
||||
public SignedRepoUpdater(Context ctx, Repo repo) {
|
||||
super(ctx, repo);
|
||||
}
|
||||
@ -25,15 +27,22 @@ public class SignedRepoUpdater extends RepoUpdater {
|
||||
throw new UpdateException(repo, "No signature found in index");
|
||||
}
|
||||
|
||||
Log.d("FDroid", "Index has " + certs.length + " signature(s)");
|
||||
Log.d(TAG, "Index has " + certs.length + " signature(s)");
|
||||
boolean match = false;
|
||||
for (Certificate cert : certs) {
|
||||
String certdata = Hasher.hex(cert);
|
||||
if (repo.pubkey == null && repo.fingerprint.equals(Utils.calcFingerprint(cert))) {
|
||||
repo.pubkey = certdata;
|
||||
usePubkeyInJar = true;
|
||||
if (repo.pubkey == null && repo.fingerprint != null) {
|
||||
String certFingerprint = Utils.calcFingerprint(cert);
|
||||
Log.d(TAG, "No public key for repo " + repo.address + " yet, but it does have a fingerprint, so comparing them.");
|
||||
Log.d(TAG, "Repo fingerprint: " + repo.fingerprint);
|
||||
Log.d(TAG, "Cert fingerprint: " + certFingerprint);
|
||||
if (repo.fingerprint.equalsIgnoreCase(certFingerprint)) {
|
||||
repo.pubkey = certdata;
|
||||
usePubkeyInJar = true;
|
||||
}
|
||||
}
|
||||
if (repo.pubkey != null && repo.pubkey.equals(certdata)) {
|
||||
Log.d(TAG, "Checking repo public key against cert found in jar.");
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
@ -105,7 +114,7 @@ public class SignedRepoUpdater extends RepoUpdater {
|
||||
protected File getIndexFromFile(File downloadedFile) throws
|
||||
UpdateException {
|
||||
Date updateTime = new Date(System.currentTimeMillis());
|
||||
Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
|
||||
Log.d(TAG, "Getting signed index from " + repo.address + " at " +
|
||||
Utils.LOG_DATE_FORMAT.format(updateTime));
|
||||
|
||||
File indexJar = downloadedFile;
|
||||
|
@ -74,7 +74,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
resetNetworkInfo();
|
||||
setRepoSwitchChecked(FDroidApp.isLocalRepoServiceRunnig());
|
||||
setRepoSwitchChecked(FDroidApp.isLocalRepoServiceRunning());
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||
|
@ -5,7 +5,6 @@ import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -17,6 +16,7 @@ import android.view.MenuItem;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.NfcHelper;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
@ -67,27 +67,18 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
|
||||
@TargetApi(14)
|
||||
private void setNfc() {
|
||||
if (Build.VERSION.SDK_INT < 14)
|
||||
return;
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
if (nfcAdapter == null) {
|
||||
return;
|
||||
if (NfcHelper.setPushMessage(this, Utils.getSharingUri(this, repo))) {
|
||||
findViewById(android.R.id.content).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onNewIntent(getIntent());
|
||||
}
|
||||
});
|
||||
}
|
||||
nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] {
|
||||
NdefRecord.createUri(Utils.getSharingUri(this, repo)),
|
||||
}), this);
|
||||
findViewById(android.R.id.content).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Runnable.run()");
|
||||
onNewIntent(getIntent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
Log.i(TAG, "onResume");
|
||||
super.onResume();
|
||||
// FDroid.java and AppDetails set different NFC actions, so reset here
|
||||
setNfc();
|
||||
@ -96,9 +87,6 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent i) {
|
||||
Log.i(TAG, "onNewIntent");
|
||||
Log.i(TAG, "action: " + i.getAction());
|
||||
Log.i(TAG, "data: " + i.getData());
|
||||
// onResume gets called after this to handle the intent
|
||||
setIntent(i);
|
||||
}
|
||||
@ -108,7 +96,6 @@ public class RepoDetailsActivity extends ActionBarActivity {
|
||||
if (Build.VERSION.SDK_INT < 9)
|
||||
return;
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(i.getAction())) {
|
||||
Log.i(TAG, "ACTION_NDEF_DISCOVERED");
|
||||
Parcelable[] rawMsgs =
|
||||
i.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
|
||||
NdefMessage msg = (NdefMessage) rawMsgs[0];
|
||||
|
@ -6,22 +6,21 @@ import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.listener.PauseOnScrollListener;
|
||||
import org.fdroid.fdroid.*;
|
||||
import org.fdroid.fdroid.AppDetails;
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.views.AppListAdapter;
|
||||
|
||||
abstract public class AppListFragment extends ListFragment implements
|
||||
abstract public class AppListFragment extends ThemeableListFragment implements
|
||||
AdapterView.OnItemClickListener,
|
||||
Preferences.ChangeListener,
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
@ -0,0 +1,65 @@
|
||||
package org.fdroid.fdroid.views.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public abstract class ThemeableListFragment extends ListFragment {
|
||||
|
||||
protected int getThemeStyle() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int getHeaderLayout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected View getHeaderView(LayoutInflater inflater, ViewGroup container) {
|
||||
if (getHeaderLayout() > 0) {
|
||||
return inflater.inflate(getHeaderLayout(), null, false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LayoutInflater getThemedInflater(Context context)
|
||||
{
|
||||
Context c = (getThemeStyle() == 0) ? context : new ContextThemeWrapper(context, getThemeStyle());
|
||||
return (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally we'd just let the baseclass ListFrament.onCreateView() from the support library do its magic.
|
||||
* However, it doesn't allow us to theme it. That is, it always passes getActivity() into the constructor
|
||||
* of widgets. We are more interested in a ContextThemeWrapper, so that the widgets get appropriately
|
||||
* themed. In order to get it working, we need to work around android bug 21742 as well
|
||||
* (https://code.google.com/p/android/issues/detail?id=21742).
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
LayoutInflater themedInflater = getThemedInflater(inflater.getContext());
|
||||
|
||||
View view = themedInflater.inflate(R.layout.list_content, container, false);
|
||||
|
||||
View headerView = getHeaderView(themedInflater, container);
|
||||
if (headerView != null) {
|
||||
ListView listView = (ListView) view.findViewById(android.R.id.list);
|
||||
listView.addHeaderView(headerView);
|
||||
}
|
||||
|
||||
// Workaround for https://code.google.com/p/android/issues/detail?id=21742
|
||||
view.findViewById(android.R.id.empty).setId(0x00ff0001);
|
||||
view.findViewById(R.id.progressContainer).setId(0x00ff0002);
|
||||
view.findViewById(android.R.id.progress).setId(0x00ff0003);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
107
src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java
Normal file
@ -0,0 +1,107 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentValues;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.UpdateService;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
|
||||
public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener {
|
||||
|
||||
private NewRepoConfig newRepoConfig;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
View view = inflater.inflate(R.layout.swap_confirm_receive, container, false);
|
||||
|
||||
view.findViewById(R.id.no_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.yes_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
confirm();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
newRepoConfig = new NewRepoConfig(getActivity(), getActivity().getIntent());
|
||||
if (newRepoConfig.isValidRepo()) {
|
||||
((TextView) getView().findViewById(R.id.text_description)).setText(
|
||||
getString(R.string.swap_confirm_connect, newRepoConfig.getHost())
|
||||
);
|
||||
} else {
|
||||
// TODO: Show error message on screen (not in popup).
|
||||
}
|
||||
}
|
||||
|
||||
private void confirm() {
|
||||
Repo repo = ensureRepoExists();
|
||||
UpdateService.updateRepoNow(repo.address, getActivity()).setListener(this);
|
||||
}
|
||||
|
||||
private Repo ensureRepoExists() {
|
||||
// TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with
|
||||
// the repos address in the database.
|
||||
Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString());
|
||||
if (repo == null) {
|
||||
ContentValues values = new ContentValues(5);
|
||||
|
||||
// TODO: i18n and think about most appropriate name. Although ideally, it will not be seen often,
|
||||
// because we're whacking a pretty UI over the swap process so they don't need to "Manage repos"...
|
||||
values.put(RepoProvider.DataColumns.NAME, "Swap");
|
||||
values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString());
|
||||
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
|
||||
values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint());
|
||||
values.put(RepoProvider.DataColumns.IN_USE, true);
|
||||
Uri uri = RepoProvider.Helper.insert(getActivity(), values);
|
||||
repo = RepoProvider.Helper.findByUri(getActivity(), uri);
|
||||
}
|
||||
return repo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(Event event) {
|
||||
// TODO: Show progress, but we can worry about that later.
|
||||
// Might be nice to have it nicely embedded in the UI, rather than as
|
||||
// an additional dialog. E.g. White text on blue, letting the user
|
||||
// know what we are up to.
|
||||
|
||||
if (event.type.equals(UpdateService.EVENT_COMPLETE_AND_SAME) ||
|
||||
event.type.equals(UpdateService.EVENT_COMPLETE_WITH_CHANGES)) {
|
||||
((ConnectSwapActivity)getActivity()).onRepoUpdated();
|
||||
/*Intent intent = new Intent();
|
||||
intent.putExtra("category", newRepoConfig.getHost()); // TODO: Load repo from database to get proper name. This is what the category we want to select will be called.
|
||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||
finish();*/
|
||||
} else if (event.type.equals(UpdateService.EVENT_ERROR)) {
|
||||
// TODO: Show message on this screen (with a big "okay" button that goes back to F-Droid activity)
|
||||
// rather than finishing directly.
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
51
src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java
Normal file
@ -0,0 +1,51 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
public class ConnectSwapActivity extends FragmentActivity {
|
||||
|
||||
private static final String STATE_CONFIRM = "startSwap";
|
||||
private static final String STATE_APP_LIST = "swapAppList";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(android.R.id.content, new ConfirmReceiveSwapFragment(), STATE_CONFIRM)
|
||||
.addToBackStack(STATE_CONFIRM)
|
||||
.commit();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (currentState().equals(STATE_CONFIRM)) {
|
||||
finish();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private String currentState() {
|
||||
int index = getSupportFragmentManager().getBackStackEntryCount() - 1;
|
||||
FragmentManager.BackStackEntry lastFragment = getSupportFragmentManager().getBackStackEntryAt(index);
|
||||
return lastFragment.getName();
|
||||
}
|
||||
|
||||
public void onRepoUpdated() {
|
||||
|
||||
Intent intent = new Intent(this, SwapAppListActivity.class);
|
||||
startActivity(intent);
|
||||
|
||||
}
|
||||
}
|
86
src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java
Normal file
@ -0,0 +1,86 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.text.TextUtils;
|
||||
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.TextView;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
|
||||
public class JoinWifiFragment extends Fragment {
|
||||
|
||||
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
refreshWifiState();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.swap_next, menu);
|
||||
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
|
||||
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
|
||||
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View joinWifiView = inflater.inflate(R.layout.swap_join_wifi, container, false);
|
||||
joinWifiView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
openAvailableNetworks();
|
||||
}
|
||||
});
|
||||
return joinWifiView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
// TODO: Listen for "Connecting..." state and reflect that in the view too.
|
||||
LocalBroadcastManager.getInstance(activity).registerReceiver(
|
||||
onWifiChange,
|
||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshWifiState();
|
||||
}
|
||||
|
||||
private void refreshWifiState() {
|
||||
if (getView() != null) {
|
||||
TextView ssidView = (TextView) getView().findViewById(R.id.wifi_ssid);
|
||||
String text = TextUtils.isEmpty(FDroidApp.ssid) ? getString(R.string.swap_no_wifi_network) : FDroidApp.ssid;
|
||||
ssidView.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void openAvailableNetworks() {
|
||||
startActivity(new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK));
|
||||
}
|
||||
}
|
46
src/org/fdroid/fdroid/views/swap/NfcSwapFragment.java
Normal file
@ -0,0 +1,46 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
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.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class NfcSwapFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.swap_skip, menu);
|
||||
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
|
||||
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
|
||||
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.swap_nfc, container, false);
|
||||
CheckBox dontShowAgain = (CheckBox)view.findViewById(R.id.checkbox_dont_show);
|
||||
dontShowAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.get().setShowNfcDuringSwap(!isChecked);
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
249
src/org/fdroid/fdroid/views/swap/SelectAppsFragment.java
Normal file
@ -0,0 +1,249 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
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.SimpleCursorAdapter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
import org.fdroid.fdroid.localrepo.LocalRepoManager;
|
||||
import org.fdroid.fdroid.views.fragments.ThemeableListFragment;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SelectAppsFragment extends ThemeableListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>, SearchView.OnQueryTextListener {
|
||||
|
||||
private PackageManager packageManager;
|
||||
private Drawable defaultAppIcon;
|
||||
private ActionMode mActionMode = null;
|
||||
private String mCurrentFilterString;
|
||||
private Set<String> previouslySelectedApps = new HashSet<String>();
|
||||
|
||||
public Set<String> getSelectedApps() {
|
||||
return FDroidApp.selectedApps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.swap_next, menu);
|
||||
MenuItem nextMenuItem = menu.findItem(R.id.action_next);
|
||||
int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;
|
||||
MenuItemCompat.setShowAsAction(nextMenuItem, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
previouslySelectedApps.clear();
|
||||
if (FDroidApp.selectedApps != null) {
|
||||
previouslySelectedApps.addAll(FDroidApp.selectedApps);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSelectionChanged() {
|
||||
|
||||
Set<String> currentlySelected = getSelectedApps();
|
||||
if (currentlySelected.size() != previouslySelectedApps.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String current : currentlySelected) {
|
||||
boolean found = false;
|
||||
for (String previous : previouslySelectedApps) {
|
||||
if (current.equals(previous)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
setEmptyText(getString(R.string.no_applications_found));
|
||||
|
||||
packageManager = getActivity().getPackageManager();
|
||||
defaultAppIcon = getResources().getDrawable(android.R.drawable.sym_def_app_icon);
|
||||
|
||||
ListView listView = getListView();
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
||||
new ContextThemeWrapper(getActivity(), R.style.SwapTheme_AppList_ListItem),
|
||||
R.layout.select_local_apps_list_item,
|
||||
null,
|
||||
new String[] {
|
||||
InstalledAppProvider.DataColumns.APPLICATION_LABEL,
|
||||
InstalledAppProvider.DataColumns.APP_ID,
|
||||
},
|
||||
new int[] {
|
||||
R.id.application_label,
|
||||
R.id.package_name,
|
||||
});
|
||||
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
|
||||
|
||||
@Override
|
||||
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
||||
if (columnIndex == cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)) {
|
||||
String packageName = cursor.getString(columnIndex);
|
||||
TextView textView = (TextView) view.findViewById(R.id.package_name);
|
||||
textView.setText(packageName);
|
||||
LinearLayout ll = (LinearLayout) view.getParent().getParent();
|
||||
ImageView iconView = (ImageView) ll.getChildAt(0);
|
||||
Drawable icon;
|
||||
try {
|
||||
icon = packageManager.getApplicationIcon(packageName);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
icon = defaultAppIcon;
|
||||
}
|
||||
iconView.setImageDrawable(icon);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setListAdapter(adapter);
|
||||
setListShown(false); // start out with a progress indicator
|
||||
|
||||
// either reconnect with an existing loader or start a new one
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
// build list of existing apps from what is on the file system
|
||||
if (FDroidApp.selectedApps == null) {
|
||||
FDroidApp.selectedApps = new HashSet<String>();
|
||||
for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) {
|
||||
if (filename.matches(".*\\.apk")) {
|
||||
String packageName = filename.substring(0, filename.indexOf("_"));
|
||||
FDroidApp.selectedApps.add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
Cursor c = (Cursor) l.getAdapter().getItem(position);
|
||||
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
||||
if (FDroidApp.selectedApps.contains(packageName)) {
|
||||
FDroidApp.selectedApps.remove(packageName);
|
||||
} else {
|
||||
FDroidApp.selectedApps.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CursorLoader onCreateLoader(int id, Bundle args) {
|
||||
Uri baseUri;
|
||||
if (TextUtils.isEmpty(mCurrentFilterString)) {
|
||||
baseUri = InstalledAppProvider.getContentUri();
|
||||
} else {
|
||||
baseUri = InstalledAppProvider.getSearchUri(mCurrentFilterString);
|
||||
}
|
||||
return new CursorLoader(
|
||||
this.getActivity(),
|
||||
baseUri,
|
||||
InstalledAppProvider.DataColumns.ALL,
|
||||
null,
|
||||
null,
|
||||
InstalledAppProvider.DataColumns.APPLICATION_LABEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
((SimpleCursorAdapter) this.getListAdapter()).swapCursor(cursor);
|
||||
|
||||
ListView listView = getListView();
|
||||
String fdroid = loader.getContext().getPackageName();
|
||||
for (int i = 0; i < listView.getCount() - 1; i++) {
|
||||
Cursor c = ((Cursor) listView.getItemAtPosition(i + 1));
|
||||
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
|
||||
if (TextUtils.equals(packageName, fdroid)) {
|
||||
listView.setItemChecked(i, true); // always include FDroid
|
||||
} else {
|
||||
for (String selected : FDroidApp.selectedApps) {
|
||||
if (TextUtils.equals(packageName, selected)) {
|
||||
listView.setItemChecked(i + 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
((SimpleCursorAdapter) this.getListAdapter()).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;
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
// this is not needed since we respond to every change in text
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getCurrentFilterString() {
|
||||
return mCurrentFilterString;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getThemeStyle() {
|
||||
return R.style.SwapTheme_StartSwap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHeaderLayout() {
|
||||
return R.layout.swap_create_header;
|
||||
}
|
||||
}
|
38
src/org/fdroid/fdroid/views/swap/StartSwapFragment.java
Normal file
@ -0,0 +1,38 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class StartSwapFragment extends Fragment {
|
||||
|
||||
private SwapProcessManager manager;
|
||||
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
manager = (SwapProcessManager)activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
LayoutInflater themedInflater = (LayoutInflater)new ContextThemeWrapper(inflater.getContext(), R.style.SwapTheme_StartSwap).getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
View view = themedInflater.inflate(R.layout.swap_blank, container, false);
|
||||
view.findViewById(R.id.button_start_swap).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
manager.nextStep();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
263
src/org/fdroid/fdroid/views/swap/SwapActivity.java
Normal file
@ -0,0 +1,263 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.NfcHelper;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.localrepo.LocalRepoManager;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class SwapActivity extends ActionBarActivity implements SwapProcessManager {
|
||||
|
||||
private static final String STATE_START_SWAP = "startSwap";
|
||||
private static final String STATE_SELECT_APPS = "selectApps";
|
||||
private static final String STATE_JOIN_WIFI = "joinWifi";
|
||||
private static final String STATE_NFC = "nfc";
|
||||
private static final String STATE_WIFI_QR = "wifiQr";
|
||||
|
||||
private Timer shutdownLocalRepoTimer;
|
||||
private UpdateAsyncTask updateSwappableAppsTask = null;
|
||||
private boolean hasPreparedLocalRepo = false;
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (currentState().equals(STATE_START_SWAP)) {
|
||||
finish();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private String currentState() {
|
||||
FragmentManager.BackStackEntry lastFragment = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1);
|
||||
return lastFragment.getName();
|
||||
}
|
||||
|
||||
public void nextStep() {
|
||||
String current = currentState();
|
||||
if (current.equals(STATE_START_SWAP)) {
|
||||
showSelectApps();
|
||||
} else if (current.equals(STATE_SELECT_APPS)) {
|
||||
prepareLocalRepo();
|
||||
} else if (current.equals(STATE_JOIN_WIFI)) {
|
||||
ensureLocalRepoRunning();
|
||||
if (!attemptToShowNfc()) {
|
||||
showWifiQr();
|
||||
}
|
||||
} else if (current.equals(STATE_NFC)) {
|
||||
showWifiQr();
|
||||
} else if (current.equals(STATE_WIFI_QR)) {
|
||||
}
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_next) {
|
||||
nextStep();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
|
||||
showFragment(new StartSwapFragment(), STATE_START_SWAP);
|
||||
|
||||
if (FDroidApp.isLocalRepoServiceRunning()) {
|
||||
showSelectApps();
|
||||
showJoinWifi();
|
||||
attemptToShowNfc();
|
||||
showWifiQr();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showSelectApps() {
|
||||
|
||||
showFragment(new SelectAppsFragment(), STATE_SELECT_APPS);
|
||||
|
||||
}
|
||||
|
||||
private void showJoinWifi() {
|
||||
|
||||
showFragment(new JoinWifiFragment(), STATE_JOIN_WIFI);
|
||||
|
||||
}
|
||||
|
||||
private boolean attemptToShowNfc() {
|
||||
// TODO: What if NFC is disabled? Hook up with NfcNotEnabledActivity? Or maybe only if they
|
||||
// click a relevant button?
|
||||
|
||||
// Even if they opted to skip the message which says "Touch devices to swap",
|
||||
// we still want to actually enable the feature, so that they could touch
|
||||
// during the wifi qr code being shown too.
|
||||
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(this, FDroidApp.repo));
|
||||
|
||||
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
|
||||
showFragment(new NfcSwapFragment(), STATE_NFC);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void showBluetooth() {
|
||||
|
||||
}
|
||||
|
||||
private void showWifiQr() {
|
||||
showFragment(new WifiQrFragment(), STATE_WIFI_QR);
|
||||
}
|
||||
|
||||
private void showFragment(Fragment fragment, String name) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(android.R.id.content, fragment, name)
|
||||
.addToBackStack(name)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void prepareLocalRepo() {
|
||||
SelectAppsFragment fragment = (SelectAppsFragment)getSupportFragmentManager().findFragmentByTag(STATE_SELECT_APPS);
|
||||
boolean needsUpdating = !hasPreparedLocalRepo || fragment.hasSelectionChanged();
|
||||
if (updateSwappableAppsTask == null && needsUpdating) {
|
||||
updateSwappableAppsTask = new UpdateAsyncTask(this, fragment.getSelectedApps());
|
||||
updateSwappableAppsTask.execute();
|
||||
} else {
|
||||
showJoinWifi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the UpdateAsyncTask has finished preparing our repository index, we can
|
||||
* show the next screen to the user.
|
||||
*/
|
||||
private void onLocalRepoPrepared() {
|
||||
|
||||
updateSwappableAppsTask = null;
|
||||
hasPreparedLocalRepo = true;
|
||||
showJoinWifi();
|
||||
|
||||
}
|
||||
|
||||
private void ensureLocalRepoRunning() {
|
||||
if (!FDroidApp.isLocalRepoServiceRunning()) {
|
||||
FDroidApp.startLocalRepoService(this);
|
||||
initLocalRepoTimer(900000); // 15 mins
|
||||
}
|
||||
}
|
||||
|
||||
private void initLocalRepoTimer(long timeoutMilliseconds) {
|
||||
|
||||
// reset the timer if viewing this Activity again
|
||||
if (shutdownLocalRepoTimer != null)
|
||||
shutdownLocalRepoTimer.cancel();
|
||||
|
||||
// automatically turn off after 15 minutes
|
||||
shutdownLocalRepoTimer = new Timer();
|
||||
shutdownLocalRepoTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
FDroidApp.stopLocalRepoService(SwapActivity.this);
|
||||
}
|
||||
}, timeoutMilliseconds);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSwapping() {
|
||||
if (FDroidApp.isLocalRepoServiceRunning()) {
|
||||
if (shutdownLocalRepoTimer != null) {
|
||||
shutdownLocalRepoTimer.cancel();
|
||||
}
|
||||
FDroidApp.stopLocalRepoService(SwapActivity.this);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
class UpdateAsyncTask extends AsyncTask<Void, String, Void> {
|
||||
private static final String TAG = "UpdateAsyncTask";
|
||||
private ProgressDialog progressDialog;
|
||||
private Set<String> selectedApps;
|
||||
private Uri sharingUri;
|
||||
|
||||
public UpdateAsyncTask(Context c, Set<String> apps) {
|
||||
selectedApps = apps;
|
||||
progressDialog = new ProgressDialog(c);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progressDialog.setTitle(R.string.updating);
|
||||
sharingUri = Utils.getSharingUri(c, FDroidApp.repo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
final LocalRepoManager lrm = LocalRepoManager.get(SwapActivity.this);
|
||||
publishProgress(getString(R.string.deleting_repo));
|
||||
lrm.deleteRepo();
|
||||
for (String app : selectedApps) {
|
||||
publishProgress(String.format(getString(R.string.adding_apks_format), app));
|
||||
lrm.addApp(SwapActivity.this, app);
|
||||
}
|
||||
lrm.writeIndexPage(sharingUri.toString());
|
||||
publishProgress(getString(R.string.writing_index_jar));
|
||||
lrm.writeIndexJar();
|
||||
publishProgress(getString(R.string.linking_apks));
|
||||
lrm.copyApksToRepo();
|
||||
publishProgress(getString(R.string.copying_icons));
|
||||
// run the icon copy without progress, its not a blocker
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
lrm.copyIconsToRepo();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... progress) {
|
||||
super.onProgressUpdate(progress);
|
||||
progressDialog.setMessage(progress[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
progressDialog.dismiss();
|
||||
Toast.makeText(SwapActivity.this, R.string.updated_local_repo, Toast.LENGTH_SHORT).show();
|
||||
onLocalRepoPrepared();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
52
src/org/fdroid/fdroid/views/swap/SwapAppListActivity.java
Normal file
@ -0,0 +1,52 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.views.AppListAdapter;
|
||||
import org.fdroid.fdroid.views.AvailableAppListAdapter;
|
||||
import org.fdroid.fdroid.views.fragments.AppListFragment;
|
||||
|
||||
public class SwapAppListActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, new SwapAppListFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SwapAppListFragment extends AppListFragment {
|
||||
|
||||
@Override
|
||||
protected int getHeaderLayout() {
|
||||
return R.layout.swap_success_header;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AppListAdapter getAppListAdapter() {
|
||||
return new AvailableAppListAdapter(getActivity(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFromTitle() {
|
||||
return getString(R.string.swap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Uri getDataUri() {
|
||||
return AppProvider.getCategoryUri("LocalRepo");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
6
src/org/fdroid/fdroid/views/swap/SwapProcessManager.java
Normal file
@ -0,0 +1,6 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
public interface SwapProcessManager {
|
||||
public void nextStep();
|
||||
public void stopSwapping();
|
||||
}
|
158
src/org/fdroid/fdroid/views/swap/WifiQrFragment.java
Normal file
@ -0,0 +1,158 @@
|
||||
package org.fdroid.fdroid.views.swap;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.google.zxing.integration.android.IntentIntegrator;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.QrGenAsyncTask;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class WifiQrFragment extends Fragment {
|
||||
|
||||
private static final int CONNECT_TO_SWAP = 1;
|
||||
|
||||
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent i) {
|
||||
setUIFromWifi();
|
||||
}
|
||||
};
|
||||
|
||||
private SwapProcessManager swapManager;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.swap_wifi_qr, container, false);
|
||||
ImageView qrImage = (ImageView)view.findViewById(R.id.wifi_qr_code);
|
||||
|
||||
// Replace all blacks with the background blue.
|
||||
qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue)));
|
||||
|
||||
Button openQr = (Button)view.findViewById(R.id.btn_qr_scanner);
|
||||
openQr.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
IntentIntegrator integrator = new IntentIntegrator(WifiQrFragment.this);
|
||||
integrator.initiateScan();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancel = (Button)view.findViewById(R.id.btn_cancel_swap);
|
||||
cancel.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
swapManager.stopSwapping();
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
swapManager = (SwapProcessManager)activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||
if (scanResult != null) {
|
||||
if (scanResult.getContents() != null) {
|
||||
NewRepoConfig repoConfig = new NewRepoConfig(getActivity(), scanResult.getContents());
|
||||
if (repoConfig.isValidRepo()) {
|
||||
startActivityForResult(new Intent(FDroid.ACTION_ADD_REPO, Uri.parse(scanResult.getContents()), getActivity(), ConnectSwapActivity.class), CONNECT_TO_SWAP);
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "The QR code you scanned doesn't look like a swap code.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} else if (requestCode == CONNECT_TO_SWAP && resultCode == Activity.RESULT_OK) {
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setUIFromWifi();
|
||||
|
||||
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(onWifiChange,
|
||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||
}
|
||||
|
||||
@TargetApi(14)
|
||||
private void setUIFromWifi() {
|
||||
|
||||
if (TextUtils.isEmpty(FDroidApp.repo.address))
|
||||
return;
|
||||
|
||||
String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://";
|
||||
|
||||
// the fingerprint is not useful on the button label
|
||||
String buttonLabel = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port;
|
||||
TextView ipAddressView = (TextView) getView().findViewById(R.id.device_ip_address);
|
||||
ipAddressView.setText(buttonLabel);
|
||||
|
||||
/*
|
||||
* Set URL to UPPER for compact QR Code, FDroid will translate it back.
|
||||
* Remove the SSID from the query string since SSIDs are case-sensitive.
|
||||
* Instead the receiver will have to rely on the BSSID to find the right
|
||||
* wifi AP to join. Lots of QR Scanners are buggy and do not respect
|
||||
* custom URI schemes, so we have to use http:// or https:// :-(
|
||||
*/
|
||||
Uri sharingUri = Utils.getSharingUri(getActivity(), FDroidApp.repo);
|
||||
String qrUriString = ( scheme + sharingUri.getHost() ).toUpperCase(Locale.ENGLISH);
|
||||
if (sharingUri.getPort() != 80) {
|
||||
qrUriString += ":" + sharingUri.getPort();
|
||||
}
|
||||
qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH);
|
||||
boolean first = true;
|
||||
for (String parameterName : sharingUri.getQueryParameterNames()) {
|
||||
if (!parameterName.equals("ssid")) {
|
||||
if (first) {
|
||||
qrUriString += "?";
|
||||
first = false;
|
||||
} else {
|
||||
qrUriString += "&";
|
||||
}
|
||||
qrUriString += parameterName.toUpperCase(Locale.ENGLISH) + "=" +
|
||||
sharingUri.getQueryParameter(parameterName).toUpperCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("QRURI", qrUriString);
|
||||
|
||||
// zxing requires >= 8
|
||||
// TODO: What about 7? I don't feel comfortable bumping the min version for this...
|
||||
// I would suggest show some alternate info, with directions for how to add a new repository manually.
|
||||
if (Build.VERSION.SDK_INT >= 8)
|
||||
new QrGenAsyncTask(getActivity(), R.id.wifi_qr_code).execute(qrUriString);
|
||||
|
||||
}
|
||||
|
||||
}
|