summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/layout/media_projection_dialog_title.xml40
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java27
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java2
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java18
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java18
-rw-r--r--services/core/java/com/android/server/locales/SystemAppUpdateTracker.java249
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java297
-rw-r--r--telephony/java/android/service/euicc/EuiccService.java17
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);