File indexing completed on 2024-12-22 04:41:42

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 package org.kde.kdeconnect.UserInterface;
0008 
0009 import android.Manifest;
0010 import android.app.Activity;
0011 import android.content.Context;
0012 import android.content.Intent;
0013 import android.content.pm.PackageManager;
0014 import android.content.res.Resources;
0015 import android.net.Uri;
0016 import android.os.Bundle;
0017 import android.provider.Settings;
0018 import android.view.LayoutInflater;
0019 import android.view.Menu;
0020 import android.view.MenuInflater;
0021 import android.view.MenuItem;
0022 import android.view.View;
0023 import android.view.ViewGroup;
0024 import android.widget.TextView;
0025 
0026 import androidx.annotation.NonNull;
0027 import androidx.core.app.ActivityCompat;
0028 import androidx.core.content.ContextCompat;
0029 import androidx.fragment.app.Fragment;
0030 
0031 import org.kde.kdeconnect.BackgroundService;
0032 import org.kde.kdeconnect.Device;
0033 import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
0034 import org.kde.kdeconnect.KdeConnect;
0035 import org.kde.kdeconnect.UserInterface.List.ListAdapter;
0036 import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem;
0037 import org.kde.kdeconnect.UserInterface.List.SectionItem;
0038 import org.kde.kdeconnect_tp.R;
0039 import org.kde.kdeconnect_tp.databinding.DevicesListBinding;
0040 import org.kde.kdeconnect_tp.databinding.PairingExplanationNotTrustedBinding;
0041 import org.kde.kdeconnect_tp.databinding.PairingExplanationTextBinding;
0042 import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoNotificationsBinding;
0043 import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoWifiBinding;
0044 
0045 import java.util.ArrayList;
0046 import java.util.Collection;
0047 
0048 
0049 /**
0050  * The view that the user will see when there are no devices paired, or when you choose "add a new device" from the sidebar.
0051  */
0052 
0053 public class PairingFragment extends Fragment implements PairingDeviceItem.Callback {
0054 
0055     private static final int RESULT_PAIRING_SUCCESFUL = Activity.RESULT_FIRST_USER;
0056 
0057     private DevicesListBinding devicesListBinding;
0058     private PairingExplanationNotTrustedBinding pairingExplanationNotTrustedBinding;
0059     private PairingExplanationTextBinding pairingExplanationTextBinding;
0060     private PairingExplanationTextNoWifiBinding pairingExplanationTextNoWifiBinding;
0061     private PairingExplanationTextNoNotificationsBinding pairingExplanationTextNoNotificationsBinding;
0062 
0063     private MainActivity mActivity;
0064 
0065     private boolean listRefreshCalledThisFrame = false;
0066 
0067     private TextView headerText;
0068     private TextView noWifiHeader;
0069     private TextView noNotificationsHeader;
0070     private TextView notTrustedText;
0071 
0072     @Override
0073     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
0074                              Bundle savedInstanceState) {
0075         mActivity.getSupportActionBar().setTitle(R.string.pairing_title);
0076 
0077         setHasOptionsMenu(true);
0078 
0079         devicesListBinding = DevicesListBinding.inflate(inflater, container, false);
0080 
0081         pairingExplanationNotTrustedBinding = PairingExplanationNotTrustedBinding.inflate(inflater);
0082         notTrustedText = pairingExplanationNotTrustedBinding.getRoot();
0083         notTrustedText.setOnClickListener(null);
0084         notTrustedText.setOnLongClickListener(null);
0085 
0086         pairingExplanationTextBinding = PairingExplanationTextBinding.inflate(inflater);
0087         headerText = pairingExplanationTextBinding.getRoot();
0088         headerText.setOnClickListener(null);
0089         headerText.setOnLongClickListener(null);
0090 
0091         pairingExplanationTextNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater);
0092         noWifiHeader = pairingExplanationTextNoWifiBinding.getRoot();
0093         noWifiHeader.setOnClickListener(view -> startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
0094 
0095         pairingExplanationTextNoNotificationsBinding = PairingExplanationTextNoNotificationsBinding.inflate(inflater);
0096         noNotificationsHeader = pairingExplanationTextNoNotificationsBinding.getRoot();
0097         noNotificationsHeader.setOnClickListener(view -> ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.POST_NOTIFICATIONS}, MainActivity.RESULT_NOTIFICATIONS_ENABLED));
0098         noNotificationsHeader.setOnLongClickListener(view -> {
0099             Intent intent = new Intent();
0100             intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
0101             Uri uri = Uri.fromParts("package", requireContext().getPackageName(), null);
0102             intent.setData(uri);
0103             startActivity(intent);
0104             return true;
0105         });
0106 
0107         devicesListBinding.devicesList.addHeaderView(headerText);
0108         devicesListBinding.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction);
0109 
0110         return devicesListBinding.getRoot();
0111     }
0112 
0113     @Override
0114     public void onDestroyView() {
0115         super.onDestroyView();
0116         devicesListBinding = null;
0117         pairingExplanationNotTrustedBinding = null;
0118         pairingExplanationTextBinding = null;
0119         pairingExplanationTextNoWifiBinding = null;
0120     }
0121 
0122     @Override
0123     public void onAttach(@NonNull Context context) {
0124         super.onAttach(context);
0125         mActivity = ((MainActivity) getActivity());
0126     }
0127 
0128     private void refreshDevicesAction() {
0129         BackgroundService.ForceRefreshConnections(requireContext());
0130 
0131         devicesListBinding.refreshListLayout.setRefreshing(true);
0132         devicesListBinding.refreshListLayout.postDelayed(() -> {
0133             if (devicesListBinding != null) { // the view might be destroyed by now
0134                 devicesListBinding.refreshListLayout.setRefreshing(false);
0135             }
0136         }, 1500);
0137     }
0138 
0139     private void updateDeviceList() {
0140         if (!isAdded()) {
0141             //Fragment is not attached to an activity. We will crash if we try to do anything here.
0142             return;
0143         }
0144 
0145         if (listRefreshCalledThisFrame) {
0146             // This makes sure we don't try to call list.getFirstVisiblePosition()
0147             // twice per frame, because the second time the list hasn't been drawn
0148             // yet and it would always return 0.
0149             return;
0150         }
0151         listRefreshCalledThisFrame = true;
0152 
0153         //Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special
0154         BackgroundService service = BackgroundService.getInstance();
0155         if (service == null) {
0156             updateConnectivityInfoHeader(true);
0157         } else {
0158             service.isConnectedToNonCellularNetwork().observe(this, this::updateConnectivityInfoHeader);
0159         }
0160 
0161         try {
0162             final ArrayList<ListAdapter.Item> items = new ArrayList<>();
0163 
0164             SectionItem connectedSection;
0165             Resources res = getResources();
0166 
0167             connectedSection = new SectionItem(res.getString(R.string.category_connected_devices));
0168             items.add(connectedSection);
0169 
0170             Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
0171             for (Device device : devices) {
0172                 if (device.isReachable() && device.isPaired()) {
0173                     items.add(new PairingDeviceItem(device, PairingFragment.this));
0174                     connectedSection.isEmpty = false;
0175                 }
0176             }
0177             if (connectedSection.isEmpty) {
0178                 items.remove(items.size() - 1); //Remove connected devices section if empty
0179             }
0180 
0181             SectionItem availableSection = new SectionItem(res.getString(R.string.category_not_paired_devices));
0182             items.add(availableSection);
0183             for (Device device : devices) {
0184                 if (device.isReachable() && !device.isPaired()) {
0185                     items.add(new PairingDeviceItem(device, PairingFragment.this));
0186                     availableSection.isEmpty = false;
0187                 }
0188             }
0189             if (availableSection.isEmpty && !connectedSection.isEmpty) {
0190                 items.remove(items.size() - 1); //Remove remembered devices section if empty
0191             }
0192 
0193             SectionItem rememberedSection = new SectionItem(res.getString(R.string.category_remembered_devices));
0194             items.add(rememberedSection);
0195             for (Device device : devices) {
0196                 if (!device.isReachable() && device.isPaired()) {
0197                     items.add(new PairingDeviceItem(device, PairingFragment.this));
0198                     rememberedSection.isEmpty = false;
0199                 }
0200             }
0201             if (rememberedSection.isEmpty) {
0202                 items.remove(items.size() - 1); //Remove remembered devices section if empty
0203             }
0204 
0205             //Store current scroll
0206             int index = devicesListBinding.devicesList.getFirstVisiblePosition();
0207             View v = devicesListBinding.devicesList.getChildAt(0);
0208             int top = (v == null) ? 0 : (v.getTop() - devicesListBinding.devicesList.getPaddingTop());
0209 
0210             devicesListBinding.devicesList.setAdapter(new ListAdapter(mActivity, items));
0211 
0212             //Restore scroll
0213             devicesListBinding.devicesList.setSelectionFromTop(index, top);
0214         } catch (IllegalStateException e) {
0215             //Ignore: The activity was closed while we were trying to update it
0216         } finally {
0217             listRefreshCalledThisFrame = false;
0218         }
0219     }
0220 
0221     void updateConnectivityInfoHeader(boolean isConnectedToNonCellularNetwork) {
0222         Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
0223         boolean someDevicesReachable = false;
0224         for (Device device : devices) {
0225             if (device.isReachable()) {
0226                 someDevicesReachable = true;
0227             }
0228         }
0229 
0230         boolean hasNotificationsPermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
0231 
0232         devicesListBinding.devicesList.removeHeaderView(headerText);
0233         devicesListBinding.devicesList.removeHeaderView(noWifiHeader);
0234         devicesListBinding.devicesList.removeHeaderView(notTrustedText);
0235         devicesListBinding.devicesList.removeHeaderView(noNotificationsHeader);
0236 
0237         if (someDevicesReachable || isConnectedToNonCellularNetwork) {
0238             if (!hasNotificationsPermission) {
0239                 devicesListBinding.devicesList.addHeaderView(noNotificationsHeader);
0240             } else if (TrustedNetworkHelper.isTrustedNetwork(getContext())) {
0241                 devicesListBinding.devicesList.addHeaderView(headerText);
0242             } else {
0243                 devicesListBinding.devicesList.addHeaderView(notTrustedText);
0244             }
0245         } else {
0246             devicesListBinding.devicesList.addHeaderView(noWifiHeader);
0247         }
0248     }
0249     @Override
0250     public void onStart() {
0251         super.onStart();
0252         KdeConnect.getInstance().addDeviceListChangedCallback("PairingFragment", () -> mActivity.runOnUiThread(this::updateDeviceList));
0253         BackgroundService.ForceRefreshConnections(requireContext()); // force a network re-discover
0254         updateDeviceList();
0255     }
0256 
0257     @Override
0258     public void onStop() {
0259         KdeConnect.getInstance().removeDeviceListChangedCallback("PairingFragment");
0260         super.onStop();
0261     }
0262 
0263     @Override
0264     public void pairingClicked(Device device) {
0265         mActivity.onDeviceSelected(device.getDeviceId(), !device.isPaired() || !device.isReachable());
0266     }
0267 
0268     @Override
0269     public void onActivityResult(int requestCode, int resultCode, Intent data) {
0270         switch (requestCode) {
0271             case RESULT_PAIRING_SUCCESFUL:
0272                 if (resultCode == 1) {
0273                     String deviceId = data.getStringExtra("deviceId");
0274                     mActivity.onDeviceSelected(deviceId);
0275                 }
0276                 break;
0277             default:
0278                 super.onActivityResult(requestCode, resultCode, data);
0279         }
0280     }
0281 
0282     @Override
0283     public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
0284         inflater.inflate(R.menu.pairing, menu);
0285     }
0286 
0287     @Override
0288     public boolean onOptionsItemSelected(final MenuItem item) {
0289         int id = item.getItemId();
0290         if (id == R.id.menu_refresh) {
0291             refreshDevicesAction();
0292             return true;
0293         } else if (id == R.id.menu_custom_device_list) {
0294             startActivity(new Intent(mActivity, CustomDevicesActivity.class));
0295             return true;
0296         } else if (id == R.id.menu_trusted_networks) {
0297             startActivity(new Intent(mActivity, TrustedNetworksActivity.class));
0298             return true;
0299         } else {
0300             return super.onOptionsItemSelected(item);
0301         }
0302     }
0303 
0304 
0305 }