কম্পিউটার টিউটোরিয়াল

অ্যান্ড্রয়েডে প্রোগ্রাম্যাটিক্যালি ওয়াইফাই নেটওয়ার্ক স্ক্যান করার গাইড

এই আর্টিকেলটি পূর্বের আর্টিকেলের (ID: 1006061517) আপডেটেড এবং রিফাইন করা ভার্সন। এখানে মডার্ন অ্যান্ড্রয়েড API (API 29+) এর সীমাবদ্ধতা, থ্রটলিং, এবং নতুন পারমিশন মডেল (Android 13+ NEARBY_WIFI_DEVICES) বিবেচনা করে একটি রোবাস্ট ওয়াইফাই স্ক্যানার বানানো দেখানো হলো।

নোট: এই আর্টিকেলটি আর্টিকেল ১০০৬০৬১৫১৭-র উন্নত সংস্করণ। মূল কোড স্ট্রাকচার একই, কিন্তু API 29+ থ্রটলিং, Android 13 পারমিশন, এবং RecyclerView ব্যবহার করে আপডেট করা হয়েছে।

কী নতুন এই ভার্সনে?

  • ✅ RecyclerView + ViewHolder (ListView এর পরিবর্তে)
  • ✅ Android 13 (API 33) NEARBY_WIFI_DEVICES পারমিশন সাপোর্ট
  • ✅ স্ক্যান থ্রটলিং হ্যান্ডলিং (API 29+)
  • ✅ করআউট/ডুপ্লিকেট SSID ফিল্টার
  • ✅ সিগন্যাল স্ট্রেংথ অনুযায়ী সর্টিং
  • ✅ ম্যাটেরিয়াল ডিজাইন UI

১. build.gradle (dependencies)

dependencies {
    implementation "androidx.recyclerview:recyclerview:1.3.2"
    implementation "com.google.android.material:material:1.11.0"
}

২. activity_main.xml (RecyclerView + MaterialButton)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvWifiList"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="8dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toTopOf="@+id/btnScan"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btnScan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="Scan WiFi Networks"
        android:icon="@android:drawable/ic_menu_rotate"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/tvEmpty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="কোনো নেটওয়ার্ক পাওয়া যায়নি\n\"Scan WiFi\" চাপুন"
        android:textAlignment="center"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@+id/btnScan"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

৩. item_wifi_network.xml (RecyclerView আইটেম লেআউট)

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="2dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

    <TextView
        android:id="@+id/tvSsid"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold"
        android:textColor="?attr/colorOnSurface" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="4dp">

        <TextView
            android:id="@+id/tvSecurity"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="12sp"
            android:textColor="?attr/colorOnSurface"
            android:alpha="0.7" />

        <TextView
            android:id="@+id/tvSignal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="@color/colorPrimary"
            android:textStyle="bold" />
    </LinearLayout>
    </LinearLayout>
</com.google.android.material.card.MaterialCardView>

৪. WifiNetwork.java (মডেল ক্লাস)

package com.example.wifiscannerpro;

public class WifiNetwork {
    public final String ssid;
    public final String security;
    public final int level; // dBm
    public final String bssid;
    public final int frequency; // MHz

    public WifiNetwork(String ssid, String security, int level, String bssid, int frequency) {
        this.ssid = ssid;
        this.security = security;
        this.level = level;
        this.bssid = bssid;
        this.frequency = frequency;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof WifiNetwork)) return false;
        WifiNetwork that = (WifiNetwork) o;
        return bssid.equals(that.bssid); // BSSID ইউনিক আইডেন্টিফায়ার
    }

    @Override
    public int hashCode() {
        return bssid.hashCode();
    }
}

৫. WifiNetworkAdapter.java (RecyclerView Adapter)

