1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.androidx.webkit;
18 
19 import android.app.usage.NetworkStats;
20 import android.app.usage.NetworkStatsManager;
21 import android.net.NetworkCapabilities;
22 import android.os.Build;
23 import android.os.Bundle;
24 import android.os.Process;
25 import android.view.View;
26 import android.webkit.WebView;
27 import android.widget.Toast;
28 
29 import androidx.annotation.RequiresApi;
30 import androidx.appcompat.app.AppCompatActivity;
31 import androidx.webkit.WebViewCompat;
32 import androidx.webkit.WebViewFeature;
33 
34 import org.jspecify.annotations.Nullable;
35 
36 import java.util.Locale;
37 import java.util.concurrent.Executors;
38 
39 public class DefaultTrafficStatsTaggingActivity extends AppCompatActivity {
40 
41     private static final int TRAFFIC_TAG = 1234567;
42     private static final String URL = "https://example.com";
43 
44     @Override
onCreate(@ullable Bundle savedInstanceState)45     protected void onCreate(@Nullable Bundle savedInstanceState) {
46         super.onCreate(savedInstanceState);
47 
48         setContentView(R.layout.activity_default_traffic_tagging);
49         if (!WebViewFeature.isFeatureSupported(WebViewFeature.DEFAULT_TRAFFICSTATS_TAGGING)) {
50             WebkitHelpers.showMessageInActivity(DefaultTrafficStatsTaggingActivity.this,
51                     R.string.default_trafficstats_tagging_unsupported);
52             return;
53         }
54 
55         setupWebView();
56         findViewById(R.id.fetch_traffic_stats).setOnClickListener(this::fetchTrafficStats);
57     }
58 
setupWebView()59     private void setupWebView() {
60         WebView webView = findViewById(R.id.default_trafficstats_tagging_webview);
61         WebViewCompat.setDefaultTrafficStatsTag(TRAFFIC_TAG);
62         webView.loadUrl(URL);
63     }
64 
fetchTrafficStats(View view)65     private void fetchTrafficStats(View view) {
66         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
67             showToast("Unable to fetch stats tag. Require Android N+");
68             return;
69         }
70 
71         // Fetch stats in background thread
72         Executors.newSingleThreadExecutor().execute(() -> {
73             NetworkStatsManager statsManager = this.getSystemService(NetworkStatsManager.class);
74             NetworkStats.Bucket bucket = fetchTrafficStatsBucket(
75                     NetworkCapabilities.TRANSPORT_CELLULAR, statsManager);
76             String transportType = "Cellular";
77             if (bucket == null) {
78                 bucket = fetchTrafficStatsBucket(NetworkCapabilities.TRANSPORT_WIFI, statsManager);
79                 transportType = "WiFi";
80             }
81 
82             String text = makeBytesTransferredText(transportType, bucket);
83             runOnUiThread(() -> showToast(text));
84         });
85     }
86 
87     @RequiresApi(api = Build.VERSION_CODES.N)
fetchTrafficStatsBucket(int transportType, NetworkStatsManager statsManager)88     private NetworkStats.@Nullable Bucket fetchTrafficStatsBucket(int transportType,
89             NetworkStatsManager statsManager) {
90         try (NetworkStats stats = statsManager.queryDetailsForUidTag(transportType, null,
91                 Long.MIN_VALUE, Long.MAX_VALUE, Process.myUid(), TRAFFIC_TAG)) {
92             if (stats.hasNextBucket()) {
93                 NetworkStats.Bucket outputBucket = new NetworkStats.Bucket();
94                 stats.getNextBucket(outputBucket);
95                 return outputBucket;
96             }
97         }
98         return null;
99     }
100 
101     @RequiresApi(api = Build.VERSION_CODES.N)
makeBytesTransferredText(String transportType, NetworkStats.Bucket b)102     private String makeBytesTransferredText(String transportType, NetworkStats.Bucket b) {
103         if (b == null) {
104             return "No tagged bytes transferred. This may be due to caching.";
105         }
106 
107         return String.format(Locale.getDefault(),
108                 "The total bytes transferred via %s for tag %d is %d.", transportType, b.getTag(),
109                 b.getTxBytes() + b.getRxBytes());
110     }
111 
showToast(String msg)112     private void showToast(String msg) {
113         Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
114     }
115 }
116 
117