Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
.cxx
local.properties
*.jks
.env
6 changes: 6 additions & 0 deletions .idea/AndroidProjectSystem.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/markdown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/runConfigurations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/studiobot.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 41 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
# Android Restricted WebView
# Single Site Android Browser

The Android Restricted WebView is an Android application template that allows you to display a specific website using a WebView while restricting access to other sites. If the application attempts to load a URL other than the allowed site, an error message "This URL is not allowed" will be displayed.
הפרויקט מאפשר לך ליצור אפליקציית אנדרואיד עבור כל אתר אינטרנט, עם אפשרות להגביל את הגישה לאתרים באמצעות רשימה לבנה.

## Configuration
In the file [`app/build.gradle`](./app/build.gradle) (editing the file or using env variables).
בנוי כהרחבה למתקדמים לפרויקט:
https://github.com/ShlomoCode/AndroidRestrictedWebView

recompile and redeploy the application for the change to take effect.
### תכונות

## License
- מאפשר רשימה לבנה לא מוגבלת.
- מאפשר לציין כתובת ייחודית שתהיה נקודת ההתחלה (אם לא צוינה, יתחיל בכתובת הראשונה ברשימה הלבנה).
- טיפול ב-URI מותאם של `tel`, `mailto` ו-`whatsapp`.
- כפיית מצב תצוגה.
- חסימת מדיה.
- מצב no ssl.
- הסתרת הבאנר של נטפרי.
- טעינה מחדש של הדף בהחלקה למטה בשליש העליון של המסך.

## Configuration - קונפיגורציה

ניתן לערוך את ההגדרות ישירות ב-[`app/build.gradle`](./app/build.gradle) (או לטעון באמצעות קובץ `env` שממוקם בתיקיית `app`).

לאחר ביצוע השינויים יש לבצע הידור מחדש של הפרויקט כדי לסנכרן את השינויים.

### אפשרויות בקובץ `env`:

```env
ALLOWED_DOMAINS=example.com // כתובות דומיין שיאופשרו באפליקציה
STARTUP_URL=example.com // כתובת האתר שבו תתחיל האפליקציה
ALLOW_GOOGLE_LOGIN=true // אפשר התחברות עם גוגל
VIEW_MODE=AUTO // מצב תצוגה (אפשרי: AUTO, PORTRAIT, LANDSCAPE)
BLOCK_MEDIA=false
BLOCK_ADS=true
NO_SSL=false
HIDE_NETFREE=true // הסתרת הבאנר של נטפרי
APP_NAME=my app
APPLICATION_ID=com.myapp.webapp
VERSION=1.0.0
APP_ICON_PATH= // אייקון לאפליקציה, יש לשים לב שצריך לוכסנים כפולים או הפוכים
```

## License - רישיון

This application is distributed under the GNU General Public License version 3 (GNU GPL-3.0). See the [LICENSE](LICENSE) file for more details.

## Disclaimer
## Disclaimer - כתב ויתור

לידיעתך, אינני עורך דין ואין לראות במידע הניתן כאן ייעוץ משפטי. חשוב להתייעץ עם גורם משפטי מוסמך כדי להבטיח עמידה בכל דרישות הרישוי הרלוונטיות וחובות.

Please note that I am not a lawyer and the information provided here should not be considered legal advice. It is important to consult with a qualified legal professional to ensure compliance with all relevant licensing requirements and obligations.
82 changes: 68 additions & 14 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,53 @@ plugins {
id 'com.android.application'
}

def loadEnvProps() {
def envFile = new File("${project.projectDir}/.env")
def props = new Properties()
if (envFile.exists()) {
envFile.withInputStream { stream ->
props.load(new InputStreamReader(stream, "UTF-8"))
}
}
return props
}

def getEnvValue(key, defaultValue = '') {
def props = loadEnvProps()
return props.getProperty(key, System.getenv(key) ?: defaultValue)
}

