Skip to content
Merged
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
22 changes: 0 additions & 22 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,3 @@ If revisited later, start by deciding whether the desired goal is:

- just to reduce cross-app cache bleed, or
- to redesign tracker attribution so "per-app blocking" is backed by per-app evidence.

## Remaining Material 3 migration items

The core M3 migration is complete (themes, switches, settings with MaterialToolbar, FABs, tabs). The following are cosmetic items that still reference AppCompat/MaterialComponents but work correctly:

### TextAppearance references (~16 occurrences)
- `traffic.xml` — 8 uses of `Base.TextAppearance.AppCompat.Small` → should be `TextAppearance.Material3.BodySmall`
- `item_onboarding.xml` / `item_onboarding_blocking_mode.xml` — uses of `TextAppearance.AppCompat.Large`, `.Body1`, `.Caption` → should be `TextAppearance.Material3.HeadlineSmall`, `.BodyLarge`, `.LabelSmall`

### Button styles (4 buttons)
- `activity_onboarding.xml:28` — `Widget.AppCompat.Button.Borderless` → `Widget.Material3.Button.TextButton`
- `troubleshooting.xml:56,90,156` — `Widget.AppCompat.Button.Borderless.Colored` → `Widget.Material3.Button.TextButton`

### Layout-level theme references
- `traffic.xml:5` — `Theme.AppCompat.Light.DarkActionBar` on a RelativeLayout
- `list_item_trackers_header.xml:158-159` — `Theme.MaterialComponents.DayNight` and `Widget.MaterialComponents.Button.OutlinedButton` → `Theme.Material3.DayNight` and `Widget.Material3.Button.OutlinedButton`

### AlertDialog → MaterialAlertDialogBuilder (12 occurrences)
Uses of `androidx.appcompat.app.AlertDialog.Builder` across ActivityMain, ActivityLog, ActivitySettings, ActivityForwarding, ActivityBlocklists, ActionsFragment, and Util. Migrating to `com.google.android.material.dialog.MaterialAlertDialogBuilder` would give M3-styled dialogs (rounded corners, M3 color tokens).

### Action bar styles in styles.xml
`ActionBar.Red`, `ActionBarTheme.Red`, `Toolbar.Red`, `ActionBarTitleText` still reference AppCompat parents (`Widget.AppCompat.ActionBar.Solid`, `TextAppearance.AppCompat.Widget.ActionBar.Title`). These are used by the main screen's legacy action bar and work correctly but aren't pure M3.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

// https://developer.android.com/jetpack/androidx/releases/
implementation 'androidx.activity:activity:1.9.3'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
Expand Down
12 changes: 11 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,22 @@
android:value="eu.faircode.netguard.ActivitySettings" />
</activity>

<activity
android:name="net.kollnig.missioncontrol.ActivityTimeline"
android:label="@string/title_tracker_activity"
android:parentActivityName="eu.faircode.netguard.ActivityMain"
android:theme="@style/AppThemeRed.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="eu.faircode.netguard.ActivityMain" />
</activity>

<activity
android:name="net.kollnig.missioncontrol.DetailsActivity"
android:exported="true"
android:label="@string/title_activity_detail"
android:parentActivityName="eu.faircode.netguard.ActivityMain"
android:theme="@style/AppTheme.EdgeToEdge">
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="eu.faircode.netguard.ActivityMain" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;

import net.kollnig.missioncontrol.R;
Expand Down Expand Up @@ -183,7 +185,7 @@ protected void onPostExecute(List<Rule> rules) {
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

dialog = new AlertDialog.Builder(this)
dialog = new MaterialAlertDialogBuilder(this)
.setView(view)
.setCancelable(true)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/eu/faircode/netguard/ActivityLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import com.google.android.material.materialswitch.MaterialSwitch;
Expand Down Expand Up @@ -405,7 +407,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
}

