diff options
9 files changed, 598 insertions, 75 deletions
diff --git a/packages/SystemUI/res/layout/media_projection_dialog_title.xml b/packages/SystemUI/res/layout/media_projection_dialog_title.xml deleted file mode 100644 index b9e39dae13dc..000000000000 --- a/packages/SystemUI/res/layout/media_projection_dialog_title.xml +++ /dev/null @@ -1,40 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2019, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:android="http://schemas.android.com/apk/res/android" - android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog" - android:paddingStart="?android:attr/dialogPreferredPadding" - android:paddingEnd="?android:attr/dialogPreferredPadding" - android:orientation="vertical"> - <ImageView - android:id="@+id/dialog_icon" - android:src="@drawable/ic_media_projection_permission" - android:layout_height="24dp" - android:layout_width="24dp" - android:layout_marginTop="18dp" - android:layout_marginBottom="12dp" - android:layout_gravity="center_horizontal" /> - <TextView - android:id="@+id/dialog_title" - android:gravity="center" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textSize="20sp" - android:textColor="?android:attr/textColorPrimary" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Title" /> -</LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index e2716e992c48..77873e829be3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -38,12 +38,10 @@ import android.text.TextPaint; import android.text.TextUtils; import android.text.style.StyleSpan; import android.util.Log; -import android.view.View; import android.view.Window; -import android.view.WindowManager; -import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.Utils; public class MediaProjectionPermissionActivity extends Activity @@ -56,7 +54,7 @@ public class MediaProjectionPermissionActivity extends Activity private int mUid; private IMediaProjectionManager mService; - private AlertDialog mDialog; + private SystemUIDialog mDialog; @Override public void onCreate(Bundle icicle) { @@ -143,25 +141,18 @@ public class MediaProjectionPermissionActivity extends Activity dialogTitle = getString(R.string.media_projection_dialog_title, appName); } - View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null); - TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title); - titleText.setText(dialogTitle); - - mDialog = new AlertDialog.Builder(this) - .setCustomTitle(dialogTitleView) - .setMessage(dialogText) - .setPositiveButton(R.string.media_projection_action_text, this) - .setNegativeButton(android.R.string.cancel, this) - .setOnCancelListener(this) - .create(); + mDialog = new SystemUIDialog(this); + mDialog.setTitle(dialogTitle); + mDialog.setIcon(R.drawable.ic_media_projection_permission); + mDialog.setMessage(dialogText); + mDialog.setPositiveButton(R.string.media_projection_action_text, this); + mDialog.setNeutralButton(android.R.string.cancel, this); + mDialog.setOnCancelListener(this); mDialog.create(); mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); final Window w = mDialog.getWindow(); - // QS is not closed when pressing CastTile. Match the type of the dialog shown from the - // tile. - w.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); mDialog.show(); diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index c1c3435f35ff..561c10b93e34 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -1322,9 +1322,11 @@ public final class AppRestrictionController { prefix = " " + prefix; pw.print(prefix); pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS"); + /* synchronized (mSettingsLock) { mRestrictionSettings.dumpLocked(pw, " " + prefix); } + */ mConstantsObserver.dump(pw, " " + prefix); for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) { pw.println(); diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index c42770555bab..176c08c8da29 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -78,10 +78,20 @@ public class LocaleManagerService extends SystemService { Process.THREAD_PRIORITY_BACKGROUND); broadcastHandlerThread.start(); + SystemAppUpdateTracker systemAppUpdateTracker = + new SystemAppUpdateTracker(this); + broadcastHandlerThread.getThreadHandler().postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + systemAppUpdateTracker.init(); + } + }); + mBackupHelper = new LocaleManagerBackupHelper(this, mPackageManagerInternal, broadcastHandlerThread); - mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper); + mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper, + systemAppUpdateTracker); mPackageMonitor.register(context, broadcastHandlerThread.getLooper(), UserHandle.ALL, true); @@ -246,7 +256,7 @@ public class LocaleManagerService extends SystemService { * <p><b>Note:</b> This is can be used by installers to deal with cases such as * language-based APK Splits. */ - private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, + void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { String installingPackageName = getInstallingPackageName(appPackageName); if (installingPackageName != null) { @@ -271,7 +281,7 @@ public class LocaleManagerService extends SystemService { mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } - private static Intent createBaseIntent(String intentAction, String appPackageName, + static Intent createBaseIntent(String intentAction, String appPackageName, LocaleList locales) { return new Intent(intentAction) .putExtra(Intent.EXTRA_PACKAGE_NAME, appPackageName) @@ -406,7 +416,7 @@ public class LocaleManagerService extends SystemService { } @Nullable - private String getInstallingPackageName(String packageName) { + String getInstallingPackageName(String packageName) { try { return mContext.getPackageManager() .getInstallSourceInfo(packageName).getInstallingPackageName(); diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java index b459be768b9f..32080ef356ae 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java +++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java @@ -23,13 +23,22 @@ import com.android.internal.content.PackageMonitor; * * <p> These listeners forward the call to different aspects of locale service that * handle the business logic. - * <p> We're interested in package added, package data cleared and package removed events. + * <p> We're interested in the following events: + * <ul> + * <li> Package added + * <li> Package data cleared + * <li> Package removed + * <li> Package Updated + * </ul> */ final class LocaleManagerServicePackageMonitor extends PackageMonitor { private LocaleManagerBackupHelper mBackupHelper; + private SystemAppUpdateTracker mSystemAppUpdateTracker; - LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper) { + LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper, + SystemAppUpdateTracker systemAppUpdateTracker) { mBackupHelper = localeManagerBackupHelper; + mSystemAppUpdateTracker = systemAppUpdateTracker; } @Override @@ -46,4 +55,9 @@ final class LocaleManagerServicePackageMonitor extends PackageMonitor { public void onPackageRemoved(String packageName, int uid) { mBackupHelper.onPackageRemoved(); } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid); + } } diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java new file mode 100644 index 000000000000..d13b1f4d2845 --- /dev/null +++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locales; + +import static com.android.server.locales.LocaleManagerService.DEBUG; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.os.LocaleList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.XmlUtils; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +/** + * Track if a system app is being updated for the first time after the user completed device setup. + * + * <p> The entire operation is being done on a background thread from {@link LocaleManagerService}. + * If it is the first time that a system app is being updated, then it fetches the app-specific + * locales and sends a broadcast to the newly set installer of the app. It maintains a file to store + * the name of the apps that have been updated. + */ +public class SystemAppUpdateTracker { + private static final String TAG = "SystemAppUpdateTracker"; + private static final String PACKAGE_XML_TAG = "package"; + private static final String ATTR_NAME = "name"; + private static final String SYSTEM_APPS_XML_TAG = "system_apps"; + + private final Context mContext; + private final LocaleManagerService mLocaleManagerService; + private final AtomicFile mUpdatedAppsFile; + + // Lock used while writing to the file. + private final Object mFileLock = new Object(); + + // In-memory list of all the system apps that have been updated once after device setup. + // We do not need to store the userid->packages mapping because when updating a system app on + // one user updates for all users. + private final Set<String> mUpdatedApps = new HashSet<>(); + + SystemAppUpdateTracker(LocaleManagerService localeManagerService) { + this(localeManagerService.mContext, localeManagerService, new AtomicFile( + new File(Environment.getDataSystemDirectory(), + /* child = */ "locale_manager_service_updated_system_apps.xml"))); + } + + @VisibleForTesting + SystemAppUpdateTracker(Context context, LocaleManagerService localeManagerService, + AtomicFile file) { + mContext = context; + mLocaleManagerService = localeManagerService; + mUpdatedAppsFile = file; + } + + /** + * Loads the info of updated system apps from the file. + * + * <p> Invoked once during device boot from {@link LocaleManagerService} by a background thread. + */ + void init() { + if (DEBUG) { + Slog.d(TAG, "Loading the app info from storage. "); + } + loadUpdatedSystemApps(); + } + + /** + * Reads the XML stored in the {@link #mUpdatedAppsFile} and populates it in the in-memory list + * {@link #mUpdatedApps}. + */ + private void loadUpdatedSystemApps() { + if (!mUpdatedAppsFile.getBaseFile().exists()) { + if (DEBUG) { + Slog.d(TAG, "loadUpdatedSystemApps: File does not exist."); + } + return; + } + InputStream updatedAppNamesInputStream = null; + try { + updatedAppNamesInputStream = mUpdatedAppsFile.openRead(); + readFromXml(updatedAppNamesInputStream); + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "loadUpdatedSystemApps: Could not parse storage file ", e); + } finally { + IoUtils.closeQuietly(updatedAppNamesInputStream); + } + } + + /** + * Parses the update data from the serialized XML input stream. + */ + private void readFromXml(InputStream updateInfoInputStream) + throws XmlPullParserException, IOException { + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(updateInfoInputStream, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(parser, SYSTEM_APPS_XML_TAG); + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (parser.getName().equals(PACKAGE_XML_TAG)) { + String packageName = parser.getAttributeValue(/* namespace= */ null, + ATTR_NAME); + if (!TextUtils.isEmpty(packageName)) { + mUpdatedApps.add(packageName); + } + } + } + } + + /** + * Sends a broadcast to the newly set installer with app-locales if it is a system app being + * updated for the first time. + * + * <p><b>Note:</b> Invoked by service's common monitor + * {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package updated. + */ + void onPackageUpdateFinished(String packageName, int uid) { + try { + if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) { + // If a system app is updated, verify that it has an installer-on-record. + String installingPackageName = mLocaleManagerService.getInstallingPackageName( + packageName); + if (installingPackageName == null) { + // We want to broadcast the locales info to the installer. + // If this app does not have an installer then do nothing. + return; + } + + try { + int userId = UserHandle.getUserId(uid); + // Fetch the app-specific locales. + // If non-empty then send the info to the installer. + LocaleList appLocales = mLocaleManagerService.getApplicationLocales( + packageName, userId); + if (!appLocales.isEmpty()) { + // The broadcast would be sent to the newly set installer of the + // updated system app. + mLocaleManagerService.notifyInstallerOfAppWhoseLocaleChanged(packageName, + userId, appLocales); + } + } catch (RemoteException e) { + if (DEBUG) { + Slog.d(TAG, "onPackageUpdateFinished: Error in fetching app locales"); + } + } + updateBroadcastedAppsList(packageName); + } + } catch (Exception e) { + Slog.e(TAG, "Exception in onPackageUpdateFinished.", e); + } + } + + /** + * Writes in-memory data {@link #mUpdatedApps} to the storage file in a synchronized manner. + */ + private void updateBroadcastedAppsList(String packageName) { + synchronized (mFileLock) { + mUpdatedApps.add(packageName); + writeUpdatedAppsFileLocked(); + } + } + + private void writeUpdatedAppsFileLocked() { + FileOutputStream stream = null; + try { + stream = mUpdatedAppsFile.startWrite(); + writeToXmlLocked(stream); + mUpdatedAppsFile.finishWrite(stream); + } catch (IOException e) { + mUpdatedAppsFile.failWrite(stream); + Slog.e(TAG, "Failed to persist the updated apps list", e); + } + } + + /** + * Converts the list of updated app data into a serialized xml stream. + */ + private void writeToXmlLocked(OutputStream stream) throws IOException { + final TypedXmlSerializer xml = Xml.newFastSerializer(); + xml.setOutput(stream, StandardCharsets.UTF_8.name()); + xml.startDocument(/* encoding= */ null, /* standalone= */ true); + xml.startTag(/* namespace= */ null, SYSTEM_APPS_XML_TAG); + + for (String packageName : mUpdatedApps) { + xml.startTag(/* namespace= */ null, PACKAGE_XML_TAG); + xml.attribute(/* namespace= */ null, ATTR_NAME, packageName); + xml.endTag(/* namespace= */ null, PACKAGE_XML_TAG); + } + + xml.endTag(null, SYSTEM_APPS_XML_TAG); + xml.endDocument(); + } + + private boolean isUpdatedSystemApp(String packageName) { + ApplicationInfo appInfo = null; + try { + appInfo = mContext.getPackageManager().getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY)); + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Slog.d(TAG, "isUpdatedSystemApp: Package not found " + packageName); + } + } + if (appInfo == null) { + return false; + } + return (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + + @VisibleForTesting + Set<String> getUpdatedApps() { + return mUpdatedApps; + } +} diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java index 0287510041be..bd35be4d0f3e 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java @@ -107,6 +107,7 @@ public class LocaleManagerBackupRestoreTest { private PackageManager mMockPackageManager; @Mock private LocaleManagerService mMockLocaleManagerService; + BroadcastReceiver mUserMonitor; PackageMonitor mPackageMonitor; @@ -131,6 +132,7 @@ public class LocaleManagerBackupRestoreTest { mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockPackageManager = mock(PackageManager.class); mMockLocaleManagerService = mock(LocaleManagerService.class); + SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class); doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); @@ -144,7 +146,8 @@ public class LocaleManagerBackupRestoreTest { doNothing().when(mBackupHelper).notifyBackupManager(); mUserMonitor = mBackupHelper.getUserMonitor(); - mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper); + mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper, + systemAppUpdateTracker); setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS); } diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java new file mode 100644 index 000000000000..5185e15a8557 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locales; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.app.ActivityManagerInternal; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.os.Binder; +import android.os.Environment; +import android.os.LocaleList; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.AtomicFile; +import android.util.TypedXmlPullParser; +import android.util.Xml; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.XmlUtils; +import com.android.server.wm.ActivityTaskManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +/** + * Unit tests for {@link SystemAppUpdateTracker}. + */ +public class SystemAppUpdateTrackerTest { + private static final String DEFAULT_PACKAGE_NAME_1 = "com.android.myapp1"; + private static final String DEFAULT_PACKAGE_NAME_2 = "com.android.myapp2"; + private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB"; + private static final LocaleList DEFAULT_LOCALES = + LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); + private static final String PACKAGE_XML_TAG = "package"; + private static final String ATTR_NAME = "name"; + private static final String SYSTEM_APPS_XML_TAG = "system_apps"; + private static final int DEFAULT_USER_ID = 0; + + private AtomicFile mStoragefile; + private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer"; + private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo( + /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null, + /* originatingPackageName = */ null, + /* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME, + /* packageSource = */ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED); + + @Mock + private Context mMockContext; + @Mock + PackageManager mMockPackageManager; + @Mock + private PackageManagerInternal mMockPackageManagerInternal; + @Mock + private ActivityTaskManagerInternal mMockActivityTaskManager; + @Mock + private ActivityManagerInternal mMockActivityManager; + @Mock + private LocaleManagerBackupHelper mMockLocaleManagerBackupHelper; + @Mock + PackageMonitor mMockPackageMonitor; + + private LocaleManagerService mLocaleManagerService; + + // Object under test. + private SystemAppUpdateTracker mSystemAppUpdateTracker; + + @Before + public void setUp() throws Exception { + mMockContext = mock(Context.class); + mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class); + mMockActivityManager = mock(ActivityManagerInternal.class); + mMockPackageManagerInternal = mock(PackageManagerInternal.class); + mMockPackageMonitor = mock(PackageMonitor.class); + mMockLocaleManagerBackupHelper = mock(ShadowLocaleManagerBackupHelper.class); + mLocaleManagerService = new LocaleManagerService(mMockContext, + mMockActivityTaskManager, mMockActivityManager, + mMockPackageManagerInternal, mMockLocaleManagerBackupHelper, mMockPackageMonitor); + + doReturn(DEFAULT_USER_ID).when(mMockActivityManager) + .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(), + anyString(), anyString()); + + mMockPackageManager = mock(PackageManager.class); + doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) + .getInstallSourceInfo(anyString()); + doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + + mStoragefile = new AtomicFile(new File( + Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); + + mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext, + mLocaleManagerService, mStoragefile); + } + + @After + public void tearDown() { + mStoragefile.delete(); + } + + @Test + public void testInit_loadsCorrectly() throws Exception { + doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1, + /* isUpdatedSystemApp = */ true)) + .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); + + // Updates the app once so that it writes to the file. + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + Binder.getCallingUid()); + // Clear the in-memory data of updated apps + mSystemAppUpdateTracker.getUpdatedApps().clear(); + // Invoke init to verify if it correctly populates in-memory set. + mSystemAppUpdateTracker.init(); + + assertEquals(Set.of(DEFAULT_PACKAGE_NAME_1), mSystemAppUpdateTracker.getUpdatedApps()); + } + + @Test + public void testOnPackageUpdatedFinished_systemAppFirstUpdate_writesToFile() throws Exception { + doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1, + /* isUpdatedSystemApp = */ true)) + .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); + doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0, + DEFAULT_LOCALES)).when(mMockActivityTaskManager) + .getApplicationConfig(anyString(), anyInt()); + + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + Binder.getCallingUid()); + + assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES); + Set<String> expectedAppList = Set.of(DEFAULT_PACKAGE_NAME_1); + assertEquals(expectedAppList, mSystemAppUpdateTracker.getUpdatedApps()); + verifyStorageFileContents(expectedAppList); + } + + @Test + public void testOnPackageUpdatedFinished_systemAppSecondUpdate_doesNothing() throws Exception { + doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1, + /* isUpdatedSystemApp = */ true)) + .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); + doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0, + DEFAULT_LOCALES)).when(mMockActivityTaskManager) + .getApplicationConfig(anyString(), anyInt()); + + // first update + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + Binder.getCallingUid()); + + assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES); + Set<String> expectedAppList = Set.of(DEFAULT_PACKAGE_NAME_1); + assertEquals(expectedAppList, mSystemAppUpdateTracker.getUpdatedApps()); + verifyStorageFileContents(expectedAppList); + + // second update + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + Binder.getCallingUid()); + // getApplicationLocales should be invoked only once on the first update. + verify(mMockActivityTaskManager, times(1)) + .getApplicationConfig(anyString(), anyInt()); + // Broadcast should be sent only once on first update. + verify(mMockContext, times(1)).sendBroadcastAsUser(any(), any()); + // Verify that the content remains the same. + verifyStorageFileContents(expectedAppList); + } + + @Test + public void testOnPackageUpdatedFinished_notSystemApp_doesNothing() throws Exception { + doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_2, + /* isUpdatedSystemApp = */false)) + .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_2), any()); + + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_2, + Binder.getCallingUid()); + + assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2)); + // getApplicationLocales should be never be invoked if not a system app. + verifyZeroInteractions(mMockActivityTaskManager); + // Broadcast should be never sent if not a system app. + verify(mMockContext, never()).sendBroadcastAsUser(any(), any()); + // It shouldn't write to the file if not a system app. + assertTrue(!mStoragefile.getBaseFile().isFile()); + } + + @Test + public void testOnPackageUpdatedFinished_noInstaller_doesNothing() throws Exception { + doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1, + /* isUpdatedSystemApp = */ true)) + .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any()); + doReturn(null).when(mMockPackageManager).getInstallSourceInfo(anyString()); + + mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1, + Binder.getCallingUid()); + + // getApplicationLocales should be never be invoked if not installer is not present. + verifyZeroInteractions(mMockActivityTaskManager); + // Broadcast should be never sent if installer is not present. + verify(mMockContext, never()).sendBroadcastAsUser(any(), any()); + // It shouldn't write to file if no installer present. + assertTrue(!mStoragefile.getBaseFile().isFile()); + } + + private void verifyStorageFileContents(Set<String> expectedAppList) + throws IOException, XmlPullParserException { + assertTrue(mStoragefile.getBaseFile().isFile()); + try (InputStream storageInputStream = mStoragefile.openRead()) { + assertEquals(expectedAppList, readFromXml(storageInputStream)); + } catch (IOException | XmlPullParserException e) { + throw e; + } + } + + private Set<String> readFromXml(InputStream storageInputStream) + throws XmlPullParserException, IOException { + Set<String> outputList = new HashSet<>(); + final TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(storageInputStream, StandardCharsets.UTF_8.name()); + XmlUtils.beginDocument(parser, SYSTEM_APPS_XML_TAG); + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (parser.getName().equals(PACKAGE_XML_TAG)) { + String packageName = parser.getAttributeValue(/* namespace= */ null, + ATTR_NAME); + if (!TextUtils.isEmpty(packageName)) { + outputList.add(packageName); + } + } + } + return outputList; + } + + /** + * Verifies the broadcast sent to the installer of the updated app. + */ + private void assertBroadcastSentToInstaller(String packageName, LocaleList locales) { + ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); + verify(mMockContext).sendBroadcastAsUser(captor.capture(), any(UserHandle.class)); + for (Intent intent : captor.getAllValues()) { + assertTrue(Intent.ACTION_APPLICATION_LOCALE_CHANGED.equals(intent.getAction())); + assertTrue(DEFAULT_INSTALLER_PACKAGE_NAME.equals(intent.getPackage())); + assertTrue(packageName.equals(intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME))); + assertTrue(locales.equals(intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST))); + } + } + + private ApplicationInfo createApplicationInfoForApp(String packageName, + boolean isUpdatedSystemApp) { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = packageName; + if (isUpdatedSystemApp) { + applicationInfo.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + } + return applicationInfo; + } +} diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index 8d9252019538..7129a27c3cd6 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -29,7 +29,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.telephony.TelephonyManager; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; import android.telephony.euicc.EuiccManager; @@ -744,18 +743,16 @@ public abstract class EuiccService extends Service { public void run() { DownloadSubscriptionResult result; try { - result = - EuiccService.this.onDownloadSubscription( - slotId, subscription, switchAfterDownload, forceDeactivateSim, - resolvedBundle); + result = EuiccService.this.onDownloadSubscription( + slotId, portIndex, subscription, switchAfterDownload, + forceDeactivateSim, resolvedBundle); } catch (AbstractMethodError e) { - Log.w(TAG, "The new onDownloadSubscription(int, " + Log.w(TAG, "The new onDownloadSubscription(int, int, " + "DownloadableSubscription, boolean, boolean, Bundle) is not " + "implemented. Fall back to the old one.", e); - int resultCode = EuiccService.this.onDownloadSubscription( - slotId, subscription, switchAfterDownload, forceDeactivateSim); - result = new DownloadSubscriptionResult(resultCode, - 0 /* resolvableErrors */, TelephonyManager.UNSUPPORTED_CARD_ID); + result = EuiccService.this.onDownloadSubscription( + slotId, subscription, switchAfterDownload, + forceDeactivateSim, resolvedBundle); } try { callback.onComplete(result); |