Activity/Service for running a local repo via http://
This is a skeleton for the upcoming local repo (aka swap aka Kerplapp). Right now, it just provides an Activity for controlling a Service which manages a local webserver (nanohttpd). Next, it will be wired up to the local repo created via a dedicated Activity for managing the list of apps included in the local repo. refs #3204 https://dev.guardianproject.info/issues/3204
This commit is contained in:
parent
2c2d8c868c
commit
5050605f72
@ -182,6 +182,19 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".NfcNotEnabledActivity"
|
android:name=".NfcNotEnabledActivity"
|
||||||
android:noHistory="true" />
|
android:noHistory="true" />
|
||||||
|
<activity
|
||||||
|
android:name=".views.LocalRepoActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
|
android:label="@string/local_repo"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:parentActivityName=".FDroid"
|
||||||
|
android:screenOrientation="portrait" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".views.RepoDetailsActivity"
|
android:name=".views.RepoDetailsActivity"
|
||||||
android:label="@string/menu_manage"
|
android:label="@string/menu_manage"
|
||||||
@ -312,6 +325,7 @@
|
|||||||
|
|
||||||
<service android:name=".UpdateService" />
|
<service android:name=".UpdateService" />
|
||||||
<service android:name=".net.WifiStateChangeService" />
|
<service android:name=".net.WifiStateChangeService" />
|
||||||
|
<service android:name=".localrepo.LocalRepoService" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
63
res/layout/local_repo_activity.xml
Normal file
63
res/layout/local_repo_activity.xml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/repoSwitch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/wifiNetwork"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="@string/wifi_network" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/wifiNetworkName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="15dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="@string/fingerprint" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="15dp"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/instrucionsTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
android:layout_marginRight="20dp"
|
||||||
|
android:text="@string/same_wifi_instructions" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/repoQrCode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/qr_content_description" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
10
res/menu/local_repo_activity.xml
Normal file
10
res/menu/local_repo_activity.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_settings"
|
||||||
|
android:icon="@android:drawable/ic_menu_preferences"
|
||||||
|
android:showAsAction="never"
|
||||||
|
android:title="@string/menu_preferences"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -150,8 +150,17 @@
|
|||||||
<string name="category_whatsnew">What\'s New</string>
|
<string name="category_whatsnew">What\'s New</string>
|
||||||
<string name="category_recentlyupdated">Recently Updated</string>
|
<string name="category_recentlyupdated">Recently Updated</string>
|
||||||
|
|
||||||
|
<string name="local_repo">Local Repo</string>
|
||||||
<string name="local_repos_title">Local FDroid Repos</string>
|
<string name="local_repos_title">Local FDroid Repos</string>
|
||||||
<string name="local_repos_scanning">Discovering 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="touch_to_configure_local_repo">Touch to setup your local repo.</string>
|
||||||
|
<string name="fingerprint">Fingerprint:</string>
|
||||||
|
<string name="wifi_network">WiFi Network:</string>
|
||||||
|
<string name="enable_wifi">Enable WiFi</string>
|
||||||
|
<string name="enabling_wifi">Enabling WiFi…</string>
|
||||||
|
<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_content_description">QR Code of repo URL</string>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
status_download takes four parameters:
|
status_download takes four parameters:
|
||||||
|
@ -19,20 +19,17 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.AlertDialog.Builder;
|
import android.app.AlertDialog.Builder;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.*;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.nfc.NfcAdapter;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
@ -49,6 +46,7 @@ import android.widget.TextView;
|
|||||||
import org.fdroid.fdroid.compat.TabManager;
|
import org.fdroid.fdroid.compat.TabManager;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
|
import org.fdroid.fdroid.views.AppListFragmentPageAdapter;
|
||||||
|
import org.fdroid.fdroid.views.LocalRepoActivity;
|
||||||
|
|
||||||
public class FDroid extends FragmentActivity {
|
public class FDroid extends FragmentActivity {
|
||||||
|
|
||||||
@ -65,6 +63,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
private static final int ABOUT = Menu.FIRST + 3;
|
private static final int ABOUT = Menu.FIRST + 3;
|
||||||
private static final int SEARCH = Menu.FIRST + 4;
|
private static final int SEARCH = Menu.FIRST + 4;
|
||||||
private static final int BLUETOOTH_APK = Menu.FIRST + 5;
|
private static final int BLUETOOTH_APK = Menu.FIRST + 5;
|
||||||
|
private static final int LOCAL_REPO = Menu.FIRST + 6;
|
||||||
|
|
||||||
private FDroidApp fdroidApp = null;
|
private FDroidApp fdroidApp = null;
|
||||||
|
|
||||||
@ -135,6 +134,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
android.R.drawable.ic_menu_search);
|
android.R.drawable.ic_menu_search);
|
||||||
if (fdroidApp.bluetoothAdapter != null) // ignore on devices without Bluetooth
|
if (fdroidApp.bluetoothAdapter != null) // ignore on devices without Bluetooth
|
||||||
menu.add(Menu.NONE, BLUETOOTH_APK, 3, R.string.menu_send_apk_bt);
|
menu.add(Menu.NONE, BLUETOOTH_APK, 3, R.string.menu_send_apk_bt);
|
||||||
|
menu.add(Menu.NONE, LOCAL_REPO, 4, R.string.local_repo);
|
||||||
menu.add(Menu.NONE, PREFERENCES, 4, R.string.menu_preferences).setIcon(
|
menu.add(Menu.NONE, PREFERENCES, 4, R.string.menu_preferences).setIcon(
|
||||||
android.R.drawable.ic_menu_preferences);
|
android.R.drawable.ic_menu_preferences);
|
||||||
menu.add(Menu.NONE, ABOUT, 5, R.string.menu_about).setIcon(
|
menu.add(Menu.NONE, ABOUT, 5, R.string.menu_about).setIcon(
|
||||||
@ -162,6 +162,10 @@ public class FDroid extends FragmentActivity {
|
|||||||
startActivityForResult(prefs, REQUEST_PREFS);
|
startActivityForResult(prefs, REQUEST_PREFS);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case LOCAL_REPO:
|
||||||
|
startActivity(new Intent(this, LocalRepoActivity.class));
|
||||||
|
return true;
|
||||||
|
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
onSearchRequested();
|
onSearchRequested();
|
||||||
return true;
|
return true;
|
||||||
|
@ -26,6 +26,7 @@ import android.bluetooth.BluetoothManager;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@ -36,6 +37,7 @@ import android.net.wifi.WifiManager;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -53,6 +55,7 @@ import org.fdroid.fdroid.compat.PRNGFixes;
|
|||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
import org.thoughtcrime.ssl.pinning.PinningTrustManager;
|
import org.thoughtcrime.ssl.pinning.PinningTrustManager;
|
||||||
import org.thoughtcrime.ssl.pinning.SystemKeyStore;
|
import org.thoughtcrime.ssl.pinning.SystemKeyStore;
|
||||||
@ -82,6 +85,9 @@ public class FDroidApp extends Application {
|
|||||||
public static Repo repo = new Repo();
|
public static Repo repo = new Repo();
|
||||||
static Set<String> selectedApps = new HashSet<String>();
|
static Set<String> selectedApps = new HashSet<String>();
|
||||||
|
|
||||||
|
private static Messenger localRepoServiceMessenger = null;
|
||||||
|
private static boolean localRepoServiceIsBound = false;
|
||||||
|
|
||||||
BluetoothAdapter bluetoothAdapter = null;
|
BluetoothAdapter bluetoothAdapter = null;
|
||||||
|
|
||||||
private static enum Theme {
|
private static enum Theme {
|
||||||
@ -279,4 +285,41 @@ public class FDroidApp extends Application {
|
|||||||
activity.startActivity(sendBt);
|
activity.startActivity(sendBt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
localRepoServiceMessenger = new Messenger(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
localRepoServiceMessenger = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void startLocalRepoService(Context context) {
|
||||||
|
context.bindService(new Intent(context, LocalRepoService.class),
|
||||||
|
serviceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
localRepoServiceIsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopLocalRepoService(Context context) {
|
||||||
|
if (localRepoServiceIsBound) {
|
||||||
|
context.unbindService(serviceConnection);
|
||||||
|
localRepoServiceIsBound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void restartLocalRepoService() {
|
||||||
|
if (localRepoServiceMessenger != null) {
|
||||||
|
try {
|
||||||
|
Message msg = Message.obtain(null,
|
||||||
|
LocalRepoService.RESTART, LocalRepoService.RESTART, 0);
|
||||||
|
localRepoServiceMessenger.send(msg);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
146
src/org/fdroid/fdroid/localrepo/LocalRepoService.java
Normal file
146
src/org/fdroid/fdroid/localrepo/LocalRepoService.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
|
||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
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.content.SharedPreferences;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
import org.fdroid.fdroid.views.LocalRepoActivity;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class LocalRepoService extends Service {
|
||||||
|
private static final String TAG = "LocalRepoService";
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
// Unique Identification Number for the Notification.
|
||||||
|
// We use it on Notification start, and to cancel it.
|
||||||
|
private int NOTIFICATION = R.string.local_repo_running;
|
||||||
|
|
||||||
|
private Handler webServerThreadHandler = null;
|
||||||
|
|
||||||
|
public static int START = 1111111;
|
||||||
|
public static int STOP = 12345678;
|
||||||
|
public static int RESTART = 87654;
|
||||||
|
|
||||||
|
final Messenger messenger = new Messenger(new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (msg.arg1 == START) {
|
||||||
|
startWebServer();
|
||||||
|
} else if (msg.arg1 == STOP) {
|
||||||
|
stopWebServer();
|
||||||
|
} else if (msg.arg1 == RESTART) {
|
||||||
|
stopWebServer();
|
||||||
|
startWebServer();
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "unsupported msg.arg1, ignored");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent i) {
|
||||||
|
stopWebServer();
|
||||||
|
startWebServer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
// launch LocalRepoActivity if the user selects this notification
|
||||||
|
Intent intent = new Intent(this, LocalRepoActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
|
||||||
|
Notification 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)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.build();
|
||||||
|
startForeground(NOTIFICATION, notification);
|
||||||
|
startWebServer();
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
||||||
|
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
// We want this service to continue running until it is explicitly
|
||||||
|
// stopped, so return sticky.
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stopWebServer();
|
||||||
|
notificationManager.cancel(NOTIFICATION);
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return messenger.getBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWebServer() {
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
|
Runnable webServer = new Runnable() {
|
||||||
|
// Tell Eclipse this is not a leak because of Looper use.
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final LocalHTTPD localHttpd = new LocalHTTPD(getFilesDir(),
|
||||||
|
prefs.getBoolean("use_https", false));
|
||||||
|
|
||||||
|
Looper.prepare(); // must be run before creating a Handler
|
||||||
|
webServerThreadHandler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj);
|
||||||
|
localHttpd.stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
localHttpd.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Looper.loop(); // start the message receiving loop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new Thread(webServer).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopWebServer() {
|
||||||
|
if (webServerThreadHandler == null) {
|
||||||
|
Log.i(TAG, "null handler in stopWebServer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Message msg = webServerThreadHandler.obtainMessage();
|
||||||
|
msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop";
|
||||||
|
webServerThreadHandler.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
341
src/org/fdroid/fdroid/net/LocalHTTPD.java
Normal file
341
src/org/fdroid/fdroid/net/LocalHTTPD.java
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
|
||||||
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
|
|
||||||
|
public class LocalHTTPD extends NanoHTTPD {
|
||||||
|
private static final String TAG = LocalHTTPD.class.getCanonicalName();
|
||||||
|
|
||||||
|
private final File webRoot;
|
||||||
|
private final boolean logRequests;
|
||||||
|
|
||||||
|
public LocalHTTPD(File webRoot, boolean useHttps) {
|
||||||
|
super(FDroidApp.ipAddressString, FDroidApp.port);
|
||||||
|
this.logRequests = false;
|
||||||
|
this.webRoot = webRoot;
|
||||||
|
if (useHttps)
|
||||||
|
enableHTTPS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL-encodes everything between "/"-characters. Encodes spaces as '%20'
|
||||||
|
* instead of '+'.
|
||||||
|
*/
|
||||||
|
private String encodeUriBetweenSlashes(String uri) {
|
||||||
|
String newUri = "";
|
||||||
|
StringTokenizer st = new StringTokenizer(uri, "/ ", true);
|
||||||
|
while (st.hasMoreTokens()) {
|
||||||
|
String tok = st.nextToken();
|
||||||
|
if (tok.equals("/"))
|
||||||
|
newUri += "/";
|
||||||
|
else if (tok.equals(" "))
|
||||||
|
newUri += "%20";
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
newUri += URLEncoder.encode(tok, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response serve(IHTTPSession session) {
|
||||||
|
Map<String, String> header = session.getHeaders();
|
||||||
|
Map<String, String> parms = session.getParms();
|
||||||
|
String uri = session.getUri();
|
||||||
|
|
||||||
|
if (logRequests) {
|
||||||
|
Log.i(TAG, session.getMethod() + " '" + uri + "' ");
|
||||||
|
|
||||||
|
Iterator<String> e = header.keySet().iterator();
|
||||||
|
while (e.hasNext()) {
|
||||||
|
String value = e.next();
|
||||||
|
Log.i(TAG, " HDR: '" + value + "' = '" + header.get(value) + "'");
|
||||||
|
}
|
||||||
|
e = parms.keySet().iterator();
|
||||||
|
while (e.hasNext()) {
|
||||||
|
String value = e.next();
|
||||||
|
Log.i(TAG, " PRM: '" + value + "' = '" + parms.get(value) + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!webRoot.isDirectory()) {
|
||||||
|
return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"INTERNAL ERRROR: given path is not a directory (" + webRoot + ").");
|
||||||
|
}
|
||||||
|
|
||||||
|
return respond(Collections.unmodifiableMap(header), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableHTTPS() {
|
||||||
|
// TODO copy implementation from Kerplapp
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response respond(Map<String, String> headers, String uri) {
|
||||||
|
// Remove URL arguments
|
||||||
|
uri = uri.trim().replace(File.separatorChar, '/');
|
||||||
|
if (uri.indexOf('?') >= 0) {
|
||||||
|
uri = uri.substring(0, uri.indexOf('?'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prohibit getting out of current directory
|
||||||
|
if (uri.contains("../")) {
|
||||||
|
return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"FORBIDDEN: Won't serve ../ for security reasons.");
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = new File(webRoot, uri);
|
||||||
|
if (!f.exists()) {
|
||||||
|
return createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"Error 404, file not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browsers get confused without '/' after the directory, send a
|
||||||
|
// redirect.
|
||||||
|
if (f.isDirectory() && !uri.endsWith("/")) {
|
||||||
|
uri += "/";
|
||||||
|
Response res = createResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML,
|
||||||
|
"<html><body>Redirected: <a href=\"" +
|
||||||
|
uri + "\">" + uri + "</a></body></html>");
|
||||||
|
res.addHeader("Location", uri);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
// First look for index files (index.html, index.htm, etc) and if
|
||||||
|
// none found, list the directory if readable.
|
||||||
|
String indexFile = findIndexFileInDirectory(f);
|
||||||
|
if (indexFile == null) {
|
||||||
|
if (f.canRead()) {
|
||||||
|
// No index file, list the directory if it is readable
|
||||||
|
return createResponse(Response.Status.OK, NanoHTTPD.MIME_HTML,
|
||||||
|
listDirectory(uri, f));
|
||||||
|
} else {
|
||||||
|
return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"FORBIDDEN: No directory listing.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return respond(headers, uri + indexFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response response = null;
|
||||||
|
response = serveFile(uri, headers, f, getMimeTypeForFile(uri));
|
||||||
|
return response != null ? response :
|
||||||
|
createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"Error 404, file not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves file from homeDir and its' subdirectories (only). Uses only URI,
|
||||||
|
* ignores all headers and HTTP parameters.
|
||||||
|
*/
|
||||||
|
Response serveFile(String uri, Map<String, String> header, File file, String mime) {
|
||||||
|
Response res;
|
||||||
|
try {
|
||||||
|
// Calculate etag
|
||||||
|
String etag = Integer
|
||||||
|
.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length())
|
||||||
|
.hashCode());
|
||||||
|
|
||||||
|
// Support (simple) skipping:
|
||||||
|
long startFrom = 0;
|
||||||
|
long endAt = -1;
|
||||||
|
String range = header.get("range");
|
||||||
|
if (range != null) {
|
||||||
|
if (range.startsWith("bytes=")) {
|
||||||
|
range = range.substring("bytes=".length());
|
||||||
|
int minus = range.indexOf('-');
|
||||||
|
try {
|
||||||
|
if (minus > 0) {
|
||||||
|
startFrom = Long.parseLong(range.substring(0, minus));
|
||||||
|
endAt = Long.parseLong(range.substring(minus + 1));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change return code and add Content-Range header when skipping is
|
||||||
|
// requested
|
||||||
|
long fileLen = file.length();
|
||||||
|
if (range != null && startFrom >= 0) {
|
||||||
|
if (startFrom >= fileLen) {
|
||||||
|
res = createResponse(Response.Status.RANGE_NOT_SATISFIABLE,
|
||||||
|
NanoHTTPD.MIME_PLAINTEXT, "");
|
||||||
|
res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
|
||||||
|
res.addHeader("ETag", etag);
|
||||||
|
} else {
|
||||||
|
if (endAt < 0) {
|
||||||
|
endAt = fileLen - 1;
|
||||||
|
}
|
||||||
|
long newLen = endAt - startFrom + 1;
|
||||||
|
if (newLen < 0) {
|
||||||
|
newLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final long dataLen = newLen;
|
||||||
|
FileInputStream fis = new FileInputStream(file) {
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return (int) dataLen;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fis.skip(startFrom);
|
||||||
|
|
||||||
|
res = createResponse(Response.Status.PARTIAL_CONTENT, mime, fis);
|
||||||
|
res.addHeader("Content-Length", "" + dataLen);
|
||||||
|
res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/"
|
||||||
|
+ fileLen);
|
||||||
|
res.addHeader("ETag", etag);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (etag.equals(header.get("if-none-match")))
|
||||||
|
res = createResponse(Response.Status.NOT_MODIFIED, mime, "");
|
||||||
|
else {
|
||||||
|
res = createResponse(Response.Status.OK, mime, new FileInputStream(file));
|
||||||
|
res.addHeader("Content-Length", "" + fileLen);
|
||||||
|
res.addHeader("ETag", etag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
res = createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT,
|
||||||
|
"FORBIDDEN: Reading file failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announce that the file server accepts partial content requests
|
||||||
|
private Response createResponse(Response.Status status, String mimeType, InputStream message) {
|
||||||
|
Response res = new Response(status, mimeType, message);
|
||||||
|
res.addHeader("Accept-Ranges", "bytes");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Announce that the file server accepts partial content requests
|
||||||
|
private Response createResponse(Response.Status status, String mimeType, String message) {
|
||||||
|
Response res = new Response(status, mimeType, message);
|
||||||
|
res.addHeader("Accept-Ranges", "bytes");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMimeTypeForFile(String uri) {
|
||||||
|
String type = null;
|
||||||
|
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
|
||||||
|
if (extension != null) {
|
||||||
|
MimeTypeMap mime = MimeTypeMap.getSingleton();
|
||||||
|
type = mime.getMimeTypeFromExtension(extension);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findIndexFileInDirectory(File directory) {
|
||||||
|
String indexFileName = "index.html";
|
||||||
|
File indexFile = new File(directory, indexFileName);
|
||||||
|
if (indexFile.exists()) {
|
||||||
|
return indexFileName;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String listDirectory(String uri, File f) {
|
||||||
|
String heading = "Directory " + uri;
|
||||||
|
StringBuilder msg = new StringBuilder("<html><head><title>" + heading
|
||||||
|
+ "</title><style><!--\n" +
|
||||||
|
"span.dirname { font-weight: bold; }\n" +
|
||||||
|
"span.filesize { font-size: 75%; }\n" +
|
||||||
|
"// -->\n" +
|
||||||
|
"</style>" +
|
||||||
|
"</head><body><h1>" + heading + "</h1>");
|
||||||
|
|
||||||
|
String up = null;
|
||||||
|
if (uri.length() > 1) {
|
||||||
|
String u = uri.substring(0, uri.length() - 1);
|
||||||
|
int slash = u.lastIndexOf('/');
|
||||||
|
if (slash >= 0 && slash < u.length()) {
|
||||||
|
up = uri.substring(0, slash + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> files = Arrays.asList(f.list(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return new File(dir, name).isFile();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Collections.sort(files);
|
||||||
|
List<String> directories = Arrays.asList(f.list(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return new File(dir, name).isDirectory();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Collections.sort(directories);
|
||||||
|
if (up != null || directories.size() + files.size() > 0) {
|
||||||
|
msg.append("<ul>");
|
||||||
|
if (up != null || directories.size() > 0) {
|
||||||
|
msg.append("<section class=\"directories\">");
|
||||||
|
if (up != null) {
|
||||||
|
msg.append("<li><a rel=\"directory\" href=\"").append(up)
|
||||||
|
.append("\"><span class=\"dirname\">..</span></a></b></li>");
|
||||||
|
}
|
||||||
|
for (String directory : directories) {
|
||||||
|
String dir = directory + "/";
|
||||||
|
msg.append("<li><a rel=\"directory\" href=\"").append(encodeUriBetweenSlashes(uri + dir))
|
||||||
|
.append("\"><span class=\"dirname\">").append(dir)
|
||||||
|
.append("</span></a></b></li>");
|
||||||
|
}
|
||||||
|
msg.append("</section>");
|
||||||
|
}
|
||||||
|
if (files.size() > 0) {
|
||||||
|
msg.append("<section class=\"files\">");
|
||||||
|
for (String file : files) {
|
||||||
|
msg.append("<li><a href=\"").append(encodeUriBetweenSlashes(uri + file))
|
||||||
|
.append("\"><span class=\"filename\">").append(file)
|
||||||
|
.append("</span></a>");
|
||||||
|
File curFile = new File(f, file);
|
||||||
|
long len = curFile.length();
|
||||||
|
msg.append(" <span class=\"filesize\">(");
|
||||||
|
if (len < 1024) {
|
||||||
|
msg.append(len).append(" bytes");
|
||||||
|
} else if (len < 1024 * 1024) {
|
||||||
|
msg.append(len / 1024).append(".").append(len % 1024 / 10 % 100)
|
||||||
|
.append(" KB");
|
||||||
|
} else {
|
||||||
|
msg.append(len / (1024 * 1024)).append(".")
|
||||||
|
.append(len % (1024 * 1024) / 10 % 100).append(" MB");
|
||||||
|
}
|
||||||
|
msg.append(")</span></li>");
|
||||||
|
}
|
||||||
|
msg.append("</section>");
|
||||||
|
}
|
||||||
|
msg.append("</ul>");
|
||||||
|
}
|
||||||
|
msg.append("</body></html>");
|
||||||
|
return msg.toString();
|
||||||
|
}
|
||||||
|
}
|
209
src/org/fdroid/fdroid/views/LocalRepoActivity.java
Normal file
209
src/org/fdroid/fdroid/views/LocalRepoActivity.java
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
|
||||||
|
package org.fdroid.fdroid.views;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.nfc.NdefMessage;
|
||||||
|
import android.nfc.NdefRecord;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.PreferencesActivity;
|
||||||
|
import org.fdroid.fdroid.QrGenAsyncTask;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class LocalRepoActivity extends Activity {
|
||||||
|
private static final String TAG = "LocalRepoActivity";
|
||||||
|
private ProgressDialog repoProgress;
|
||||||
|
|
||||||
|
private WifiManager wifiManager;
|
||||||
|
private ToggleButton repoSwitch;
|
||||||
|
|
||||||
|
private int SET_IP_ADDRESS = 7345;
|
||||||
|
|
||||||
|
/** Called when the activity is first created. */
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.local_repo_activity);
|
||||||
|
|
||||||
|
repoSwitch = (ToggleButton) findViewById(R.id.repoSwitch);
|
||||||
|
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
resetNetworkInfo();
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
||||||
|
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent i) {
|
||||||
|
resetNetworkInfo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void resetNetworkInfo() {
|
||||||
|
int wifiState = wifiManager.getWifiState();
|
||||||
|
if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
||||||
|
setUIFromWifi();
|
||||||
|
wireRepoSwitchToWebServer();
|
||||||
|
} else {
|
||||||
|
repoSwitch.setChecked(false);
|
||||||
|
repoSwitch.setText(R.string.enable_wifi);
|
||||||
|
repoSwitch.setTextOn(getString(R.string.enabling_wifi));
|
||||||
|
repoSwitch.setTextOff(getString(R.string.enable_wifi));
|
||||||
|
repoSwitch.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
wifiManager.setWifiEnabled(true);
|
||||||
|
/*
|
||||||
|
* Once the wifi is connected to a network, then
|
||||||
|
* WifiStateChangeReceiver will receive notice, and kick off
|
||||||
|
* the process of getting the info about the wifi
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.local_repo_activity, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.menu_settings:
|
||||||
|
startActivityForResult(new Intent(this, PreferencesActivity.class), SET_IP_ADDRESS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (resultCode != Activity.RESULT_OK)
|
||||||
|
return;
|
||||||
|
if (requestCode == SET_IP_ADDRESS) {
|
||||||
|
setUIFromWifi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case 0:
|
||||||
|
repoProgress = new ProgressDialog(this);
|
||||||
|
repoProgress.setMessage("Scanning Apps. Please wait...");
|
||||||
|
repoProgress.setIndeterminate(false);
|
||||||
|
repoProgress.setMax(100);
|
||||||
|
repoProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
repoProgress.setCancelable(false);
|
||||||
|
repoProgress.show();
|
||||||
|
return repoProgress;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wireRepoSwitchToWebServer() {
|
||||||
|
repoSwitch.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (repoSwitch.isChecked()) {
|
||||||
|
FDroidApp.startLocalRepoService(LocalRepoActivity.this);
|
||||||
|
} else {
|
||||||
|
FDroidApp.stopLocalRepoService(LocalRepoActivity.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(14)
|
||||||
|
private void setUIFromWifi() {
|
||||||
|
if (TextUtils.isEmpty(FDroidApp.repo.address))
|
||||||
|
return;
|
||||||
|
// the fingerprint is not useful on the button label
|
||||||
|
String buttonLabel = FDroidApp.repo.address.replaceAll("\\?.*$", "");
|
||||||
|
repoSwitch.setText(buttonLabel);
|
||||||
|
repoSwitch.setTextOn(buttonLabel);
|
||||||
|
repoSwitch.setTextOff(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:// :-(
|
||||||
|
*/
|
||||||
|
final String qrUriString = Utils.getSharingUri(this, FDroidApp.repo).toString()
|
||||||
|
.replaceFirst("fdroidrepo", "http")
|
||||||
|
.replaceAll("ssid=[^?]*", "")
|
||||||
|
.toUpperCase(Locale.ENGLISH);
|
||||||
|
Log.i("QRURI", qrUriString);
|
||||||
|
new QrGenAsyncTask(this, R.id.repoQrCode).execute(qrUriString);
|
||||||
|
|
||||||
|
TextView wifiNetworkNameTextView = (TextView) findViewById(R.id.wifiNetworkName);
|
||||||
|
wifiNetworkNameTextView.setText(FDroidApp.ssid);
|
||||||
|
|
||||||
|
TextView fingerprintTextView = (TextView) findViewById(R.id.fingerprint);
|
||||||
|
if (FDroidApp.repo.fingerprint != null) {
|
||||||
|
fingerprintTextView.setVisibility(View.VISIBLE);
|
||||||
|
fingerprintTextView.setText(FDroidApp.repo.fingerprint);
|
||||||
|
} else {
|
||||||
|
fingerprintTextView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the required NFC API was added in 4.0 aka Ice Cream Sandwich
|
||||||
|
if (Build.VERSION.SDK_INT >= 14) {
|
||||||
|
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
|
if (nfcAdapter == null)
|
||||||
|
return;
|
||||||
|
nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] {
|
||||||
|
NdefRecord.createUri(Utils.getSharingUri(this, FDroidApp.repo)),
|
||||||
|
}), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
// ignore orientation/keyboard change
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user