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