// warn that enabling will send data to ipinfo.io
AlertDialog.Builder builder = new AlertDialog.Builder(this)
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.confirm_ipinfo_title)
.setMessage(R.string.confirm_ipinfo)
.setPositiveButton(R.string.yes, (dialog, id2) -> {
Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/eu/faircode/netguard/ActivityMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import com.google.android.material.materialswitch.MaterialSwitch;
Expand Down Expand Up @@ -182,7 +184,7 @@ protected void onCreate(Bundle savedInstanceState) {

// Check for filtering
if (!Util.canFilter(this)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.device_not_supported_title)
.setMessage(R.string.device_not_supported_msg)
.setPositiveButton(R.string.ok, (dialog, id) -> {
Expand Down Expand Up @@ -302,7 +304,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LayoutInflater inflater = LayoutInflater.from(ActivityMain.this);
final View view = inflater.inflate(R.layout.vpn, null, false);

dialogVpn = new AlertDialog.Builder(ActivityMain.this)
dialogVpn = new MaterialAlertDialogBuilder(ActivityMain.this)
.setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
Expand Down Expand Up @@ -984,6 +986,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
item.setChecked(true);
prefs.edit().putString("sort", "uid").apply();
return true;
} else if (itemId == R.id.menu_timeline) {
startActivity(new Intent(this, net.kollnig.missioncontrol.ActivityTimeline.class));
return true;
} else if (itemId == R.id.menu_log) {
if (Util.canFilter(this))
startActivity(new Intent(this, ActivityLog.class));
Expand Down Expand Up @@ -1197,7 +1202,7 @@ public void onClick(View v) {
}
});

dialogTroubleshooting = new AlertDialog.Builder(this)
dialogTroubleshooting = new MaterialAlertDialogBuilder(this)
.setView(view)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, null)
Expand Down Expand Up @@ -1247,7 +1252,7 @@ private void menu_legend() {
}

// Show dialog
dialogLegend = new AlertDialog.Builder(this)
dialogLegend = new MaterialAlertDialogBuilder(this)
.setView(view)
.setCancelable(true)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
Expand Down Expand Up @@ -1289,7 +1294,7 @@ public void onClick(View view) {
});

// Show dialog
dialogAbout = new AlertDialog.Builder(this)
dialogAbout = new MaterialAlertDialogBuilder(this)
.setView(view)
.setCancelable(true)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/eu/faircode/netguard/ActivitySettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NavUtils;
import androidx.core.util.PatternsCompat;
Expand Down Expand Up @@ -624,7 +626,7 @@ else if ("filter".equals(name)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && prefs.getBoolean(name, true)) {
LayoutInflater inflater = LayoutInflater.from(ActivitySettings.this);
View view = inflater.inflate(R.layout.filter, null, false);
dialogFilter = new AlertDialog.Builder(ActivitySettings.this)
dialogFilter = new MaterialAlertDialogBuilder(ActivitySettings.this)
.setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
Expand Down
66 changes: 10 additions & 56 deletions app/src/main/java/eu/faircode/netguard/ApplicationEx.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,19 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;

import androidx.activity.ComponentActivity;
import androidx.activity.EdgeToEdge;
import androidx.activity.SystemBarStyle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;

import net.kollnig.missioncontrol.BuildConfig;
import net.kollnig.missioncontrol.Common;
import net.kollnig.missioncontrol.DetailsActivity;
import net.kollnig.missioncontrol.R;
import net.kollnig.missioncontrol.data.BlockingMode;

Expand Down Expand Up @@ -129,79 +123,39 @@ public void onCreate() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
if (activity.getClass() == DetailsActivity.class)
return;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
android.widget.FrameLayout content = activity.findViewById(android.R.id.content);

// On Android 15+, setStatusBarColor is ignored
// Set window background to primary color - this shows behind the status bar
if (activity instanceof ComponentActivity) {
int statusBarColor = ContextCompat.getColor(activity, R.color.colorPrimaryDark);
activity.getWindow().setBackgroundDrawable(new ColorDrawable(statusBarColor));

boolean isNight = Common.isNight(activity);

// Set status bar icons to light (white) since our background is dark
View decor = activity.getWindow().getDecorView();
WindowCompat.getInsetsController(activity.getWindow(), decor).setAppearanceLightStatusBars(false);
WindowCompat.getInsetsController(activity.getWindow(), decor)
.setAppearanceLightNavigationBars(!isNight);

ViewCompat.setOnApplyWindowInsetsListener(content, new OnApplyWindowInsetsListener() {
@NonNull
@Override
public WindowInsetsCompat onApplyWindowInsets(@NonNull View v,
@NonNull WindowInsetsCompat insets) {
Insets bars = insets.getInsets(WindowInsetsCompat.Type.systemBars()
| WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.ime());

// Apply padding to android.R.id.content for system bars
// We do NOT apply bottom padding here, so the content background (White/Black)
// extends to the bottom
v.setPadding(bars.left, bars.top, bars.right, 0);

// Set background on the actual layout (first child), not on the content frame
// This way the padding area shows the window background (primary color)
if (content.getChildCount() > 0) {
View child = content.getChildAt(0);
child.setBackgroundColor(Common.isNight(activity) ? Color.BLACK : Color.WHITE);
}

return insets;
}
});
// SystemBarStyle.dark() is critical: without it, EdgeToEdge defaults to
// transparent, and M3's light theme produces white-on-white status bar icons.
EdgeToEdge.enable(
(ComponentActivity) activity,
SystemBarStyle.dark(statusBarColor),
SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT));
}
}