android {
signingConfigs {
release {
storeFile = file("keystore/android_keystore.jks")
storePassword System.getenv("SIGNING_STORE_PASSWORD")
keyAlias System.getenv("SIGNING_KEY_ALIAS")
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
storePassword getEnvValue('SIGNING_STORE_PASSWORD')
keyAlias getEnvValue('SIGNING_KEY_ALIAS')
keyPassword getEnvValue('SIGNING_KEY_PASSWORD')
}
}

compileSdkVersion 34

defaultConfig {
def allowGoogleLogin = System.getenv('ALLOW_GOOGLE_LOGIN') == 'true'
def baseDomains = System.getenv('ALLOWED_DOMAINS') ?: 'example.com'
def allowGoogleLogin = getEnvValue('ALLOW_GOOGLE_LOGIN') == 'true'
def baseDomains = getEnvValue('ALLOWED_DOMAINS') ?: 'example.com'
def allowedDomains = allowGoogleLogin ? "${baseDomains},accounts.google.com" : baseDomains
buildConfigField "String", "ALLOWED_DOMAINS", "\"${allowedDomains}\""
buildConfigField "boolean", "ALLOW_GOOGLE_LOGIN", "${allowGoogleLogin}"
buildConfigField "String", "STARTUP_URL", "\"${System.getenv('STARTUP_URL') ?: '' }\""
buildConfigField "String", "VIEW_MODE", "\"${System.getenv('VIEW_MODE') ?: 'AUTO'}\""
buildConfigField "boolean", "BLOCK_MEDIA", "${System.getenv('BLOCK_MEDIA') ?: 'false'}"
buildConfigField "boolean", "BLOCK_ADS", "${System.getenv('BLOCK_ADS') ?: 'true'}"
buildConfigField "boolean", "NO_SSL", "${System.getenv('NO_SSL') ?: 'false'}"
resValue "string", "app_name", System.getenv('APP_NAME') ?: "My Application"
applicationId System.getenv('APPLICATION_ID') ?: "com.webview.myapplication"
buildConfigField "String", "STARTUP_URL", "\"${getEnvValue('STARTUP_URL') ?: '' }\""
buildConfigField "String", "VIEW_MODE", "\"${getEnvValue('VIEW_MODE') ?: 'AUTO'}\""
buildConfigField "boolean", "BLOCK_MEDIA", "${getEnvValue('BLOCK_MEDIA') ?: 'false'}"
buildConfigField "boolean", "BLOCK_ADS", "${getEnvValue('BLOCK_ADS') ?: 'true'}"
buildConfigField "boolean", "NO_SSL", "${getEnvValue('NO_SSL') ?: 'false'}"
buildConfigField "boolean", "HIDE_NETFREE", "${getEnvValue('HIDE_NETFREE', 'true')}"
resValue "string", "app_name", getEnvValue('APP_NAME') ?: "My Application"
applicationId getEnvValue('APPLICATION_ID') ?: "com.webview.myapplication"

minSdkVersion 16
targetSdkVersion 34
versionCode 1
versionName "1.0"
versionName getEnvValue('VERSION', '1.0.0')

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -52,11 +68,49 @@ android {
namespace 'com.webview.myapplication'
}

def appIconPath = getEnvValue('APP_ICON_PATH')

task replaceAppIcons {
doLast {
if (appIconPath && !appIconPath.isEmpty()) {
def iconFile = file(appIconPath)
if (!iconFile.exists()) {
project.logger.warn("App icon not found at: ${appIconPath}. Skipping icon replacement.")
return
}

android.sourceSets.main.res.srcDirs.each { resDir ->
file(resDir).eachDirMatch(~/^mipmap-.*$/) { mipmapDir ->
println "Replacing icons in ${mipmapDir.name}"
copy {
from iconFile
into mipmapDir
rename { 'ic_launcher.png' }
}
copy {
from iconFile
into mipmapDir
rename { 'ic_launcher_round.png' }
}
}
}
}
}
}

// This is the correct, more compatible way to hook into the build process.
// It finds the 'merge<VariantName>Resources' task for each variant and makes it depend on our icon task.
android.applicationVariants.all { variant ->
variant.mergeResources.dependsOn(replaceAppIcons)
}


dependencies {

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

<!-- Application -->

Expand Down
23 changes: 23 additions & 0 deletions app/src/main/java/com/webview/myapplication/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public class MainActivity extends Activity {
private static final boolean BLOCK_MEDIA = BuildConfig.BLOCK_MEDIA;
private static final boolean BLOCK_ADS = BuildConfig.BLOCK_ADS;
private static final boolean NO_SSL = BuildConfig.NO_SSL;
private static final boolean HIDE_NETFREE = BuildConfig.HIDE_NETFREE;
private static final boolean ALLOW_GOOGLE_LOGIN = BuildConfig.ALLOW_GOOGLE_LOGIN;
private static final String CHROME_USER_AGENT = "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36";
private String defaultUserAgent;
Expand Down Expand Up @@ -287,6 +288,18 @@ public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
String host = Uri.parse(url).getHost();
boolean isAllowed = false;

if (url != null && url.startsWith("whatsapp://")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
view.getContext().startActivity(intent);
return true;
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Error opening whatsapp, the application may not be installed", Toast.LENGTH_LONG).show();
return true;
}
}

if (url.startsWith("tel:")) {
Intent tel = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(tel);
Expand Down Expand Up @@ -361,9 +374,19 @@ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressBar.setVisibility(View.GONE);

if (BLOCK_MEDIA) {
view.loadUrl("javascript: (() => { function handle(node) { if (node.tagName === 'IMG' && node.style.visibility !== 'hidden' && node.width > 32 && node.height > 32) { const blankImageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; const { width, height } = window.getComputedStyle(node); node.src = blankImageUrl; node.style.visibility = 'hidden'; node.style.background = 'none'; node.style.backgroundImage = `url(${blankImageUrl})`; node.style.width = width; node.style.height = height; } else if (node.tagName === 'VIDEO' || node.tagName === 'IFRAME' || ((!node.type || node.type.includes('video')) && node.tagName === 'SOURCE') || node.tagName === 'OBJECT') { node.remove(); } } document.querySelectorAll('img,video,source,object,embed,iframe,[type^=video]').forEach(handle); const observer = new MutationObserver((mutations) => mutations.forEach((mutation) => mutation.addedNodes.forEach(handle))); observer.observe(document.body, { childList: true, subtree: true }); })();");
}

// remove netfree card
if (HIDE_NETFREE)
view.loadUrl("javascript: (() => { " +
"const element = document.querySelector('div#netfree-popup-window'); " +
"if (element) { " +
" element.parentNode.remove(); " +
"} " +
"})();");
}

@Override
Expand Down