mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-16 10:56:50 -04:00
Merge pull request #1327 from kiwix/iadeelzafar/wifi-hotspot
Wifi Hotspot Feature
This commit is contained in:
commit
e7c3d5dcdf
@ -130,6 +130,7 @@ dependencies {
|
||||
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||
implementation "io.objectbox:objectbox-kotlin:$objectboxVersion"
|
||||
implementation "io.objectbox:objectbox-rxjava:$objectboxVersion"
|
||||
implementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:5.4.2"
|
||||
testImplementation "io.mockk:mockk:1.9"
|
||||
@ -248,9 +249,9 @@ android {
|
||||
'LogConditional'
|
||||
|
||||
warning 'UnknownNullness',
|
||||
'SelectableText',
|
||||
'IconDensities',
|
||||
'SyntheticAccessor'
|
||||
'SelectableText',
|
||||
'IconDensities',
|
||||
'SyntheticAccessor'
|
||||
baseline file("lint-baseline.xml")
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto"
|
||||
package="org.kiwix.kiwixmobile">
|
||||
|
||||
@ -7,11 +8,18 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<!-- Devices with version >= Oreo need location permission to start/stop the hotspot -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<!-- Device with versions >= Pie need this permission -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
|
||||
<application
|
||||
android:name=".KiwixApplication"
|
||||
android:allowBackup="true"
|
||||
@ -140,6 +148,7 @@
|
||||
android:name=".zim_manager.ZimManageActivity"
|
||||
android:label="@string/choose_file"
|
||||
android:launchMode="singleTop">
|
||||
|
||||
<!-- TODO -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT"/>
|
||||
@ -159,6 +168,7 @@
|
||||
<activity android:name=".settings.KiwixSettingsActivity"/>
|
||||
<activity android:name=".search.SearchActivity"/>
|
||||
<activity android:name=".bookmark.BookmarksActivity"/>
|
||||
<activity android:name=".webserver.ZimHostActivity"/>
|
||||
|
||||
<provider
|
||||
android:name=".data.ZimContentProvider"
|
||||
@ -179,6 +189,8 @@
|
||||
android:resource="@xml/kiwix_widget_provider_info"/>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".wifi_hotspot.HotspotService"/>
|
||||
|
||||
<activity
|
||||
android:name=".error.ErrorActivity"
|
||||
android:process=":error_activity"/>
|
||||
@ -193,7 +205,6 @@
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
|
||||
|
||||
<activity android:name=".intro.IntroActivity"/>
|
||||
<activity android:name=".language.LanguageActivity"/>
|
||||
<activity android:name=".history.HistoryActivity"/>
|
||||
@ -202,12 +213,14 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="main.MainActivity"/>
|
||||
</activity>
|
||||
<activity android:name=".zim_manager.local_file_transfer.LocalFileTransferActivity"
|
||||
<activity
|
||||
android:name=".zim_manager.local_file_transfer.LocalFileTransferActivity"
|
||||
android:label="Send to nearby device"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
<data android:pathPattern=".*\\.zim"/>
|
||||
<data android:pathPattern=".*\\..*\\.zim"/>
|
||||
@ -223,4 +236,5 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
25
app/src/main/java/org/kiwix/kiwixmobile/di/ServiceScope.kt
Normal file
25
app/src/main/java/org/kiwix/kiwixmobile/di/ServiceScope.kt
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (C) 2018 Kiwix <android.kiwix.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.kiwix.kiwixmobile.di
|
||||
|
||||
import javax.inject.Scope
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
|
||||
@Scope
|
||||
@Retention(RUNTIME)
|
||||
annotation class ServiceScope
|
@ -54,6 +54,8 @@ public interface ApplicationComponent {
|
||||
|
||||
ActivityComponent.Builder activityComponent();
|
||||
|
||||
ServiceComponent.Builder serviceComponent();
|
||||
|
||||
void inject(KiwixApplication application);
|
||||
|
||||
void inject(DownloadService service);
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.kiwix.kiwixmobile.di.components
|
||||
|
||||
import android.app.Service
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import org.kiwix.kiwixmobile.di.ServiceScope
|
||||
import org.kiwix.kiwixmobile.di.modules.ServiceModule
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.HotspotService
|
||||
|
||||
@Subcomponent(modules = [ServiceModule::class])
|
||||
@ServiceScope
|
||||
interface ServiceComponent {
|
||||
fun inject(hotspotService: HotspotService)
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance fun service(service: Service): Builder
|
||||
|
||||
fun build(): ServiceComponent
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ import org.kiwix.kiwixmobile.main.MainModule;
|
||||
import org.kiwix.kiwixmobile.search.SearchActivity;
|
||||
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
|
||||
import org.kiwix.kiwixmobile.splash.SplashActivity;
|
||||
import org.kiwix.kiwixmobile.webserver.ZimHostActivity;
|
||||
import org.kiwix.kiwixmobile.webserver.ZimHostModule;
|
||||
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
|
||||
|
||||
/**
|
||||
@ -70,4 +72,8 @@ public abstract class ActivityBindingModule {
|
||||
@PerActivity
|
||||
@ContributesAndroidInjector
|
||||
public abstract HelpActivity provideHelpActivity();
|
||||
|
||||
@PerActivity
|
||||
@ContributesAndroidInjector(modules = ZimHostModule.class)
|
||||
public abstract ZimHostActivity provideZimHostActivity();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package org.kiwix.kiwixmobile.di.modules;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javax.inject.Singleton;
|
||||
@ -30,7 +31,7 @@ import org.kiwix.kiwixlib.JNIKiwix;
|
||||
@Module public class JNIModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
public JNIKiwix providesJNIKiwix(Context context) {
|
||||
public JNIKiwix providesJNIKiwix(@NonNull Context context) {
|
||||
return new JNIKiwix(context);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package org.kiwix.kiwixmobile.di.modules
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.net.wifi.WifiManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.kiwix.kiwixlib.JNIKiwixLibrary
|
||||
import org.kiwix.kiwixlib.JNIKiwixServer
|
||||
import org.kiwix.kiwixmobile.di.ServiceScope
|
||||
import org.kiwix.kiwixmobile.webserver.WebServerHelper
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.HotspotNotificationManager
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.HotspotStateListener
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.IpAddressCallbacks
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.WifiHotspotManager
|
||||
|
||||
@Module
|
||||
class ServiceModule {
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesWebServerHelper(
|
||||
jniKiwixLibrary: JNIKiwixLibrary,
|
||||
kiwixServer: JNIKiwixServer,
|
||||
ipAddressCallbacks: IpAddressCallbacks
|
||||
): WebServerHelper = WebServerHelper(jniKiwixLibrary, kiwixServer, ipAddressCallbacks)
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesWifiHotspotManager(
|
||||
wifiManager: WifiManager,
|
||||
hotspotStateListener: HotspotStateListener
|
||||
): WifiHotspotManager =
|
||||
WifiHotspotManager(wifiManager, hotspotStateListener)
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesHotspotStateListener(service: Service): HotspotStateListener =
|
||||
service as HotspotStateListener
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesIpAddressCallbacks(service: Service): IpAddressCallbacks =
|
||||
service as IpAddressCallbacks
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesJNIKiwixLibrary(): JNIKiwixLibrary = JNIKiwixLibrary()
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesJNIKiwixServer(jniKiwixLibrary: JNIKiwixLibrary): JNIKiwixServer =
|
||||
JNIKiwixServer(jniKiwixLibrary)
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesWifiManager(context: Application): WifiManager =
|
||||
context.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
|
||||
@Provides
|
||||
@ServiceScope
|
||||
fun providesHotspotNotificationManager(
|
||||
notificationManager: NotificationManager,
|
||||
context: Context
|
||||
): HotspotNotificationManager =
|
||||
HotspotNotificationManager(notificationManager, context)
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
@ -111,6 +112,7 @@ import org.kiwix.kiwixmobile.utils.LanguageUtils;
|
||||
import org.kiwix.kiwixmobile.utils.NetworkUtils;
|
||||
import org.kiwix.kiwixmobile.utils.StyleUtils;
|
||||
import org.kiwix.kiwixmobile.utils.files.FileUtils;
|
||||
import org.kiwix.kiwixmobile.webserver.ZimHostActivity;
|
||||
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate;
|
||||
@ -939,6 +941,12 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
Intent intentSupportKiwix = new Intent(Intent.ACTION_VIEW, uriSupportKiwix);
|
||||
intentSupportKiwix.putExtra(EXTRA_EXTERNAL_LINK, true);
|
||||
openExternalUrl(intentSupportKiwix);
|
||||
break;
|
||||
|
||||
case R.id.menu_host_books:
|
||||
Intent intent = new Intent(MainActivity.this, ZimHostActivity.class);
|
||||
startActivity(intent);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
@ -1687,6 +1695,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1756,6 +1765,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
|
||||
menu.findItem(R.id.menu_home).setVisible(false);
|
||||
menu.findItem(R.id.menu_random_article).setVisible(false);
|
||||
menu.findItem(R.id.menu_searchintext).setVisible(false);
|
||||
menu.findItem(R.id.menu_host_books).setVisible(true);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_read_aloud).setVisible(true);
|
||||
menu.findItem(R.id.menu_home).setVisible(true);
|
||||
|
@ -3,6 +3,7 @@ package org.kiwix.kiwixmobile.utils
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import org.kiwix.kiwixmobile.R
|
||||
import org.kiwix.kiwixmobile.utils.KiwixDialog.StartHotspotManually
|
||||
import javax.inject.Inject
|
||||
|
||||
class AlertDialogShower @Inject constructor(
|
||||
@ -27,9 +28,17 @@ class AlertDialogShower @Inject constructor(
|
||||
clickListeners.getOrNull(0)
|
||||
?.invoke()
|
||||
}
|
||||
setNegativeButton(dialog.negativeMessage) { _, _ ->
|
||||
clickListeners.getOrNull(1)
|
||||
?.invoke()
|
||||
dialog.negativeMessage?.let {
|
||||
setNegativeButton(it) { _, _ ->
|
||||
clickListeners.getOrNull(1)
|
||||
?.invoke()
|
||||
}
|
||||
}
|
||||
if (dialog is StartHotspotManually) {
|
||||
setNeutralButton(dialog.neutralMessage) { _, _ ->
|
||||
clickListeners.getOrNull(2)
|
||||
?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
@ -126,6 +126,8 @@ public final class Constants {
|
||||
|
||||
public static final String EXTRA_NOTIFICATION_ID = "notificationID";
|
||||
|
||||
public static final String HOTSPOT_SERVICE_CHANNEL_ID = "hotspotService";
|
||||
|
||||
public static final String EXTRA_WEBVIEWS_LIST = "webviewsList";
|
||||
|
||||
public static final String EXTRA_SEARCH_TEXT = "searchText";
|
||||
|
@ -1,13 +1,15 @@
|
||||
package org.kiwix.kiwixmobile.utils
|
||||
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import org.kiwix.kiwixmobile.R
|
||||
import org.kiwix.kiwixmobile.R.string
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
|
||||
|
||||
sealed class KiwixDialog(
|
||||
val title: Int?,
|
||||
val message: Int,
|
||||
val positiveMessage: Int,
|
||||
val negativeMessage: Int
|
||||
val negativeMessage: Int?
|
||||
) {
|
||||
|
||||
data class DeleteZim(override val args: Array<out Any>) : KiwixDialog(
|
||||
@ -30,23 +32,55 @@ sealed class KiwixDialog(
|
||||
}
|
||||
|
||||
object LocationPermissionRationale : KiwixDialog(
|
||||
null, R.string.permission_rationale_location, android.R.string.yes, android.R.string.cancel
|
||||
null,
|
||||
R.string.permission_rationale_location,
|
||||
android.R.string.yes,
|
||||
android.R.string.cancel
|
||||
)
|
||||
|
||||
object StoragePermissionRationale : KiwixDialog(
|
||||
null, R.string.request_storage, android.R.string.yes, android.R.string.cancel
|
||||
null, R.string.request_storage, android.R.string.yes, android.R.string.cancel
|
||||
)
|
||||
|
||||
object EnableWifiP2pServices : KiwixDialog(
|
||||
null, R.string.request_enable_wifi, R.string.yes, android.R.string.no
|
||||
null, R.string.request_enable_wifi, R.string.yes, android.R.string.no
|
||||
)
|
||||
|
||||
object EnableLocationServices : KiwixDialog(
|
||||
null, R.string.request_enable_location, R.string.yes, android.R.string.no
|
||||
null, R.string.request_enable_location, R.string.yes, android.R.string.no
|
||||
)
|
||||
|
||||
object TurnOffHotspotManually : KiwixDialog(
|
||||
R.string.hotspot_failed_title,
|
||||
R.string.hotspot_failed_message,
|
||||
R.string.go_to_wifi_settings_label,
|
||||
null
|
||||
)
|
||||
|
||||
data class ShowHotspotDetails(override val args: Array<out Any>) : KiwixDialog(
|
||||
R.string.hotspot_turned_on,
|
||||
R.string.hotspot_details_message,
|
||||
android.R.string.ok,
|
||||
null
|
||||
), HasBodyFormatArgs {
|
||||
constructor(wifiConfiguration: WifiConfiguration) : this(
|
||||
arrayOf(
|
||||
wifiConfiguration.SSID,
|
||||
wifiConfiguration.preSharedKey
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data class StartHotspotManually(val neutralMessage: Int = string.hotspot_dialog_neutral_button) :
|
||||
KiwixDialog(
|
||||
string.hotspot_dialog_title,
|
||||
string.hotspot_dialog_message,
|
||||
string.go_to_settings_label,
|
||||
null
|
||||
)
|
||||
|
||||
data class FileTransferConfirmation(override val args: Array<out Any>) : KiwixDialog(
|
||||
null, R.string.transfer_to, R.string.yes, android.R.string.cancel
|
||||
null, R.string.transfer_to, R.string.yes, android.R.string.cancel
|
||||
), HasBodyFormatArgs {
|
||||
constructor(selectedPeerDeviceName: String) : this(arrayOf(selectedPeerDeviceName))
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
package org.kiwix.kiwixmobile.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class ServerUtils {
|
||||
public static int port;
|
||||
public static boolean isServerStarted;
|
||||
public static final String INVALID_IP = "-1";
|
||||
|
||||
// get Ip address of the device's wireless access point i.e. wifi hotspot OR wifi network
|
||||
@Nullable public static String getIpAddress() {
|
||||
String ip = "";
|
||||
try {
|
||||
Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
|
||||
.getNetworkInterfaces();
|
||||
while (enumNetworkInterfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = enumNetworkInterfaces
|
||||
.nextElement();
|
||||
Enumeration<InetAddress> enumInetAddress = networkInterface
|
||||
.getInetAddresses();
|
||||
while (enumInetAddress.hasMoreElements()) {
|
||||
InetAddress inetAddress = enumInetAddress.nextElement();
|
||||
|
||||
if (inetAddress.isSiteLocalAddress()) {
|
||||
ip += inetAddress.getHostAddress() + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
//To remove extra characters from IP for Android Pie
|
||||
if (ip.length() > 14) {
|
||||
for (int i = 15; i < 18; i++) {
|
||||
if ((ip.charAt(i) == '.')) {
|
||||
ip = ip.substring(0, i - 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
ip += "Something Wrong! " + e.toString() + "\n";
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
@NonNull public static String getSocketAddress() {
|
||||
String address = "http://" + getIpAddress() + ":" + port;
|
||||
address = address.replaceAll("\n", "");
|
||||
return address;
|
||||
}
|
||||
|
||||
@Nullable public static String getIp() {
|
||||
String ip = getIpAddress();
|
||||
ip = ip.replaceAll("\n", "");
|
||||
return ip.length() == 0 ? INVALID_IP : ip;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
public interface LocationCallbacks {
|
||||
void onLocationSet();
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.gms.common.api.ApiException;
|
||||
import com.google.android.gms.common.api.ResolvableApiException;
|
||||
import com.google.android.gms.location.LocationRequest;
|
||||
import com.google.android.gms.location.LocationSettingsRequest;
|
||||
import com.google.android.gms.location.LocationSettingsResponse;
|
||||
import com.google.android.gms.location.LocationSettingsStates;
|
||||
import com.google.android.gms.location.LocationSettingsStatusCodes;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LocationServicesHelper {
|
||||
private static final String TAG = "LocationServicesHelper";
|
||||
private final LocationCallbacks locationCallbacks;
|
||||
private final Activity activity;
|
||||
private static final int LOCATION_SETTINGS_PERMISSION_RESULT = 101;
|
||||
|
||||
@Inject
|
||||
public LocationServicesHelper(@NonNull Activity activity,
|
||||
@NonNull LocationCallbacks locationCallbacks) {
|
||||
this.activity = activity;
|
||||
this.locationCallbacks = locationCallbacks;
|
||||
}
|
||||
|
||||
private Task<LocationSettingsResponse> task;
|
||||
|
||||
public void setupLocationServices() {
|
||||
LocationRequest locationRequest = new LocationRequest();
|
||||
locationRequest.setInterval(10);
|
||||
locationRequest.setSmallestDisplacement(10);
|
||||
locationRequest.setFastestInterval(10);
|
||||
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
|
||||
LocationSettingsRequest.Builder builder = new
|
||||
LocationSettingsRequest.Builder();
|
||||
builder.addLocationRequest(locationRequest);
|
||||
|
||||
task = com.google.android.gms.location.LocationServices.getSettingsClient(activity)
|
||||
.checkLocationSettings(builder.build());
|
||||
|
||||
locationSettingsResponseBuilder();
|
||||
}
|
||||
|
||||
private void locationSettingsResponseBuilder() {
|
||||
task.addOnCompleteListener(task -> {
|
||||
try {
|
||||
LocationSettingsResponse response = task.getResult(ApiException.class);
|
||||
// All location settings are satisfied. The client can initialize location
|
||||
// requests here.
|
||||
|
||||
locationCallbacks.onLocationSet();
|
||||
//}
|
||||
} catch (ApiException exception) {
|
||||
switch (exception.getStatusCode()) {
|
||||
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
|
||||
// Location settings are not satisfied. But could be fixed by showing the
|
||||
// user a dialog.
|
||||
try {
|
||||
// Cast to a resolvable exception.
|
||||
ResolvableApiException resolvable = (ResolvableApiException) exception;
|
||||
// Show the dialog by calling startResolutionForResult(),
|
||||
// and check the result in onActivityResult().
|
||||
resolvable.startResolutionForResult(
|
||||
activity,
|
||||
LOCATION_SETTINGS_PERMISSION_RESULT);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
// Ignore the error.
|
||||
} catch (ClassCastException e) {
|
||||
// Ignore, should be an impossible error.
|
||||
}
|
||||
break;
|
||||
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
|
||||
// Location settings are not satisfied. However, we have no way to fix the
|
||||
// settings so we won't show the dialog.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data) {
|
||||
//Checking the result code for LocationSettings resolution
|
||||
if (requestCode == LOCATION_SETTINGS_PERMISSION_RESULT) {
|
||||
final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK:
|
||||
// All required changes were successfully made
|
||||
Log.v(TAG, states.isLocationPresent() + "");
|
||||
locationCallbacks.onLocationSet();
|
||||
break;
|
||||
case Activity.RESULT_CANCELED:
|
||||
// The user was asked to change settings, but chose not to
|
||||
Log.v(TAG, "Canceled");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
import org.kiwix.kiwixlib.JNIKiwixLibrary;
|
||||
import org.kiwix.kiwixlib.JNIKiwixServer;
|
||||
import org.kiwix.kiwixmobile.utils.ServerUtils;
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.IpAddressCallbacks;
|
||||
|
||||
import static org.kiwix.kiwixmobile.utils.ServerUtils.INVALID_IP;
|
||||
|
||||
/**
|
||||
* WebServerHelper class is used to set up the suitable environment i.e. getting the
|
||||
* ip address and port no. before starting the WebServer
|
||||
* Created by Adeel Zafar on 18/07/2019.
|
||||
*/
|
||||
|
||||
public class WebServerHelper {
|
||||
private static final String TAG = "WebServerHelper";
|
||||
private JNIKiwixLibrary kiwixLibrary;
|
||||
private JNIKiwixServer kiwixServer;
|
||||
private IpAddressCallbacks ipAddressCallbacks;
|
||||
private boolean isServerStarted;
|
||||
|
||||
@Inject public WebServerHelper(@NonNull JNIKiwixLibrary kiwixLibrary,
|
||||
@NonNull JNIKiwixServer kiwixServer, @NonNull IpAddressCallbacks ipAddressCallbacks) {
|
||||
this.kiwixLibrary = kiwixLibrary;
|
||||
this.kiwixServer = kiwixServer;
|
||||
this.ipAddressCallbacks = ipAddressCallbacks;
|
||||
}
|
||||
|
||||
public boolean startServerHelper(@NonNull ArrayList<String> selectedBooksPath) {
|
||||
String ip = ServerUtils.getIpAddress();
|
||||
if (ip.length() == 0) {
|
||||
return false;
|
||||
} else if (startAndroidWebServer(selectedBooksPath)) {
|
||||
return true;
|
||||
}
|
||||
return isServerStarted;
|
||||
}
|
||||
|
||||
public void stopAndroidWebServer() {
|
||||
if (isServerStarted) {
|
||||
kiwixServer.stop();
|
||||
updateServerState(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startAndroidWebServer(ArrayList<String> selectedBooksPath) {
|
||||
if (!isServerStarted) {
|
||||
int DEFAULT_PORT = 8080;
|
||||
ServerUtils.port = DEFAULT_PORT;
|
||||
for (String path : selectedBooksPath) {
|
||||
try {
|
||||
boolean isBookAdded = kiwixLibrary.addBook(path);
|
||||
Log.v(TAG, "isBookAdded: " + isBookAdded + path);
|
||||
} catch (JNIKiwixException e) {
|
||||
Log.v(TAG, "Couldn't add book " + path);
|
||||
}
|
||||
}
|
||||
kiwixServer.setPort(ServerUtils.port);
|
||||
updateServerState(kiwixServer.start());
|
||||
Log.v(TAG, "Server status" + isServerStarted);
|
||||
}
|
||||
return isServerStarted;
|
||||
}
|
||||
|
||||
private void updateServerState(boolean isStarted) {
|
||||
isServerStarted = isStarted;
|
||||
ServerUtils.isServerStarted = isStarted;
|
||||
}
|
||||
|
||||
//Keeps checking if hotspot has been turned using the ip address with an interval of 1 sec
|
||||
//If no ip is found after 15 seconds, dismisses the progress dialog
|
||||
public void pollForValidIpAddress() {
|
||||
Flowable.interval(1, TimeUnit.SECONDS)
|
||||
.map(__ -> ServerUtils.getIp())
|
||||
.filter(s -> s != INVALID_IP)
|
||||
.timeout(15, TimeUnit.SECONDS)
|
||||
.take(1)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
s -> {
|
||||
ipAddressCallbacks.onIpAddressValid();
|
||||
Log.d(TAG, "onSuccess: " + s);
|
||||
},
|
||||
e -> {
|
||||
Log.d(TAG, "Unable to turn on server", e);
|
||||
ipAddressCallbacks.onIpAddressInvalid();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function0;
|
||||
import org.kiwix.kiwixmobile.R;
|
||||
import org.kiwix.kiwixmobile.base.BaseActivity;
|
||||
import org.kiwix.kiwixmobile.utils.AlertDialogShower;
|
||||
import org.kiwix.kiwixmobile.utils.KiwixDialog;
|
||||
import org.kiwix.kiwixmobile.utils.ServerUtils;
|
||||
import org.kiwix.kiwixmobile.wifi_hotspot.HotspotService;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_CHECK_IP_ADDRESS;
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_LOCATION_ACCESS_GRANTED;
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_START_SERVER;
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_STOP_SERVER;
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_TOGGLE_HOTSPOT;
|
||||
|
||||
public class ZimHostActivity extends BaseActivity implements
|
||||
ZimHostCallbacks, ZimHostContract.View, LocationCallbacks {
|
||||
|
||||
@BindView(R.id.startServerButton)
|
||||
Button startServerButton;
|
||||
@BindView(R.id.server_textView)
|
||||
TextView serverTextView;
|
||||
@BindView(R.id.recycler_view_zim_host)
|
||||
RecyclerView recyclerViewZimHost;
|
||||
|
||||
@Inject
|
||||
ZimHostContract.Presenter presenter;
|
||||
|
||||
@Inject
|
||||
AlertDialogShower alertDialogShower;
|
||||
|
||||
@Inject
|
||||
LocationServicesHelper locationServicesHelper;
|
||||
|
||||
private static final String TAG = "ZimHostActivity";
|
||||
private static final int MY_PERMISSIONS_ACCESS_FINE_LOCATION = 102;
|
||||
private static final String IP_STATE_KEY = "ip_state_key";
|
||||
public static final String SELECTED_ZIM_PATHS_KEY = "selected_zim_paths";
|
||||
|
||||
private BooksOnDiskAdapter booksAdapter;
|
||||
private BookOnDiskDelegate.BookDelegate bookDelegate;
|
||||
private HotspotService hotspotService;
|
||||
private String ip;
|
||||
private ServiceConnection serviceConnection;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_zim_host);
|
||||
|
||||
setUpToolbar();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
ip = savedInstanceState.getString(IP_STATE_KEY);
|
||||
layoutServerStarted();
|
||||
}
|
||||
bookDelegate =
|
||||
new BookOnDiskDelegate.BookDelegate(sharedPreferenceUtil,
|
||||
null,
|
||||
null,
|
||||
bookOnDiskItem -> {
|
||||
select(bookOnDiskItem);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
bookDelegate.setSelectionMode(SelectionMode.MULTI);
|
||||
booksAdapter = new BooksOnDiskAdapter(bookDelegate,
|
||||
BookOnDiskDelegate.LanguageDelegate.INSTANCE
|
||||
);
|
||||
|
||||
presenter.attachView(this);
|
||||
|
||||
presenter.loadBooks();
|
||||
recyclerViewZimHost.setAdapter(booksAdapter);
|
||||
|
||||
serviceConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
hotspotService = ((HotspotService.HotspotBinder) service).getService();
|
||||
hotspotService.registerCallBack(ZimHostActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName arg0) {
|
||||
}
|
||||
};
|
||||
|
||||
startServerButton.setOnClickListener(v -> {
|
||||
//Get the path of ZIMs user has selected
|
||||
if (!ServerUtils.isServerStarted) {
|
||||
if (getSelectedBooksPath().size() > 0) {
|
||||
startHotspotHelper();
|
||||
} else {
|
||||
Toast.makeText(ZimHostActivity.this, R.string.no_books_selected_toast_message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
startHotspotHelper();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startHotspotHelper() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
toggleHotspot();
|
||||
} else {
|
||||
if (ServerUtils.isServerStarted) {
|
||||
startService(createHotspotIntent(ACTION_STOP_SERVER));
|
||||
} else {
|
||||
startHotspotManuallyDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<String> getSelectedBooksPath() {
|
||||
ArrayList<String> selectedBooksPath = new ArrayList<>();
|
||||
for (BooksOnDiskListItem item : booksAdapter.getItems()) {
|
||||
if (item.isSelected()) {
|
||||
BooksOnDiskListItem.BookOnDisk bookOnDisk = (BooksOnDiskListItem.BookOnDisk) item;
|
||||
File file = bookOnDisk.getFile();
|
||||
selectedBooksPath.add(file.getAbsolutePath());
|
||||
Log.v(TAG, "ZIM PATH : " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return selectedBooksPath;
|
||||
}
|
||||
|
||||
private void select(@NonNull BooksOnDiskListItem.BookOnDisk bookOnDisk) {
|
||||
ArrayList<BooksOnDiskListItem> booksList = new ArrayList<>();
|
||||
for (BooksOnDiskListItem item : booksAdapter.getItems()) {
|
||||
if (item.equals(bookOnDisk)) {
|
||||
item.setSelected(!item.isSelected());
|
||||
}
|
||||
booksList.add(item);
|
||||
}
|
||||
booksAdapter.setItems(booksList);
|
||||
}
|
||||
|
||||
@Override protected void onStart() {
|
||||
super.onStart();
|
||||
bindService();
|
||||
}
|
||||
|
||||
@Override protected void onStop() {
|
||||
super.onStop();
|
||||
unbindService();
|
||||
}
|
||||
|
||||
private void bindService() {
|
||||
bindService(new Intent(this, HotspotService.class), serviceConnection,
|
||||
Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void unbindService() {
|
||||
if (hotspotService != null) {
|
||||
unbindService(serviceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleHotspot() {
|
||||
//Check if location permissions are granted
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
//Toggle hotspot if location permissions are granted
|
||||
startService(createHotspotIntent(
|
||||
ACTION_TOGGLE_HOTSPOT));
|
||||
} else {
|
||||
//Ask location permission if not granted
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[] { Manifest.permission.ACCESS_FINE_LOCATION },
|
||||
MY_PERMISSIONS_ACCESS_FINE_LOCATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void onResume() {
|
||||
super.onResume();
|
||||
presenter.loadBooks();
|
||||
if (ServerUtils.isServerStarted) {
|
||||
ip = ServerUtils.getSocketAddress();
|
||||
layoutServerStarted();
|
||||
}
|
||||
}
|
||||
|
||||
private void layoutServerStarted() {
|
||||
serverTextView.setText(getString(R.string.server_started_message, ip));
|
||||
startServerButton.setText(getString(R.string.stop_server_label));
|
||||
startServerButton.setBackgroundColor(getResources().getColor(R.color.stopServer));
|
||||
bookDelegate.setSelectionMode(SelectionMode.NORMAL);
|
||||
for (BooksOnDiskListItem item : booksAdapter.getItems()) {
|
||||
item.setSelected(false);
|
||||
}
|
||||
booksAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void layoutServerStopped() {
|
||||
serverTextView.setText(getString(R.string.server_textview_default_message));
|
||||
startServerButton.setText(getString(R.string.start_server_label));
|
||||
startServerButton.setBackgroundColor(getResources().getColor(R.color.greenTick));
|
||||
bookDelegate.setSelectionMode(SelectionMode.MULTI);
|
||||
booksAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
if (requestCode == MY_PERMISSIONS_ACCESS_FINE_LOCATION) {
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
toggleHotspot();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
locationServicesHelper.onActivityResult(requestCode, resultCode, (data));
|
||||
}
|
||||
|
||||
@Override protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
presenter.detachView();
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setTitle(getString(R.string.menu_host_books));
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
toolbar.setNavigationOnClickListener(v -> onBackPressed());
|
||||
}
|
||||
|
||||
//Advice user to turn on hotspot manually for API<26
|
||||
private void startHotspotManuallyDialog() {
|
||||
|
||||
alertDialogShower.show(new KiwixDialog.StartHotspotManually(),
|
||||
() -> {
|
||||
launchTetheringSettingsScreen();
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
null,
|
||||
() -> {
|
||||
progressDialog =
|
||||
ProgressDialog.show(this,
|
||||
getString(R.string.progress_dialog_starting_server), "",
|
||||
true);
|
||||
startService(createHotspotIntent(ACTION_CHECK_IP_ADDRESS));
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Intent createHotspotIntent(String action) {
|
||||
return new Intent(this, HotspotService.class).setAction(action);
|
||||
}
|
||||
|
||||
@Override public void onServerStarted(@NonNull String ipAddress) {
|
||||
this.ip = ipAddress;
|
||||
layoutServerStarted();
|
||||
}
|
||||
|
||||
@Override public void onServerStopped() {
|
||||
layoutServerStopped();
|
||||
}
|
||||
|
||||
@Override public void onServerFailedToStart() {
|
||||
Toast.makeText(this, R.string.server_failed_toast_message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override public void onHotspotTurnedOn(@NonNull WifiConfiguration wifiConfiguration) {
|
||||
alertDialogShower.show(new KiwixDialog.ShowHotspotDetails(wifiConfiguration),
|
||||
(Function0<Unit>) () -> {
|
||||
progressDialog =
|
||||
ProgressDialog.show(this,
|
||||
getString(R.string.progress_dialog_starting_server), "",
|
||||
true);
|
||||
startService(createHotspotIntent(ACTION_CHECK_IP_ADDRESS));
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
private void launchTetheringSettingsScreen() {
|
||||
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
final ComponentName cn =
|
||||
new ComponentName("com.android.settings", "com.android.settings.TetherSettings");
|
||||
intent.setComponent(cn);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override public void onHotspotFailedToStart() {
|
||||
//Show a dialog to turn off default hotspot
|
||||
alertDialogShower.show(KiwixDialog.TurnOffHotspotManually.INSTANCE,
|
||||
(Function0<Unit>) () -> {
|
||||
launchTetheringSettingsScreen();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
@Override public void requestLocationAccess() {
|
||||
locationServicesHelper.setupLocationServices();
|
||||
}
|
||||
|
||||
@Override protected void onSaveInstanceState(@Nullable Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (ServerUtils.isServerStarted) {
|
||||
outState.putString(IP_STATE_KEY, ip);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void addBooks(@Nullable List<BooksOnDiskListItem> books) {
|
||||
booksAdapter.setItems(books);
|
||||
}
|
||||
|
||||
@Override public void onLocationSet() {
|
||||
startService(createHotspotIntent(ACTION_LOCATION_ACCESS_GRANTED));
|
||||
}
|
||||
|
||||
@Override public void onIpAddressValid() {
|
||||
progressDialog.dismiss();
|
||||
startService(createHotspotIntent(ACTION_START_SERVER).putStringArrayListExtra(
|
||||
SELECTED_ZIM_PATHS_KEY, getSelectedBooksPath()));
|
||||
}
|
||||
|
||||
@Override public void onIpAddressInvalid() {
|
||||
progressDialog.dismiss();
|
||||
Toast.makeText(this, R.string.server_failed_message,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface ZimHostCallbacks {
|
||||
|
||||
void onServerStarted(@NonNull String ip);
|
||||
|
||||
void onServerStopped();
|
||||
|
||||
void onServerFailedToStart();
|
||||
|
||||
void onHotspotTurnedOn(@NonNull WifiConfiguration wifiConfiguration);
|
||||
|
||||
void onHotspotFailedToStart();
|
||||
|
||||
void requestLocationAccess();
|
||||
|
||||
void onIpAddressValid();
|
||||
|
||||
void onIpAddressInvalid();
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import java.util.List;
|
||||
import org.kiwix.kiwixmobile.base.BaseContract;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||
|
||||
class ZimHostContract {
|
||||
|
||||
interface View
|
||||
extends BaseContract.View<org.kiwix.kiwixmobile.webserver.ZimHostContract.Presenter> {
|
||||
void addBooks(List<BooksOnDiskListItem> books);
|
||||
}
|
||||
|
||||
interface Presenter
|
||||
extends BaseContract.Presenter<org.kiwix.kiwixmobile.webserver.ZimHostContract.View> {
|
||||
void loadBooks();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.app.Activity;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.kiwix.kiwixmobile.di.PerActivity;
|
||||
|
||||
@Module
|
||||
public class ZimHostModule {
|
||||
|
||||
@PerActivity
|
||||
@Provides
|
||||
ZimHostContract.Presenter provideZimHostPresenter(ZimHostPresenter zimHostPresenter) {
|
||||
return zimHostPresenter;
|
||||
}
|
||||
|
||||
@PerActivity
|
||||
@Provides Activity providesActivity(ZimHostActivity zimHostActivity) {
|
||||
return zimHostActivity;
|
||||
}
|
||||
|
||||
@PerActivity
|
||||
@Provides LocationServicesHelper providesLocationServicesHelper(ZimHostActivity activity,
|
||||
LocationCallbacks locationCallbacks) {
|
||||
return new LocationServicesHelper(activity, locationCallbacks);
|
||||
}
|
||||
|
||||
@PerActivity
|
||||
@Provides LocationCallbacks providesLocationCallbacks(ZimHostActivity activity) {
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package org.kiwix.kiwixmobile.webserver;
|
||||
|
||||
import android.util.Log;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import org.kiwix.kiwixmobile.base.BasePresenter;
|
||||
import org.kiwix.kiwixmobile.data.DataSource;
|
||||
import org.kiwix.kiwixmobile.di.PerActivity;
|
||||
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
|
||||
|
||||
@PerActivity
|
||||
class ZimHostPresenter extends BasePresenter<ZimHostContract.View>
|
||||
implements ZimHostContract.Presenter {
|
||||
|
||||
private static final String TAG = "ZimHostPresenter";
|
||||
private final DataSource dataSource;
|
||||
|
||||
@Inject ZimHostPresenter(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBooks() {
|
||||
dataSource.getLanguageCategorizedBooks()
|
||||
.subscribe(new SingleObserver<List<BooksOnDiskListItem>>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
compositeDisposable.add(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(List<BooksOnDiskListItem> books) {
|
||||
view.addBooks(books);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "Unable to load books", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.kiwix.kiwixmobile.wifi_hotspot;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import javax.inject.Inject;
|
||||
import org.kiwix.kiwixmobile.R;
|
||||
import org.kiwix.kiwixmobile.utils.Constants;
|
||||
import org.kiwix.kiwixmobile.webserver.ZimHostActivity;
|
||||
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotService.ACTION_STOP;
|
||||
|
||||
public class HotspotNotificationManager {
|
||||
|
||||
public static final int HOTSPOT_NOTIFICATION_ID = 666;
|
||||
private Context context;
|
||||
|
||||
@Inject
|
||||
NotificationManager notificationManager;
|
||||
|
||||
@Inject
|
||||
public HotspotNotificationManager(@NonNull NotificationManager notificationManager,
|
||||
@NonNull Context context) {
|
||||
this.notificationManager = notificationManager;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void hotspotNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel hotspotServiceChannel = new NotificationChannel(
|
||||
Constants.HOTSPOT_SERVICE_CHANNEL_ID,
|
||||
context.getString(R.string.hotspot_service_channel_name),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
hotspotServiceChannel.setDescription(context.getString(R.string.hotspot_channel_description));
|
||||
hotspotServiceChannel.setSound(null, null);
|
||||
notificationManager.createNotificationChannel(hotspotServiceChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull public Notification buildForegroundNotification() {
|
||||
Intent targetIntent = new Intent(context, ZimHostActivity.class);
|
||||
targetIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent contentIntent =
|
||||
PendingIntent.getActivity(context, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
hotspotNotificationChannel();
|
||||
|
||||
Intent stopIntent = new Intent(context, HotspotService.class).setAction(ACTION_STOP);
|
||||
PendingIntent stopHotspot =
|
||||
PendingIntent.getService(context, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
return new NotificationCompat.Builder(context)
|
||||
.setContentTitle(context.getString(R.string.hotspot_notification_content_title))
|
||||
.setContentText(context.getString(R.string.hotspot_running))
|
||||
.setContentIntent(contentIntent)
|
||||
.setSmallIcon(R.mipmap.kiwix_icon)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.addAction(R.drawable.ic_close_white_24dp,
|
||||
context.getString(R.string.stop_hotspot_button),
|
||||
stopHotspot)
|
||||
.setChannelId(Constants.HOTSPOT_SERVICE_CHANNEL_ID)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void dismissNotification() {
|
||||
notificationManager.cancel(HOTSPOT_NOTIFICATION_ID);
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package org.kiwix.kiwixmobile.wifi_hotspot;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import org.kiwix.kiwixmobile.KiwixApplication;
|
||||
import org.kiwix.kiwixmobile.R;
|
||||
import org.kiwix.kiwixmobile.utils.ServerUtils;
|
||||
import org.kiwix.kiwixmobile.webserver.ZimHostCallbacks;
|
||||
import org.kiwix.kiwixmobile.webserver.WebServerHelper;
|
||||
|
||||
import static org.kiwix.kiwixmobile.webserver.ZimHostActivity.SELECTED_ZIM_PATHS_KEY;
|
||||
import static org.kiwix.kiwixmobile.wifi_hotspot.HotspotNotificationManager.HOTSPOT_NOTIFICATION_ID;
|
||||
|
||||
/**
|
||||
* HotspotService is used to add a foreground service for the wifi hotspot.
|
||||
* Created by Adeel Zafar on 07/01/2019.
|
||||
*/
|
||||
|
||||
public class HotspotService extends Service implements HotspotStateListener, IpAddressCallbacks {
|
||||
|
||||
public static final String ACTION_TOGGLE_HOTSPOT = "toggle_hotspot";
|
||||
public static final String ACTION_LOCATION_ACCESS_GRANTED = "location_access_granted";
|
||||
public static final String ACTION_START_SERVER = "start_server";
|
||||
public static final String ACTION_STOP_SERVER = "stop_server";
|
||||
public static final String ACTION_CHECK_IP_ADDRESS = "check_ip_address";
|
||||
|
||||
public static final String ACTION_STOP = "hotspot_stop";
|
||||
private ZimHostCallbacks zimHostCallbacks;
|
||||
private final IBinder serviceBinder = new HotspotBinder();
|
||||
|
||||
@Inject
|
||||
WebServerHelper webServerHelper;
|
||||
|
||||
@Inject
|
||||
WifiHotspotManager hotspotManager;
|
||||
|
||||
@Inject
|
||||
HotspotNotificationManager hotspotNotificationManager;
|
||||
|
||||
@Override public void onCreate() {
|
||||
KiwixApplication.getApplicationComponent()
|
||||
.serviceComponent()
|
||||
.service(this)
|
||||
.build()
|
||||
.inject(this);
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
@Override public int onStartCommand(@NonNull Intent intent, int flags, int startId) {
|
||||
switch (intent.getAction()) {
|
||||
|
||||
case ACTION_TOGGLE_HOTSPOT:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (hotspotManager.isHotspotStarted()) {
|
||||
stopHotspotAndDismissNotification();
|
||||
} else {
|
||||
zimHostCallbacks.requestLocationAccess();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_LOCATION_ACCESS_GRANTED:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
hotspotManager.turnOnHotspot();
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_START_SERVER:
|
||||
if (webServerHelper.startServerHelper(
|
||||
intent.getStringArrayListExtra(SELECTED_ZIM_PATHS_KEY))) {
|
||||
zimHostCallbacks.onServerStarted(ServerUtils.getSocketAddress());
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
startForegroundNotificationHelper();
|
||||
}
|
||||
Toast.makeText(this, R.string.server_started__successfully_toast_message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
zimHostCallbacks.onServerFailedToStart();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
hotspotNotificationManager.dismissNotification();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ACTION_STOP_SERVER:
|
||||
stopHotspotAndDismissNotification();
|
||||
break;
|
||||
|
||||
case ACTION_CHECK_IP_ADDRESS:
|
||||
webServerHelper.pollForValidIpAddress();
|
||||
break;
|
||||
|
||||
case ACTION_STOP:
|
||||
stopHotspotAndDismissNotification();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Nullable @Override public IBinder onBind(@Nullable Intent intent) {
|
||||
return serviceBinder;
|
||||
}
|
||||
|
||||
//Dismiss notification and turn off hotspot for devices>=O
|
||||
private void stopHotspotAndDismissNotification() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
hotspotManager.turnOffHotspot();
|
||||
} else {
|
||||
webServerHelper.stopAndroidWebServer();
|
||||
zimHostCallbacks.onServerStopped();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
hotspotNotificationManager.dismissNotification();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerCallBack(@Nullable ZimHostCallbacks myCallback) {
|
||||
zimHostCallbacks = myCallback;
|
||||
}
|
||||
|
||||
private void startForegroundNotificationHelper() {
|
||||
startForeground(HOTSPOT_NOTIFICATION_ID,
|
||||
hotspotNotificationManager.buildForegroundNotification());
|
||||
}
|
||||
|
||||
@Override public void onHotspotTurnedOn(@NonNull WifiConfiguration wifiConfiguration) {
|
||||
startForegroundNotificationHelper();
|
||||
zimHostCallbacks.onHotspotTurnedOn(wifiConfiguration);
|
||||
}
|
||||
|
||||
@Override public void onHotspotFailedToStart() {
|
||||
zimHostCallbacks.onHotspotFailedToStart();
|
||||
}
|
||||
|
||||
@Override public void onHotspotStopped() {
|
||||
webServerHelper.stopAndroidWebServer();
|
||||
zimHostCallbacks.onServerStopped();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
hotspotNotificationManager.dismissNotification();
|
||||
}
|
||||
|
||||
@Override public void onIpAddressValid() {
|
||||
zimHostCallbacks.onIpAddressValid();
|
||||
}
|
||||
|
||||
@Override public void onIpAddressInvalid() {
|
||||
zimHostCallbacks.onIpAddressInvalid();
|
||||
}
|
||||
|
||||
public class HotspotBinder extends Binder {
|
||||
|
||||
@NonNull public HotspotService getService() {
|
||||
return HotspotService.this;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package org.kiwix.kiwixmobile.wifi_hotspot;
|
||||
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface HotspotStateListener {
|
||||
void onHotspotTurnedOn(@NonNull WifiConfiguration wifiConfiguration);
|
||||
|
||||
void onHotspotFailedToStart();
|
||||
|
||||
void onHotspotStopped();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.kiwix.kiwixmobile.wifi_hotspot;
|
||||
|
||||
public interface IpAddressCallbacks {
|
||||
|
||||
void onIpAddressValid();
|
||||
|
||||
void onIpAddressInvalid();
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package org.kiwix.kiwixmobile.wifi_hotspot;
|
||||
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* WifiHotstopManager class makes use of the Android's WifiManager and WifiConfiguration class
|
||||
* to implement the wifi hotspot feature.
|
||||
* Created by Adeel Zafar on 28/5/2019.
|
||||
*/
|
||||
|
||||
public class WifiHotspotManager {
|
||||
private static final String TAG = "WifiHotspotManager";
|
||||
private WifiManager wifiManager;
|
||||
private WifiManager.LocalOnlyHotspotReservation hotspotReservation;
|
||||
private HotspotStateListener hotspotStateListener;
|
||||
|
||||
@Inject
|
||||
public WifiHotspotManager(@NonNull WifiManager wifiManager,
|
||||
@NonNull HotspotStateListener hotspotStateListener) {
|
||||
this.wifiManager = wifiManager;
|
||||
this.hotspotStateListener = hotspotStateListener;
|
||||
}
|
||||
|
||||
//Workaround to turn on hotspot for Oreo versions
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void turnOnHotspot() {
|
||||
wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {
|
||||
|
||||
@Override
|
||||
public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
|
||||
super.onStarted(reservation);
|
||||
hotspotReservation = reservation;
|
||||
WifiConfiguration currentConfig = hotspotReservation.getWifiConfiguration();
|
||||
|
||||
printCurrentConfig(currentConfig);
|
||||
hotspotStateListener.onHotspotTurnedOn(currentConfig);
|
||||
Log.v(TAG, "Local Hotspot Started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
super.onStopped();
|
||||
hotspotStateListener.onHotspotStopped();
|
||||
Log.v(TAG, "Local Hotspot Stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(int reason) {
|
||||
super.onFailed(reason);
|
||||
hotspotStateListener.onHotspotFailedToStart();
|
||||
Log.v(TAG, "Local Hotspot failed to start");
|
||||
}
|
||||
}, new Handler());
|
||||
}
|
||||
|
||||
//Workaround to turn off hotspot for Oreo versions
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void turnOffHotspot() {
|
||||
if (hotspotReservation != null) {
|
||||
hotspotReservation.close();
|
||||
hotspotReservation = null;
|
||||
hotspotStateListener.onHotspotStopped();
|
||||
Log.v(TAG, "Turned off hotspot");
|
||||
}
|
||||
}
|
||||
|
||||
//This method checks the state of the hostpot for devices>=Oreo
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public boolean isHotspotStarted() {
|
||||
return hotspotReservation != null;
|
||||
}
|
||||
|
||||
private void printCurrentConfig(WifiConfiguration wifiConfiguration) {
|
||||
Log.v(TAG, "THE PASSWORD IS: "
|
||||
+ wifiConfiguration.preSharedKey
|
||||
+ " \n SSID is : "
|
||||
+ wifiConfiguration.SSID);
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ sealed class BookOnDiskDelegate<I : BooksOnDiskListItem, out VH : BookOnDiskView
|
||||
|
||||
class BookDelegate(
|
||||
val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val clickAction: (BookOnDisk) -> Unit,
|
||||
private val clickAction: ((BookOnDisk) -> Unit)? = null,
|
||||
private val longClickAction: ((BookOnDisk) -> Unit)? = null,
|
||||
private val multiSelectAction: ((BookOnDisk) -> Unit)? = null
|
||||
) : BookOnDiskDelegate<BookOnDisk, BookViewHolder>() {
|
||||
|
@ -32,7 +32,7 @@ sealed class BookOnDiskViewHolder<in T : BooksOnDiskListItem>(containerView: Vie
|
||||
class BookViewHolder(
|
||||
containerView: View,
|
||||
private val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||
private val clickAction: (BookOnDisk) -> Unit,
|
||||
private val clickAction: ((BookOnDisk) -> Unit)?,
|
||||
private val longClickAction: ((BookOnDisk) -> Unit)?,
|
||||
private val multiSelectAction: ((BookOnDisk) -> Unit)?
|
||||
) : BookOnDiskViewHolder<BookOnDisk>(containerView) {
|
||||
@ -80,7 +80,7 @@ sealed class BookOnDiskViewHolder<in T : BooksOnDiskListItem>(containerView: Vie
|
||||
}
|
||||
NORMAL -> {
|
||||
itemBookCheckbox.visibility = View.GONE
|
||||
item_book_clickable_area.setOnClickListener { clickAction.invoke(item) }
|
||||
item_book_clickable_area.setOnClickListener { clickAction?.invoke(item) }
|
||||
item_book_clickable_area.setOnLongClickListener {
|
||||
longClickAction?.invoke(item)
|
||||
return@setOnLongClickListener true
|
||||
|
69
app/src/main/res/layout/activity_zim_host.xml
Normal file
69
app/src/main/res/layout/activity_zim_host.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".webserver.ZimHostActivity"
|
||||
>
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/zimHostAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"
|
||||
/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/server_textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/server_textview_default_message"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/zimHostAppBarLayout"
|
||||
/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view_zim_host"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/startServerButton"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/server_textView"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/startServerButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:background="@color/greenTick"
|
||||
android:text="@string/start_server_label"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -79,6 +79,11 @@
|
||||
android:visible="false"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_host_books"
|
||||
android:title="@string/menu_host_books"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_help"
|
||||
android:title="@string/menu_help"
|
||||
|
@ -26,6 +26,7 @@
|
||||
<color name="actionModeBackground">#4285F4</color>
|
||||
<color name="titleBar">#000000</color>
|
||||
<color name="greenTick">#4CAF50</color>
|
||||
<color name="stopServer">#E53935</color>
|
||||
|
||||
<!-- Dark Text Color for Light Background -->
|
||||
<color name="textDarkPrimary">#000000</color> <!-- 0% opacity-->
|
||||
|
@ -14,6 +14,7 @@
|
||||
<string name="menu_read_aloud">Read aloud</string>
|
||||
<string name="menu_read_aloud_stop">Stop reading aloud</string>
|
||||
<string name="menu_support_kiwix">Support Kiwix</string>
|
||||
<string name="menu_host_books">Host Books</string>
|
||||
<string name="save_media">Save Media</string>
|
||||
<string name="save_media_error">An error occurred when trying to save the media!</string>
|
||||
<string name="save_media_saved">Saved media as %s to Android/media/org.kiwix…/</string>
|
||||
@ -21,6 +22,29 @@
|
||||
<string name="search_label">Search</string>
|
||||
<string name="choose_file">Select a Content File (*.zim)</string>
|
||||
<string name="open_in_new_tab">Open link in new tab?</string>
|
||||
<string name="hotspot_service_channel_name">Hotspot Service Channel</string>
|
||||
<string name="hotspot_failed_title">Failed to start hotspot</string>
|
||||
<string name="hotspot_failed_message">It seems like your hotspot is already turned on. Please disable your wifi hotspot to continue.</string>
|
||||
<string name="go_to_wifi_settings_label">Go to WIFI settings</string>
|
||||
<string name="go_to_settings_label">Go to settings</string>
|
||||
<string name="hotspot_running">Running Hotspot</string>
|
||||
<string name="stop_hotspot_button">STOP</string>
|
||||
<string name="no_books_selected_toast_message">Please select books first</string>
|
||||
<string name="server_failed_message">Couldn’t start server. Please turn on your hotspot</string>
|
||||
<string name="server_failed_toast_message">Couldn’t start server.</string>
|
||||
<string name="server_started__successfully_toast_message">Server started successfully.</string>
|
||||
<string name="hotspot_turned_on">Hotspot turned on</string>
|
||||
<string name="hotspot_details_message">Following are the details of your local hotspot. \nSSID : %1$s \nPass : %2$s</string>
|
||||
<string name="server_textview_default_message">Select the files you wish to host on the server</string>
|
||||
<string name="progress_dialog_starting_server">Starting server</string>
|
||||
<string name="hotspot_dialog_title">Turn on your WIFI hotspot</string>
|
||||
<string name="hotspot_dialog_message">In order for this feature to work you need to first turn on your WIFI hotspot manually.</string>
|
||||
<string name="hotspot_dialog_neutral_button">YES, I’VE TURNED IT ON</string>
|
||||
<string name="hotspot_channel_description">Updates about the state of your hotspot/server.</string>
|
||||
<string name="hotspot_notification_content_title">Kiwix Hotspot</string>
|
||||
<string name="start_server_label">Start server</string>
|
||||
<string name="stop_server_label">Stop server</string>
|
||||
<string name="server_started_message">Enter this ip address into your browser to access the server %s</string>
|
||||
<string name="error_file_not_found">Error: The selected ZIM file could not be found.</string>
|
||||
<string name="error_file_invalid">Error: The selected file is not a valid ZIM file.</string>
|
||||
<string name="error_article_url_not_found">Error: Loading article (Url: %1$s) failed.</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user