@Override
public void onActivityStarted(@NonNull Activity activity) {

}

@Override
public void onActivityResumed(@NonNull Activity activity) {

}

@Override
public void onActivityPaused(@NonNull Activity activity) {

}

@Override
public void onActivityStopped(@NonNull Activity activity) {

}

@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {

}
});
}
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/eu/faircode/netguard/DatabaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,26 @@ public Cursor getInsightsData7Days() {
}
}

public Cursor getRecentTrackerActivity() {
lock.readLock().lock();
try {
SQLiteDatabase db = this.getReadableDatabase();
long sevenDaysAgo = System.currentTimeMillis() - (7L * 24 * 60 * 60 * 1000);

String query = "SELECT uid, daddr, allowed, MAX(time) as last_time, " +
"COUNT(*) as attempts, uncertain " +
"FROM access " +
"WHERE time >= ? " +
"GROUP BY uid, daddr, allowed " +
"ORDER BY last_time DESC " +
"LIMIT 500";

return db.rawQuery(query, new String[] { Long.toString(sevenDaysAgo) });
} finally {
lock.readLock().unlock();
}
}

// DNS

public boolean insertDns(ResourceRecord rr) {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/eu/faircode/netguard/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
import androidx.core.net.ConnectivityManagerCompat;
Expand Down Expand Up @@ -560,7 +560,7 @@ public static void areYouSure(Context context, int explanation, final DoubtListe
View view = inflater.inflate(R.layout.sure, null, false);
TextView tvExplanation = view.findViewById(R.id.tvExplanation);
tvExplanation.setText(explanation);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setView(view)
.setCancelable(true)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.materialswitch.MaterialSwitch;
import androidx.recyclerview.widget.LinearLayoutManager;
Expand Down Expand Up @@ -68,7 +70,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
}

private void showAddDialog(Blocklist item) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(item == null ? R.string.title_add_blocklist : R.string.title_blocklists);

final EditText input = new EditText(this);
Expand Down Expand Up @@ -162,7 +164,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.itemView.setOnClickListener(v -> showAddDialog(item));

holder.btnDelete.setOnClickListener(v -> {
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.title_delete_blocklist)
.setMessage(R.string.msg_delete_blocklist_confirm)
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
Expand Down
Loading
Loading