Fix the ViewPager not always moving to new tabs. https://redmine.stoutner.com/issues/798
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / adapters / WebViewPagerAdapter.java
1 /*
2  * Copyright © 2019-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.adapters;
21
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.widget.FrameLayout;
25
26 import androidx.annotation.NonNull;
27 import androidx.fragment.app.Fragment;
28 import androidx.fragment.app.FragmentManager;
29 import androidx.fragment.app.FragmentPagerAdapter;
30 import androidx.viewpager.widget.ViewPager;
31
32 import com.stoutner.privacybrowser.R;
33 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
34 import com.stoutner.privacybrowser.views.NestedScrollWebView;
35
36 import java.util.LinkedList;
37
38 public class WebViewPagerAdapter extends FragmentPagerAdapter {
39     // The WebView fragments list contains all the WebViews.
40     private final LinkedList<WebViewTabFragment> webViewFragmentsList = new LinkedList<>();
41
42     // Define the constructor.
43     public WebViewPagerAdapter(FragmentManager fragmentManager) {
44         // Run the default commands.
45         super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
46     }
47
48     @Override
49     public int getCount() {
50         // Return the number of pages.
51         return webViewFragmentsList.size();
52     }
53
54     @Override
55     public int getItemPosition(@NonNull Object object) {
56         //noinspection SuspiciousMethodCalls
57         if (webViewFragmentsList.contains(object)) {
58             // Return the current page position.
59             //noinspection SuspiciousMethodCalls
60             return webViewFragmentsList.indexOf(object);
61         } else {
62             // The tab has been deleted.
63             return POSITION_NONE;
64         }
65     }
66
67     @Override
68     @NonNull
69     public Fragment getItem(int pageNumber) {
70         // Get the fragment for a particular page.  Page numbers are 0 indexed.
71         return webViewFragmentsList.get(pageNumber);
72     }
73
74     @Override
75     public long getItemId(int position) {
76         // Return the unique ID for this page.
77         return webViewFragmentsList.get(position).fragmentId;
78     }
79
80     public int getPositionForId(long fragmentId) {
81         // Initialize the position variable.
82         int position = -1;
83
84         // Initialize the while counter.
85         int i = 0;
86
87         // Find the current position of the WebView fragment with the given ID.
88         while (position < 0 && i < webViewFragmentsList.size()) {
89             // Check to see if the tab ID of this WebView matches the page ID.
90             if (webViewFragmentsList.get(i).fragmentId == fragmentId) {
91                 // Store the position if they are a match.
92                 position = i;
93             }
94
95             // Increment the counter.
96             i++;
97         }
98
99         // Set the position to be the last tab if it is not found.
100         // Sometimes there is a race condition in populating the webView fragments list when resuming Privacy Browser and displaying an SSL certificate error while loading a new intent.
101         // In that case, the last tab should be the one it is looking for.
102         if (position == -1) {
103             position = webViewFragmentsList.size() - 1;
104         }
105
106         // Return the position.
107         return position;
108     }
109
110     public void addPage(int pageNumber, ViewPager webViewPager, String url, boolean moveToNewPage) {
111         // Add a new page.
112         webViewFragmentsList.add(WebViewTabFragment.createPage(pageNumber, url));
113
114         // Update the view pager.
115         notifyDataSetChanged();
116
117         // Move to the new page if indicated.
118         if (moveToNewPage) {
119             moveToNewPage(pageNumber, webViewPager);
120         }
121     }
122
123     public void restorePage(Bundle savedState, Bundle savedNestedScrollWebViewState) {
124         // Restore the page.
125         webViewFragmentsList.add(WebViewTabFragment.restorePage(savedState, savedNestedScrollWebViewState));
126
127         // Update the view pager.
128         notifyDataSetChanged();
129     }
130
131     public boolean deletePage(int pageNumber, ViewPager webViewPager) {
132         // Get the WebView tab fragment.
133         WebViewTabFragment webViewTabFragment = webViewFragmentsList.get(pageNumber);
134
135         // Get the WebView frame layout.
136         FrameLayout webViewFrameLayout = (FrameLayout) webViewTabFragment.getView();
137
138         // Remove the warning below that the WebView frame layout might be null.
139         assert webViewFrameLayout != null;
140
141         // Get a handle for the nested scroll WebView.
142         NestedScrollWebView nestedScrollWebView = webViewFrameLayout.findViewById(R.id.nestedscroll_webview);
143
144         // Pause the current WebView.
145         nestedScrollWebView.onPause();
146
147         // Remove all the views from the frame layout.
148         webViewFrameLayout.removeAllViews();
149
150         // Destroy the current WebView.
151         nestedScrollWebView.destroy();
152
153         // Delete the page.
154         webViewFragmentsList.remove(pageNumber);
155
156         // Update the view pager.
157         notifyDataSetChanged();
158
159         // Return true if the selected page number did not change after the delete (because the newly selected tab has has same number as the previously deleted tab).
160         // This will cause the calling method to reset the current WebView to the new contents of this page number.
161         return (webViewPager.getCurrentItem() == pageNumber);
162     }
163
164     public WebViewTabFragment getPageFragment(int pageNumber) {
165         // Return the page fragment.
166         return webViewFragmentsList.get(pageNumber);
167     }
168
169     private void moveToNewPage(int pageNumber, ViewPager webViewPager) {
170         // Check to see if the new page has been populated.
171         if (webViewPager.getChildCount() >= pageNumber) {  // The new page is ready.
172             // Move to the new page.
173             webViewPager.setCurrentItem(pageNumber);
174         } else {  // The new page is not yet ready.
175             // Create a handler.
176             Handler moveToNewPageHandler = new Handler();
177
178             // Create a runnable.
179             Runnable moveToNewPageRunnable = () -> {
180                 // Move to the new page.
181                 webViewPager.setCurrentItem(pageNumber);
182             };
183
184             // Try again to move to the new page after 50 milliseconds.
185             moveToNewPageHandler.postDelayed(moveToNewPageRunnable, 50);
186         }
187     }
188 }