package com.example.wifiscannerpro;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class WifiNetworkAdapter extends RecyclerView.Adapter {

    private final List networks = new ArrayList<>();

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_wifi_network, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        WifiNetwork network = networks.get(position);
        holder.tvSsid.setText(network.ssid.isEmpty() ? "<Hidden Network>" : network.ssid);
        holder.tvSecurity.setText(network.security);
        holder.tvSignal.setText(network.level + " dBm");
        
        // সিগন্যাল স্ট্রেংথ অনুযায়ী কালার কোডিং
        if (network.level >= -50) holder.tvSignal.setTextColor(0xFF4CAF50); // Green
        else if (network.level >= -70) holder.tvSignal.setTextColor(0xFFFFC107); // Amber
        else holder.tvSignal.setTextColor(0xFFF44336); // Red
    }

    @Override
    public int getItemCount() {
        return networks.size();
    }

    public void submitList(List newNetworks) {
        networks.clear();
        networks.addAll(newNetworks);
        notifyDataSetChanged();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvSsid, tvSecurity, tvSignal;
        ViewHolder(View itemView) {
            super(itemView);
            tvSsid = itemView.findViewById(R.id.tvSsid);
            tvSecurity = itemView.findViewById(R.id.tvSecurity);
            tvSignal = itemView.findViewById(R.id.tvSignal);
        }
    }
}

৬. MainActivity.java (আপডেটেড লজিক)

package com.example.wifiscannerpro;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private RecyclerView rvWifiList;
    private MaterialButton btnScan;
    private WifiManager wifiManager;
    private WifiScanReceiver wifiReceiver;
    private WifiNetworkAdapter adapter;
    private static final int PERMISSION_REQUEST_CODE = 1001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        rvWifiList = findViewById(R.id.rvWifiList);
        btnScan = findViewById(R.id.btnScan);
        wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);

        adapter = new WifiNetworkAdapter();
        rvWifiList.setAdapter(adapter);

        // ওয়াইফাই অফ থাকলে অন করা (API 29+ এ ব্যবহারকারীর অনুমতি লাগে)
        if (wifiManager != null && !wifiManager.isWifiEnabled()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                Toast.makeText(this, "দয়া করে সেটিংস থেকে WiFi অন করুন", Toast.LENGTH_LONG).show();
            } else {
                wifiManager.setWifiEnabled(true);
            }
        }

        btnScan.setOnClickListener(v -> requestPermissionAndScan());
    }

    private void requestPermissionAndScan() {
        String permission;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33+
            permission = Manifest.permission.NEARBY_WIFI_DEVICES;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // API 23+
            permission = Manifest.permission.ACCESS_COARSE_LOCATION;
        } else {
            startWifiScan();
            return;
        }

        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{permission}, PERMISSION_REQUEST_CODE);
        } else {
            startWifiScan();
        }
    }

    private void startWifiScan() {
        if (wifiManager == null) return;
        
        // API 29+ থ্রটলিং চেক (সিস্টেম স্বয়ংক্রিয়ভাবে হ্যান্ডেল করে, কিন্তু ইউজারকে জানাতে পারি)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // WifiManager.startScan() রিটার্ন করে boolean (API 28-এ) বা void (API 29+)
            // আমরা শুধু কল করব, রিসিভারে রেজাল্ট পাব
        }
        
        boolean success = wifiManager.startScan();
        if (!success && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            Toast.makeText(this, "স্ক্যান থ্রটল্ড: কিছুক্ষণ انتظار করুন", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        wifiReceiver = new WifiScanReceiver();
        IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        registerReceiver(wifiReceiver, filter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (wifiReceiver != null) {
            unregisterReceiver(wifiReceiver);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startWifiScan();
            } else {
                Toast.makeText(this, "পারমিশন দরকার। সেটিংস থেকে.Enable করুন।", Toast.LENGTH_LONG).show();
            }
        }
    }

    private class WifiScanReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (!WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) return;
            if (wifiManager == null) return;

            List results = wifiManager.getScanResults();
            List networkList = new ArrayList<>();
            Set seenBssids = new HashSet<>();

            for (ScanResult result : results) {
                // ডুপ্লিকেট BSSID ফিল্টার
                if (seenBssids.contains(result.BSSID)) continue;
                seenBssids.add(result.BSSID);

                String security = getSecurityType(result.capabilities);
                networkList.add(new WifiNetwork(
                    result.SSID, security, result.level, result.BSSID, result.frequency
                ));
            }

            // সিগন্যাল স্ট্রেংথ (level) অনুযায়ী ডেসেন্ডিং সর্ট (উচ্চে zuerst)
            Collections.sort(networkList, (a, b) -> Integer.compare(b.level, a.level));

            adapter.submitList(networkList);
            
            // এম্পটি স্টেট হ্যান্ডলিং
            findViewById(R.id.tvEmpty).setVisibility(networkList.isEmpty() ? View.VISIBLE : View.GONE);
            rvWifiList.setVisibility(networkList.isEmpty() ? View.GONE : View.VISIBLE);
        }

        private String getSecurityType(String capabilities) {
            if (capabilities.contains("WPA3")) return "WPA3";
            if (capabilities.contains("WPA2")) return "WPA2";
            if (capabilities.contains("WPA")) return "WPA";
            if (capabilities.contains("WEP")) return "WEP";
            return "Open";
        }
    }
}

