Add app on-boarding

Closes #149
This commit is contained in:
emanuele-f 2022-05-30 19:58:55 +02:00
parent b5be8b9243
commit a22d334b5a
12 changed files with 231 additions and 5 deletions

View File

@ -85,4 +85,5 @@ dependencies {
// Third-party
implementation 'cat.ereza:customactivityoncrash:2.3.0'
implementation 'com.github.KaKaVip:Android-Flag-Kit:v0.1'
implementation 'com.github.AppIntro:AppIntro:6.2.0'
}

View File

@ -54,6 +54,8 @@
android:name=".activities.EditCtrlPermissions"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.OnBoardingActivity" />
<activity
android:name=".activities.ErrorActivity" />
<activity

View File

@ -19,6 +19,7 @@
package com.emanuelef.remote_capture.activities;
import android.content.Intent;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
@ -60,19 +61,25 @@ public class AboutActivity extends BaseActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.about_menu, menu);
Billing billing = Billing.newInstance(this);
if(billing.isPlayStore())
return false;
menu.findItem(R.id.unlock_code).setVisible(false);
getMenuInflater().inflate(R.menu.unlock_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == R.id.unlock_code) {
int id = item.getItemId();
if(id == R.id.unlock_code) {
showUnlockDialog();
return true;
} else if(id == R.id.on_boarding) {
Intent intent = new Intent(this, OnBoardingActivity.class);
startActivity(intent);
}
return super.onOptionsItemSelected(item);

View File

@ -106,6 +106,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
public static final String TELEGRAM_GROUP_NAME = "PCAPdroid";
public static final String GITHUB_PROJECT_URL = "https://github.com/emanuele-f/PCAPdroid";
public static final String PRIVACY_POLICY_URL = GITHUB_PROJECT_URL + "/TODO";
public static final String DOCS_URL = "https://emanuele-f.github.io/PCAPdroid";
public static final String DONATE_URL = "https://emanuele-f.github.io/PCAPdroid/donate";
public static final String FIREWALL_DOCS_URL = DOCS_URL + "/paid_features#51-firewall";
@ -127,6 +128,17 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
setTitle("PCAPdroid");
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
int appver = Prefs.getAppVersion(mPrefs);
if(appver <= 0) {
// First run, start on-boarding
Intent intent = new Intent(MainActivity.this, OnBoardingActivity.class);
startActivity(intent);
finish();
// only refresh app version on on-boarding done
} else
Prefs.refreshAppVersion(mPrefs);
mIab = Billing.newInstance(this);
mIab.setLicense(mIab.getLicense());
@ -134,7 +146,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
initAppState();
checkPermissions();
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPcapUri = CaptureService.getPcapUri();
mCapHelper = new CaptureHelper(this);
mCapHelper.setListener(success -> {

View File

@ -0,0 +1,164 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-22 - Emanuele Faranda
*/
package com.emanuelef.remote_capture.activities;
import android.content.Intent;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.model.Prefs;
import com.github.appintro.AppIntro;
import com.github.appintro.AppIntroBaseFragment;
import com.github.appintro.model.SliderPagerBuilder;
import org.jetbrains.annotations.Nullable;
public class OnBoardingActivity extends AppIntro {
private static final String TAG = "OnBoardingActivity";
public static class OnBoardingFragment extends AppIntroBaseFragment {
@Override
protected int getLayoutId() {
return R.layout.appintro_fragment_intro;
}
public static OnBoardingFragment createInstance(CharSequence title, CharSequence description, int imageRes, int imageTint) {
OnBoardingFragment fragment = new OnBoardingFragment();
Bundle args = new SliderPagerBuilder()
.title(title)
//.description(description) see below
.imageDrawable(imageRes)
.backgroundColorRes(R.color.backgroundColor)
.titleColorRes(R.color.colorAccent)
.descriptionColorRes(R.color.colorTabText)
.build().toBundle();
args.putCharSequence("pd_descr", description);
args.putInt("pd_image_tint", imageTint);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if(view == null)
return null;
Bundle args = getArguments();
assert args != null;
// fixes links from Utils.getText not clickable
TextView tv = view.findViewById(R.id.description);
tv.setAutoLinkMask(0);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(args.getCharSequence("pd_descr"));
tv.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
// fix drawable tint and size
ImageView image = view.findViewById(R.id.image);
int tint = args.getInt("pd_image_tint");
if(tint > 0)
image.setColorFilter(ContextCompat.getColor(view.getContext(), tint));
image.setAdjustViewBounds(true);
ViewGroup.LayoutParams params = image.getLayoutParams();
params.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120, getResources().getDisplayMetrics());
return view;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addSlide(OnBoardingFragment.createInstance(getString(R.string.welcome_to_pcapdroid),
getText(R.string.app_intro_welcome_msg),
R.drawable.ic_logo, R.color.colorAccent));
addSlide(OnBoardingFragment.createInstance(getString(R.string.privacy_first),
Utils.getText(this, R.string.app_intro_privacy_msg, MainActivity.PRIVACY_POLICY_URL,
MainActivity.GITHUB_PROJECT_URL),
R.drawable.ic_shield, R.color.colorAccent));
addSlide(OnBoardingFragment.createInstance(getString(R.string.country_and_asn),
getText(R.string.app_intro_geolocation_msg),
R.drawable.ic_location_dot, R.color.colorAccent));
addSlide(OnBoardingFragment.createInstance(getString(R.string.additional_features),
Utils.getText(this, R.string.app_intro_additional_features_msg, MainActivity.FIREWALL_DOCS_URL,
MainActivity.MALWARE_DETECTION_DOCS_URL, MainActivity.TLS_DECRYPTION_DOCS_URL),
R.drawable.ic_rocket_launch, R.color.colorAccent));
showStatusBar(true);
setSkipButtonEnabled(false);
setIndicatorEnabled(true);
setSystemBackButtonLocked(true);
// Theme
int colorAccent = ContextCompat.getColor(this, R.color.colorAccent);
setIndicatorColor(colorAccent, ContextCompat.getColor(this, R.color.colorAccentLight));
setBackArrowColor(colorAccent);
setColorSkipButton(colorAccent);
setNextArrowColor(colorAccent);
setBackArrowColor(colorAccent);
setColorDoneText(colorAccent);
}
@Override
protected void onSkipPressed(@Nullable Fragment currentFragment) {
Log.d(TAG, "onSkipPressed");
super.onSkipPressed(currentFragment);
runMainActivity();
}
@Override
protected void onDonePressed(@Nullable Fragment currentFragment) {
Log.d(TAG, "onDonePressed");
super.onDonePressed(currentFragment);
runMainActivity();
}
private void runMainActivity() {
Prefs.refreshAppVersion(PreferenceManager.getDefaultSharedPreferences(this));
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
}

View File

@ -23,6 +23,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import com.emanuelef.remote_capture.Billing;
import com.emanuelef.remote_capture.BuildConfig;
import com.emanuelef.remote_capture.Utils;
public class Prefs {
@ -66,6 +67,7 @@ public class Prefs {
public static final String PREF_FULL_PAYLOAD = "full_payload";
public static final String PREF_BLOCK_QUIC = "block_quic";
public static final String PREF_AUTO_BLOCK_PRIVATE_DNS = "auto_block_private_dns";
public static final String PREF_APP_VERSION = "appver";
public enum DumpMode {
NONE,
@ -97,6 +99,14 @@ public class Prefs {
}
}
public static int getAppVersion(SharedPreferences p) {
return p.getInt(PREF_APP_VERSION, 0);
}
public static void refreshAppVersion(SharedPreferences p) {
p.edit().putInt(PREF_APP_VERSION, BuildConfig.VERSION_CODE).apply();
}
/* Prefs with defaults */
public static String getCollectorIp(SharedPreferences p) { return(p.getString(PREF_COLLECTOR_IP_KEY, "127.0.0.1")); }
public static int getCollectorPort(SharedPreferences p) { return(Integer.parseInt(p.getString(PREF_COLLECTOR_PORT_KEY, "1234"))); }

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:viewportHeight="512"
android:tint="#FFFFFF"
android:viewportWidth="384" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M168.3,499.2C116.1,435 0,279.4 0,192C0,85.96 85.96,0 192,0C298,0 384,85.96 384,192C384,279.4 267,435 215.7,499.2C203.4,514.5 180.6,514.5 168.3,499.2H168.3zM192,256C227.3,256 256,227.3 256,192C256,156.7 227.3,128 192,128C156.7,128 128,156.7 128,192C128,227.3 156.7,256 192,256z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9.19,6.35c-2.04,2.29 -3.44,5.58 -3.57,5.89L2,10.69l4.05,-4.05c0.47,-0.47 1.15,-0.68 1.81,-0.55L9.19,6.35L9.19,6.35zM11.17,17c0,0 3.74,-1.55 5.89,-3.7c5.4,-5.4 4.5,-9.62 4.21,-10.57c-0.95,-0.3 -5.17,-1.19 -10.57,4.21C8.55,9.09 7,12.83 7,12.83L11.17,17zM17.65,14.81c-2.29,2.04 -5.58,3.44 -5.89,3.57L13.31,22l4.05,-4.05c0.47,-0.47 0.68,-1.15 0.55,-1.81L17.65,14.81L17.65,14.81zM9,18c0,0.83 -0.34,1.58 -0.88,2.12C6.94,21.3 2,22 2,22s0.7,-4.94 1.88,-6.12C4.42,15.34 5.17,15 6,15C7.66,15 9,16.34 9,18zM13,9c0,-1.1 0.9,-2 2,-2s2,0.9 2,2s-0.9,2 -2,2S13,10.1 13,9z"/>
</vector>

View File

@ -8,4 +8,9 @@
android:title="@string/paid_features"
android:orderInCategory="10"
app:showAsAction="never" />
<item
android:id="@+id/on_boarding"
android:title="@string/features_onboarding"
android:orderInCategory="20"
app:showAsAction="never" />
</menu>

View File

@ -6,7 +6,8 @@
<color name="colorPrimary">#6C52AB</color>
<color name="colorPrimaryDark">#24144D</color>
<color name="colorActionBar">#121212</color>
<color name="colorAccent">@color/design_default_color_secondary</color>
<color name="colorAccent">#03DAC6</color>
<color name="colorAccentLight">#BCEBE7</color>
<color name="colorBackgroundGray">#1CFFFFFF</color>
<color name="navViewBackground">#FF202020</color>
<color name="backgroundColor">@color/background_material_dark</color>

View File

@ -7,6 +7,7 @@
<color name="colorPrimaryDark">#4D3294</color>
<color name="colorActionBar">#512da8</color>
<color name="colorAccent">#D81B60</color>
<color name="colorAccentLight">#D8ADBD</color>
<color name="colorBackgroundGray">#2C000000</color>
<color name="navViewBackground">@color/design_default_color_surface</color>
<color name="backgroundColor">@color/background_material_light</color>

View File

@ -22,6 +22,7 @@
- MaxMind DB Reader: <a href='https://github.com/maxmind/MaxMind-DB-Reader-java/blob/main/LICENSE'>Apache-2.0</a>\n\n
- FlagKit: <a href='https://github.com/madebybowtie/FlagKit/blob/master/LICENSE'>MIT</a>\n\n
- IP Geolocation by <a href='https://db-ip.com'>DB-IP</a>\n\n
- AppIntro: <a href='https://github.com/AppIntro/AppIntro/blob/main/LICENSE'>Apache-2.0</a>\n\n
- Font Awesome: <a href='https://fontawesome.com/license/free'>Licenses</a>\n\n
- App icon by <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">flaticon</a>\n\n
- SourceCodePro font: <a href='https://github.com/adobe-fonts/source-code-pro/blob/release/LICENSE.md'>OFL-1.1</a>\n\n
@ -346,4 +347,17 @@
<string name="block_private_dns_summary">Detect and possibly block private DNS to inspect DNS traffic. Disabling this can hinder detection</string>
<string name="mitm_setup_wizard_intro">This wizard will guide you through the installation of the PCAPdroid mitm addon and certification authority, which are needed to perform the <a href='%1$s'>TLS decryption</a></string>
<string name="mitm_setup_wizard_done">PCAPdroid is now ready to decrypt TLS traffic\n\nCheck out the <a href='%1$s'>user guide</a> to know more about the security measures which may prevent decryption and how to bypass them</string>
<string name="app_intro_skip_button">Skip</string>
<string name="app_intro_next_button">Next</string>
<string name="app_intro_back_button">Back</string>
<string name="app_intro_done_button">Done</string>
<string name="welcome_to_pcapdroid">Welcome to PCAPdroid</string>
<string name="app_intro_welcome_msg">PCAPdroid is a privacy-friendly app which lets you track and analyze the connections made by the apps in your device\n\nMoreover, it allows you to export a PCAP dump of the traffic, extract metadata and much more!</string>
<string name="privacy_first">Privacy-first</string>
<string name="app_intro_privacy_msg">The app does not embed any tracking, analytics or calling home\n\nHow can you be sure? Check out its <a href='%1$s'>privacy policy</a> and its <a href='%2$s'>source code</a></string>
<string name="additional_features">Additional features</string>
<string name="app_intro_additional_features_msg">Monitoring is not enough? PCAPdroid gets you covered!\n\n&#8226; <a href='%1$s'>Firewall</a>: block apps, domains or IP addresses*\n&#8226; <a href='%2$s'>Malware detection</a> and blocking*\n&#8226; <a href='%3$s'>TLS decryption</a>\n\n*paid feature, only available on Google Play</string>
<string name="country_and_asn">Country and ASN</string>
<string name="app_intro_geolocation_msg">PCAPdroid can query a local database to determine the country of a remote server\n\nYou must first download the database from the PCAPdroid settings</string>
<string name="features_onboarding">On-boarding</string>
</resources>