summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java/com/wireguard/android/activity/MainActivity.java
blob: c19be608de5d244927c96ab36899e6af0ba6467a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
 * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.wireguard.android.activity;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.ActionBar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.LinearLayout;

import com.wireguard.android.R;
import com.wireguard.android.fragment.TunnelDetailFragment;
import com.wireguard.android.fragment.TunnelEditorFragment;
import com.wireguard.android.model.Tunnel;

import java.util.List;

/**
 * CRUD interface for WireGuard tunnels. This activity serves as the main entry point to the
 * WireGuard application, and contains several fragments for listing, viewing details of, and
 * editing the configuration and interface state of WireGuard tunnels.
 */

public class MainActivity extends BaseActivity
        implements FragmentManager.OnBackStackChangedListener {
    @Nullable private ActionBar actionBar;
    private boolean isTwoPaneLayout;

    @Override
    public void onBackPressed() {
        final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
        // If the two-pane layout does not have an editor open, going back should exit the app.
        if (isTwoPaneLayout && backStackEntries <= 1) {
            finish();
            return;
        }
        // Deselect the current tunnel on navigating back from the detail pane to the one-pane list.
        if (!isTwoPaneLayout && backStackEntries == 1) {
            setSelectedTunnel(null);
            return;
        }
        super.onBackPressed();
    }

    @Override public void onBackStackChanged() {
        if (actionBar == null)
            return;
        // Do not show the home menu when the two-pane layout is at the detail view (see above).
        final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
        final int minBackStackEntries = isTwoPaneLayout ? 2 : 1;
        actionBar.setDisplayHomeAsUpEnabled(backStackEntries >= minBackStackEntries);
    }

    // We use onTouchListener here to avoid the UI click sound, hence
    // calling View#performClick defeats the purpose of it.
    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        actionBar = getSupportActionBar();
        isTwoPaneLayout = findViewById(R.id.master_detail_wrapper) instanceof LinearLayout;
        getSupportFragmentManager().addOnBackStackChangedListener(this);
        onBackStackChanged();
        // Dispatch insets on back stack change
        // This is required to ensure replaced fragments are also able to consume insets
        findViewById(R.id.master_detail_wrapper).setOnApplyWindowInsetsListener((OnApplyWindowInsetsListener) (v, insets) -> {
            final FragmentManager fragmentManager = getSupportFragmentManager();
            fragmentManager.addOnBackStackChangedListener(() -> {
                final List<Fragment> fragments = fragmentManager.getFragments();
                fragments.get(fragments.size() - 1).requireView().dispatchApplyWindowInsets(insets);
            });
            return insets;
        });
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        getMenuInflater().inflate(R.menu.main_activity, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // The back arrow in the action bar should act the same as the back button.
                onBackPressed();
                return true;
            case R.id.menu_action_edit:
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.detail_container, new TunnelEditorFragment())
                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                        .addToBackStack(null)
                        .commit();
                return true;
            case R.id.menu_action_save:
                // This menu item is handled by the editor fragment.
                return false;
            case R.id.menu_settings:
                startActivity(new Intent(this, SettingsActivity.class));
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    protected void onSelectedTunnelChanged(@Nullable final Tunnel oldTunnel,
                                           @Nullable final Tunnel newTunnel) {
        final FragmentManager fragmentManager = getSupportFragmentManager();
        final int backStackEntries = fragmentManager.getBackStackEntryCount();
        if (newTunnel == null) {
            // Clear everything off the back stack (all editors and detail fragments).
            fragmentManager.popBackStackImmediate(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            return;
        }
        if (backStackEntries == 2) {
            // Pop the editor off the back stack to reveal the detail fragment. Use the immediate
            // method to avoid the editor picking up the new tunnel while it is still visible.
            fragmentManager.popBackStackImmediate();
        } else if (backStackEntries == 0) {
            // Create and show a new detail fragment.
            fragmentManager.beginTransaction()
                    .add(R.id.detail_container, new TunnelDetailFragment())
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                    .addToBackStack(null)
                    .commit();
        }
    }
}