• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.android.internal.telephony.satellite;
18 
19 import android.annotation.NonNull;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.UserInfo;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.Log;
35 
36 import com.android.internal.telephony.PackageChangeReceiver;
37 
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 
45 /**
46  * SatelliteOptimizedApplicationsTracker will track the packages if they are Satellite optimized or
47  * not. According to this it is maintain a cache which store data about that packages.
48  */
49 public class SatelliteOptimizedApplicationsTracker {
50 
51     @NonNull private final Map<Integer, Set<String>> mSatelliteApplications = new HashMap<>();
52 
53     /** Action used to initialize the state of the Tracker. */
54     private static final int ACTION_INITIALIZE_TRACKER = 0;
55 
56     private static final int ACTION_USER_ADDED = 1;
57     private static final int ACTION_PACKAGE_ADDED = 2;
58     private static final int ACTION_PACKAGE_UPDATED = 3;
59     private static final int ACTION_PACKAGE_MODIFIED = 4;
60     private static final int ACTION_PACKAGE_REMOVED = 5;
61 
62     private static final String APP_PROPERTY =
63             "android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED";
64     private static final String TAG = "SatelliteAppTracker";
65 
66     private final Context mContext;
67     private PackageManager mPackageManager;
68     private final UserManager mUserManager;
69     private volatile Handler mCurrentHandler;
70 
SatelliteOptimizedApplicationsTracker(@onNull Looper looper, @NonNull Context context)71     public SatelliteOptimizedApplicationsTracker(@NonNull Looper looper, @NonNull Context context) {
72         mContext = context;
73         mPackageManager = context.getPackageManager();
74         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
75         mCurrentHandler =
76                 new Handler(looper) {
77                     @Override
78                     public void handleMessage(@NonNull Message msg) {
79                         switch (msg.what) {
80                             case ACTION_INITIALIZE_TRACKER, ACTION_USER_ADDED -> {
81                                 log("HandleInitializeTracker() STARTED");
82                                 handleInitializeTracker();
83                             }
84                             case ACTION_PACKAGE_ADDED,
85                                     ACTION_PACKAGE_UPDATED,
86                                     ACTION_PACKAGE_MODIFIED -> {
87                                 String packageName = (String) msg.obj;
88                                 handlePackageMonitor(packageName);
89                             }
90                             case ACTION_PACKAGE_REMOVED -> {
91                                 String packageName = (String) msg.obj;
92                                 handlePackageRemoved(packageName);
93                             }
94                         }
95                     }
96                 };
97         mCurrentHandler.sendMessage(mCurrentHandler.obtainMessage(ACTION_INITIALIZE_TRACKER));
98         Context mReceiverContext =
99                 context.createContextAsUser(UserHandle.ALL, PackageManager.GET_META_DATA);
100         mReceiverContext.registerReceiver(
101                 mBootCompleted, new IntentFilter(Intent.ACTION_USER_ADDED));
102         PackageChangeReceiver packageMonitor = new SatelliteApplicationPackageMonitor();
103         packageMonitor.register(context, mCurrentHandler.getLooper(), UserHandle.ALL);
104     }
105 
106     private final BroadcastReceiver mBootCompleted =
107             new BroadcastReceiver() {
108                 @Override
109                 public void onReceive(Context context, Intent intent) {
110                     loge("new user added");
111                     // Recalculate all cached services to pick up ones that have just been enabled
112                     // since new user is added
113                     mCurrentHandler.obtainMessage(ACTION_USER_ADDED, null).sendToTarget();
114                 }
115             };
116 
getApplicationInfo(String packageName)117     private ApplicationInfo getApplicationInfo(String packageName) {
118         mPackageManager = mContext.getPackageManager();
119         try {
120             PackageInfo packageInfo =
121                     mPackageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA);
122             if (packageInfo == null) {
123                 loge("packageInfo is NULL");
124                 return null;
125             }
126             return packageInfo.applicationInfo;
127         } catch (PackageManager.NameNotFoundException exp) {
128             loge(
129                     "Exception while reading packageInfo [ "
130                             + packageName
131                             + " ] exp = "
132                             + exp.getMessage());
133             return null;
134         }
135     }
136 
137     private class SatelliteApplicationPackageMonitor extends PackageChangeReceiver {
138         @Override
onPackageAdded(String packageName)139         public void onPackageAdded(String packageName) {
140             log("onPackageAdded : " + packageName);
141             mCurrentHandler.obtainMessage(ACTION_PACKAGE_ADDED, packageName).sendToTarget();
142         }
143 
144         @Override
onPackageRemoved(String packageName)145         public void onPackageRemoved(String packageName) {
146             log("onPackageRemoved : " + packageName);
147             mCurrentHandler.obtainMessage(ACTION_PACKAGE_REMOVED, packageName).sendToTarget();
148         }
149 
150         @Override
onPackageUpdateFinished(String packageName)151         public void onPackageUpdateFinished(String packageName) {
152             log("onPackageUpdateFinished : " + packageName);
153             mCurrentHandler.obtainMessage(ACTION_PACKAGE_UPDATED, packageName).sendToTarget();
154         }
155 
156         @Override
onPackageModified(String packageName)157         public void onPackageModified(String packageName) {
158             log("onPackageModified : " + packageName);
159             mCurrentHandler.obtainMessage(ACTION_PACKAGE_MODIFIED, packageName).sendToTarget();
160         }
161     }
162 
handlePackageRemoved(String packageName)163     private void handlePackageRemoved(String packageName) {
164         removeCacheOptimizedSatelliteApplication(packageName);
165     }
166 
handlePackageMonitor(String packageName)167     private void handlePackageMonitor(String packageName) {
168         ApplicationInfo applicationInfo = getApplicationInfo(packageName);
169         if (applicationInfo != null) {
170             if (isOptimizedSatelliteApplication(applicationInfo)) {
171                 addCacheOptimizedSatelliteApplication(packageName);
172             } else {
173                 removeCacheOptimizedSatelliteApplication(packageName);
174             }
175         }
176     }
177 
handleInitializeTracker()178     private void handleInitializeTracker() {
179         List<UserInfo> users = mUserManager.getUsers();
180         for (UserInfo user : users) {
181             int userId = user.getUserHandle().getIdentifier();
182             mSatelliteApplications.putIfAbsent(userId, new HashSet<>());
183         }
184         // Get a list of installed packages
185         List<PackageInfo> packages =
186                 mPackageManager.getInstalledPackages(PackageManager.GET_META_DATA);
187         // Iterate through the packages
188         for (PackageInfo packageInfo : packages) {
189             if (packageInfo.applicationInfo != null
190                     && isOptimizedSatelliteApplication(packageInfo.applicationInfo)) {
191                 addCacheOptimizedSatelliteApplication(packageInfo.packageName);
192             }
193         }
194     }
195 
isOptimizedSatelliteApplication(@onNull ApplicationInfo applicationInfo)196     private boolean isOptimizedSatelliteApplication(@NonNull ApplicationInfo applicationInfo) {
197         boolean flag = false;
198         if (applicationInfo.metaData != null) {
199             // Get the application's metadata
200             Bundle metadata = applicationInfo.metaData;
201             flag = metadata.containsKey(APP_PROPERTY);
202         }
203         return flag;
204     }
205 
addCacheOptimizedSatelliteApplication(@onNull String packageName)206     private void addCacheOptimizedSatelliteApplication(@NonNull String packageName) {
207         List<UserInfo> users = mUserManager.getUsers();
208         for (UserInfo user : users) {
209             int userId = user.getUserHandle().getIdentifier();
210             try {
211                 mPackageManager.getPackageUidAsUser(
212                         packageName, PackageManager.GET_META_DATA, userId);
213                 mSatelliteApplications.putIfAbsent(userId, new HashSet<>());
214                 mSatelliteApplications.get(userId).add(packageName);
215             } catch (java.lang.Exception e) {
216                 // package is not present for current user
217             }
218         }
219     }
220 
removeCacheOptimizedSatelliteApplication(@onNull String packageName)221     private void removeCacheOptimizedSatelliteApplication(@NonNull String packageName) {
222         List<UserInfo> users = mUserManager.getUsers();
223         for (UserInfo user : users) {
224             int userId = user.getUserHandle().getIdentifier();
225             try {
226                 mPackageManager.getPackageUidAsUser(
227                         packageName, PackageManager.GET_META_DATA, userId);
228             } catch (java.lang.Exception e) {
229                 // package is not present for current user
230                 if (mSatelliteApplications.containsKey(userId)
231                         && mSatelliteApplications.get(userId).contains(packageName)) {
232                     mSatelliteApplications.get(userId).remove(packageName);
233                 }
234             }
235         }
236     }
237 
238     /**
239      * Get list of applications that are optimized for low bandwidth satellite data.
240      *
241      * @param userId is Identifier of user
242      * @return List of applications package names with data optimized network property. {@link
243      *     #PROPERTY_SATELLITE_DATA_OPTIMIZED}
244      */
getSatelliteOptimizedApplications(int userId)245     public @NonNull List<String> getSatelliteOptimizedApplications(int userId) {
246         return new ArrayList<>(mSatelliteApplications.get(userId));
247     }
248 
log(String str)249     private void log(String str) {
250         Log.i(TAG, str);
251     }
252 
loge(String str)253     private void loge(String str) {
254         Log.e(TAG, str);
255     }
256 }
257