৭. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.wifiscannerpro">

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" 
        android:usesPermissionFlags="neverForLocation" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MaterialComponents.DayNight">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

চলানোর ফলাফল

  • ম্যাটেরিয়াল কার্ড-বেসড UI-এ ওয়াইফাই লিস্ট দেখাবে।
  • প্রতিটি আইটেমে: SSID, সিকিউরিটি টাইপ (WPA3/WPA2/Open), সিগন্যাল স্ট্রেংথ (dBm) এবং কালার-কোডেড সিগন্যাল ইন্ডিকেটর।
  • সিগন্যাল স্ট্রেংথ অনুযায়ী সর্টেড (সর্বোচ্চ উপরে)।
  • ডুপ্লিকেট BSSID অটোমেটিক ফিল্টার হয়।
  • হিডেন নেটওয়ার্ক "<Hidden Network>" হিসেবে দেখাবে।

পরবর্তী ধাপ (Enhancement Ideas)

  1. কানেকশন ফিচার: আইটেম ক্লিক করলে `WifiNetworkSpecifier` (API 29+) বা `WifiConfiguration` (Legacy) দিয়ে কানেক্ট করা।
  2. রিয়েল-টাইম আপডেট: `WifiManager.startScan()` পেরিয়ডিক্যালি কল করার জন্য `WorkManager` বা `Handler` ব্যবহার।
  3. ম্যাপ ইন্টিগ্রেশন: লোকেশন+tower ডেটা দিয়ে নেটওয়ার্ক লোকেশন ম্যাপে দেখানো।
  4. Jetpack Compose: `LazyColumn` + `rememberWifiScanner()` কাস্টম কম্পোজেবল বানানো।

এই ইমপ্লিমেন্টেশনটি প্রোডাকশন-রেডি এবং মডার্ন অ্যান্ড্রয়েড API গাইডলাইন অনুযায়ী। পুরনো ListView-ভিত্তিক কোডের চেয়ে এটি আরও পারফরম্যান্ট, মেইনটেনেবল এবং ইউজার-ফ্রেন্ডলি।

  1. এন্ড্রয়েডে প্রোগ্রামেটিক্যালি ক্যামেরা ক্লিক করার পদ্ধতি

  2. অ্যান্ড্রয়েডে প্রোগ্রাম্যাটিক্যালি WiFi সংযোগ করার পদ্ধতি

  3. অ্যান্ড্রয়েড ডিভাইসে প্রোগ্রাম্যাটিক্যালি WiFi অন/অফ করার পদ্ধতি

  4. Android-এ প্রোগ্রামেটিক্যালি অ্যাপ্লিকেশন বন্ধ করা