diff options
Diffstat (limited to 'services')
86 files changed, 4046 insertions, 2240 deletions
diff --git a/services/appwidget/Android.mk b/services/appwidget/Android.mk index ca38f2f..e9bab4a 100644 --- a/services/appwidget/Android.mk +++ b/services/appwidget/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.appwidget LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 609ffda..c6aa30b 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -20,6 +20,7 @@ import android.app.AlarmManager; import android.app.AppGlobals; import android.app.PendingIntent; import android.app.admin.DevicePolicyManagerInternal; +import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; @@ -102,7 +103,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider { +class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, + OnCrossProfileWidgetProvidersChangeListener { private static final String TAG = "AppWidgetServiceImpl"; private static boolean DEBUG = false; @@ -199,6 +201,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSecurityPolicy = new SecurityPolicy(); computeMaximumWidgetBitmapMemory(); registerBroadcastReceiver(); + registerOnCrossProfileProvidersChangedListener(); } private void computeMaximumWidgetBitmapMemory() { @@ -243,6 +246,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku userFilter, null, null); } + private void registerOnCrossProfileProvidersChangedListener() { + DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService( + DevicePolicyManagerInternal.class); + // The device policy is an optional component. + if (devicePolicyManager != null) { + devicePolicyManager.addOnCrossProfileWidgetProvidersChangeListener(this); + } + } + public void setSafeMode(boolean safeMode) { mSafeMode = safeMode; } @@ -368,7 +380,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku saveGroupStateAsync(userId); // If the set of providers has been modified, notify each active AppWidgetHost - scheduleNotifyHostsForProvidersChangedLocked(); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); } } } @@ -1657,11 +1669,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private void scheduleNotifyHostsForProvidersChangedLocked() { + private void scheduleNotifyGroupHostsForProvidersChangedLocked(int userId) { + final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId); + final int N = mHosts.size(); for (int i = N - 1; i >= 0; i--) { Host host = mHosts.get(i); + boolean hostInGroup = false; + final int M = profileIds.length; + for (int j = 0; j < M; j++) { + final int profileId = profileIds[j]; + if (host.getUserId() == profileId) { + hostInGroup = true; + break; + } + } + + if (!hostInGroup) { + continue; + } + if (host == null || host.zombie || host.callbacks == null) { continue; } @@ -2617,6 +2645,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void onUserStopped(int userId) { synchronized (mLock) { + boolean providersChanged = false; + boolean crossProfileWidgetsChanged = false; + // Remove widgets that have both host and provider in the user. final int widgetCount = mWidgets.size(); for (int i = widgetCount - 1; i >= 0; i--) { @@ -2645,6 +2676,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = hostCount - 1; i >= 0; i--) { Host host = mHosts.get(i); if (host.getUserId() == userId) { + crossProfileWidgetsChanged |= !host.widgets.isEmpty(); deleteHostLocked(host); } } @@ -2654,6 +2686,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int i = providerCount - 1; i >= 0; i--) { Provider provider = mProviders.get(i); if (provider.getUserId() == userId) { + crossProfileWidgetsChanged |= !provider.widgets.isEmpty(); + providersChanged = true; deleteProviderLocked(provider); } } @@ -2678,6 +2712,17 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (nextIdIndex >= 0) { mNextAppWidgetIds.removeAt(nextIdIndex); } + + // Announce removed provider changes to all hosts in the group. + if (providersChanged) { + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } + + // Save state if removing a profile changed the group state. + // Nothing will be saved if the group parent was removed. + if (crossProfileWidgetsChanged) { + saveGroupStateAsync(userId); + } } } @@ -2843,6 +2888,31 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + @Override + public void onCrossProfileWidgetProvidersChanged(int userId, List<String> packages) { + final int parentId = mSecurityPolicy.getProfileParent(userId); + // We care only if the white-listed package is in a profile of + // the group parent as only the parent can add widgets from the + // profile and not the other way around. + if (parentId != userId) { + synchronized (mLock) { + boolean providersChanged = false; + + final int packageCount = packages.size(); + for (int i = 0; i < packageCount; i++) { + String packageName = packages.get(i); + providersChanged |= updateProvidersForPackageLocked(packageName, + userId, null); + } + + if (providersChanged) { + saveGroupStateAsync(userId); + scheduleNotifyGroupHostsForProvidersChangedLocked(userId); + } + } + } + } + private final class CallbackHandler extends Handler { public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1; public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2; diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index b2d1b71..b576324 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -34,7 +34,6 @@ import android.app.backup.IBackupManager; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; -import android.app.job.JobParameters; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -79,6 +78,7 @@ import android.os.storage.IMountService; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; +import android.util.ArrayMap; import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; @@ -308,12 +308,13 @@ public class BackupManagerService extends IBackupManager.Stub { volatile boolean mClearingData; // Transport bookkeeping - final HashMap<String,String> mTransportNames - = new HashMap<String,String>(); // component name -> registration name - final HashMap<String,IBackupTransport> mTransports - = new HashMap<String,IBackupTransport>(); // registration name -> binder - final ArrayList<TransportConnection> mTransportConnections - = new ArrayList<TransportConnection>(); + final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); + final ArrayMap<String,String> mTransportNames + = new ArrayMap<String,String>(); // component name -> registration name + final ArrayMap<String,IBackupTransport> mTransports + = new ArrayMap<String,IBackupTransport>(); // registration name -> binder + final ArrayMap<String,TransportConnection> mTransportConnections + = new ArrayMap<String,TransportConnection>(); String mCurrentTransport; ActiveRestoreSession mActiveRestoreSession; @@ -867,10 +868,12 @@ public class BackupManagerService extends IBackupManager.Stub { // the timeout is suspended while a restore is in progress. Clean // up now. Slog.w(TAG, "Restore session timed out; aborting"); + mActiveRestoreSession.markTimedOut(); post(mActiveRestoreSession.new EndRestoreRunnable( BackupManagerService.this, mActiveRestoreSession)); } } + break; } case MSG_FULL_CONFIRMATION_TIMEOUT: @@ -1062,9 +1065,8 @@ public class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport); // Find transport hosts and bind to their services - Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - transportServiceIntent, 0, UserHandle.USER_OWNER); + mTransportServiceIntent, 0, UserHandle.USER_OWNER); if (DEBUG) { Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size())); } @@ -1080,17 +1082,7 @@ public class BackupManagerService extends IBackupManager.Stub { ServiceInfo info = hosts.get(i).serviceInfo; PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0); if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { - ComponentName svcName = new ComponentName(info.packageName, info.name); - if (DEBUG) { - Slog.i(TAG, "Binding to transport host " + svcName); - } - Intent intent = new Intent(transportServiceIntent); - intent.setComponent(svcName); - TransportConnection connection = new TransportConnection(); - mTransportConnections.add(connection); - context.bindServiceAsUser(intent, - connection, Context.BIND_AUTO_CREATE, - UserHandle.OWNER); + bindTransport(info); } else { Slog.w(TAG, "Transport package not privileged: " + info.packageName); } @@ -1750,6 +1742,7 @@ public class BackupManagerService extends IBackupManager.Stub { String action = intent.getAction(); boolean replacing = false; boolean added = false; + boolean rebind = false; Bundle extras = intent.getExtras(); String pkgList[] = null; if (Intent.ACTION_PACKAGE_ADDED.equals(action) || @@ -1762,7 +1755,7 @@ public class BackupManagerService extends IBackupManager.Stub { if (pkgName != null) { pkgList = new String[] { pkgName }; } - added = Intent.ACTION_PACKAGE_ADDED.equals(action); + rebind = added = Intent.ACTION_PACKAGE_ADDED.equals(action); replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { added = true; @@ -1795,6 +1788,20 @@ public class BackupManagerService extends IBackupManager.Stub { enqueueFullBackup(packageName, now); scheduleNextFullBackupJob(); } + + // if this was the PACKAGE_ADDED conclusion of an upgrade of the package + // hosting one of our transports, we need to explicitly rebind now. + if (rebind) { + synchronized (mTransportConnections) { + final TransportConnection conn = mTransportConnections.get(packageName); + if (conn != null) { + if (DEBUG) { + Slog.i(TAG, "Transport package changed; rebinding"); + } + bindTransport(conn.mTransport); + } + } + } } catch (NameNotFoundException e) { // doesn't really exist; ignore it if (DEBUG) { @@ -1802,6 +1809,7 @@ public class BackupManagerService extends IBackupManager.Stub { } } } + } else { if (replacing) { // The package is being updated. We'll receive a PACKAGE_ADDED shortly. @@ -1816,6 +1824,12 @@ public class BackupManagerService extends IBackupManager.Stub { // ----- Track connection to transports service ----- class TransportConnection implements ServiceConnection { + ServiceInfo mTransport; + + public TransportConnection(ServiceInfo transport) { + mTransport = transport; + } + @Override public void onServiceConnected(ComponentName component, IBinder service) { if (DEBUG) Slog.v(TAG, "Connected to transport " + component); @@ -1839,6 +1853,32 @@ public class BackupManagerService extends IBackupManager.Stub { } }; + void bindTransport(ServiceInfo transport) { + ComponentName svcName = new ComponentName(transport.packageName, transport.name); + if (DEBUG) { + Slog.i(TAG, "Binding to transport host " + svcName); + } + Intent intent = new Intent(mTransportServiceIntent); + intent.setComponent(svcName); + + TransportConnection connection; + synchronized (mTransportConnections) { + connection = mTransportConnections.get(transport.packageName); + if (null == connection) { + connection = new TransportConnection(transport); + mTransportConnections.put(transport.packageName, connection); + } else { + // This is a rebind due to package upgrade. The service won't be + // automatically relaunched for us until we explicitly rebind, but + // we need to unbind the now-orphaned original connection. + mContext.unbindService(connection); + } + } + mContext.bindServiceAsUser(intent, + connection, Context.BIND_AUTO_CREATE, + UserHandle.OWNER); + } + // Add the backup agents in the given packages to our set of known backup participants. // If 'packageNames' is null, adds all backup agents in the whole system. void addPackageParticipantsLocked(String[] packageNames) { @@ -7538,6 +7578,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } + // Clear any ongoing session timeout. + mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); + // If we have a PM token, we must under all circumstances be sure to // handshake when we've finished. if (mPmToken > 0) { @@ -7545,6 +7588,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF try { mPackageManagerBinder.finishPackageInstall(mPmToken); } catch (RemoteException e) { /* can't happen */ } + } else { + // We were invoked via an active restore session, not by the Package + // Manager, so start up the session timeout again. + mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, + TIMEOUT_RESTORE_INTERVAL); } // Kick off any work that may be needed regarding app widget restores @@ -7558,11 +7606,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF writeRestoreTokens(); } - // Furthermore we need to reset the session timeout clock - mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT); - mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, - TIMEOUT_RESTORE_INTERVAL); - // done; we can finally release the wakelock and be legitimately done. Slog.i(TAG, "Restore complete."); mWakelock.release(); @@ -8599,7 +8642,15 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF skip = true; } - if (!skip && mAutoRestore && mProvisioned) { + if (!mAutoRestore || !mProvisioned) { + if (DEBUG) { + Slog.w(TAG, "Non-restorable state: auto=" + mAutoRestore + + " prov=" + mProvisioned); + } + skip = true; + } + + if (!skip) { try { // okay, we're going to attempt a restore of this package from this restore set. // The eventual message back into the Package Manager to run the post-install @@ -8632,7 +8683,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (skip) { // Auto-restore disabled or no way to attempt a restore; just tell the Package // Manager to proceed with the post-install handling for this package. - if (DEBUG) Slog.v(TAG, "Skipping"); + if (DEBUG) Slog.v(TAG, "Finishing install immediately"); try { mPackageManagerBinder.finishPackageInstall(token); } catch (RemoteException e) { /* can't happen */ } @@ -8726,12 +8777,17 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF private IBackupTransport mRestoreTransport = null; RestoreSet[] mRestoreSets = null; boolean mEnded = false; + boolean mTimedOut = false; ActiveRestoreSession(String packageName, String transport) { mPackageName = packageName; mRestoreTransport = getTransport(transport); } + public void markTimedOut() { + mTimedOut = true; + } + // --- Binder interface --- public synchronized int getAvailableRestoreSets(IRestoreObserver observer) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, @@ -8744,6 +8800,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore session already ended"); } + if (mTimedOut) { + Slog.i(TAG, "Session already timed out"); + return -1; + } + long oldId = Binder.clearCallingIdentity(); try { if (mRestoreTransport == null) { @@ -8775,6 +8836,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore session already ended"); } + if (mTimedOut) { + Slog.i(TAG, "Session already timed out"); + return -1; + } + if (mRestoreTransport == null || mRestoreSets == null) { Slog.e(TAG, "Ignoring restoreAll() with no restore set"); return -1; @@ -8849,6 +8915,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore session already ended"); } + if (mTimedOut) { + Slog.i(TAG, "Session already timed out"); + return -1; + } + if (mRestoreTransport == null || mRestoreSets == null) { Slog.e(TAG, "Ignoring restoreAll() with no restore set"); return -1; @@ -8897,6 +8968,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore session already ended"); } + if (mTimedOut) { + Slog.i(TAG, "Session already timed out"); + return -1; + } + if (mPackageName != null) { if (! mPackageName.equals(packageName)) { Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName @@ -8994,6 +9070,11 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF public synchronized void endRestoreSession() { if (DEBUG) Slog.d(TAG, "endRestoreSession"); + if (mTimedOut) { + Slog.i(TAG, "Session already timed out"); + return; + } + if (mEnded) { throw new IllegalStateException("Restore session already ended"); } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 07c9048..7b64139 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -121,6 +121,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private int mState; private final BluetoothHandler mHandler; private int mErrorRecoveryRetryCounter; + private final int mSystemUiUid; private void registerForAirplaneMode(IntentFilter filter) { final ContentResolver resolver = mContext.getContentResolver(); @@ -218,6 +219,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (isBluetoothPersistedStateOn()) { mEnableExternal = true; } + + int sysUiUid = -1; + try { + sysUiUid = mContext.getPackageManager().getPackageUid("com.android.systemui", + UserHandle.USER_OWNER); + } catch (PackageManager.NameNotFoundException e) { + Log.wtf(TAG, "Unable to resolve SystemUI's UID.", e); + } + mSystemUiUid = sysUiUid; } /** @@ -1118,7 +1128,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { try { foregroundUser = ActivityManager.getCurrentUser(); valid = (callingUser == foregroundUser) || - callingAppId == Process.NFC_UID; + callingAppId == Process.NFC_UID || + callingAppId == mSystemUiUid; if (DBG) { Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser=" + callingUser diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java deleted file mode 100644 index 7249985..0000000 --- a/services/core/java/com/android/server/BootReceiver.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2009 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; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.IPackageManager; -import android.os.Build; -import android.os.DropBoxManager; -import android.os.FileObserver; -import android.os.FileUtils; -import android.os.RecoverySystem; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemProperties; -import android.provider.Downloads; -import android.util.Slog; - -import java.io.File; -import java.io.IOException; - -/** - * Performs a number of miscellaneous, non-system-critical actions - * after the system has finished booting. - */ -public class BootReceiver extends BroadcastReceiver { - private static final String TAG = "BootReceiver"; - - // Maximum size of a logged event (files get truncated if they're longer). - // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. - private static final int LOG_SIZE = - SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; - - private static final File TOMBSTONE_DIR = new File("/data/tombstones"); - - // The pre-froyo package and class of the system updater, which - // ran in the system process. We need to remove its packages here - // in order to clean up after a pre-froyo-to-froyo update. - private static final String OLD_UPDATER_PACKAGE = - "com.google.android.systemupdater"; - private static final String OLD_UPDATER_CLASS = - "com.google.android.systemupdater.SystemUpdateReceiver"; - - // Keep a reference to the observer so the finalizer doesn't disable it. - private static FileObserver sTombstoneObserver = null; - - @Override - public void onReceive(final Context context, Intent intent) { - // Log boot events in the background to avoid blocking the main thread with I/O - new Thread() { - @Override - public void run() { - try { - logBootEvents(context); - } catch (Exception e) { - Slog.e(TAG, "Can't log boot events", e); - } - try { - boolean onlyCore = false; - try { - onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService( - "package")).isOnlyCoreApps(); - } catch (RemoteException e) { - } - if (!onlyCore) { - removeOldUpdatePackages(context); - } - } catch (Exception e) { - Slog.e(TAG, "Can't remove old update packages", e); - } - - } - }.start(); - } - - private void removeOldUpdatePackages(Context context) { - Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); - } - - private void logBootEvents(Context ctx) throws IOException { - final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); - final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE); - final String headers = new StringBuilder(512) - .append("Build: ").append(Build.FINGERPRINT).append("\n") - .append("Hardware: ").append(Build.BOARD).append("\n") - .append("Revision: ") - .append(SystemProperties.get("ro.revision", "")).append("\n") - .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") - .append("Radio: ").append(Build.RADIO).append("\n") - .append("Kernel: ") - .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) - .append("\n").toString(); - final String bootReason = SystemProperties.get("ro.boot.bootreason", null); - - String recovery = RecoverySystem.handleAftermath(); - if (recovery != null && db != null) { - db.addText("SYSTEM_RECOVERY_LOG", headers + recovery); - } - - String lastKmsgFooter = ""; - if (bootReason != null) { - lastKmsgFooter = new StringBuilder(512) - .append("\n") - .append("Boot info:\n") - .append("Last boot reason: ").append(bootReason).append("\n") - .toString(); - } - - if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) { - String now = Long.toString(System.currentTimeMillis()); - SystemProperties.set("ro.runtime.firstboot", now); - if (db != null) db.addText("SYSTEM_BOOT", headers); - - // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile()) - addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter, - "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG"); - addFileWithFootersToDropBox(db, prefs, headers, lastKmsgFooter, - "/sys/fs/pstore/console-ramoops", -LOG_SIZE, - "SYSTEM_LAST_KMSG"); - addFileToDropBox(db, prefs, headers, "/cache/recovery/log", - -LOG_SIZE, "SYSTEM_RECOVERY_LOG"); - addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console", - -LOG_SIZE, "APANIC_CONSOLE"); - addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads", - -LOG_SIZE, "APANIC_THREADS"); - addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT"); - addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK"); - } else { - if (db != null) db.addText("SYSTEM_RESTART", headers); - } - - // Scan existing tombstones (in case any new ones appeared) - File[] tombstoneFiles = TOMBSTONE_DIR.listFiles(); - for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) { - addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(), - LOG_SIZE, "SYSTEM_TOMBSTONE"); - } - - // Start watching for new tombstone files; will record them as they occur. - // This gets registered with the singleton file observer thread. - sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) { - @Override - public void onEvent(int event, String path) { - try { - String filename = new File(TOMBSTONE_DIR, path).getPath(); - addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE"); - } catch (IOException e) { - Slog.e(TAG, "Can't log tombstone", e); - } - } - }; - - sTombstoneObserver.startWatching(); - } - - private static void addFileToDropBox( - DropBoxManager db, SharedPreferences prefs, - String headers, String filename, int maxSize, String tag) throws IOException { - addFileWithFootersToDropBox(db, prefs, headers, "", filename, maxSize, - tag); - } - - private static void addFileWithFootersToDropBox( - DropBoxManager db, SharedPreferences prefs, - String headers, String footers, String filename, int maxSize, - String tag) throws IOException { - if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled - - File file = new File(filename); - long fileTime = file.lastModified(); - if (fileTime <= 0) return; // File does not exist - - if (prefs != null) { - long lastTime = prefs.getLong(filename, 0); - if (lastTime == fileTime) return; // Already logged this particular file - // TODO: move all these SharedPreferences Editor commits - // outside this function to the end of logBootEvents - prefs.edit().putLong(filename, fileTime).apply(); - } - - Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")"); - db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + footers); - } - - private static void addAuditErrorsToDropBox(DropBoxManager db, SharedPreferences prefs, - String headers, int maxSize, String tag) throws IOException { - if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled - Slog.i(TAG, "Copying audit failures to DropBox"); - - File file = new File("/proc/last_kmsg"); - long fileTime = file.lastModified(); - if (fileTime <= 0) { - file = new File("/sys/fs/pstore/console-ramoops"); - fileTime = file.lastModified(); - } - - if (fileTime <= 0) return; // File does not exist - - if (prefs != null) { - long lastTime = prefs.getLong(tag, 0); - if (lastTime == fileTime) return; // Already logged this particular file - // TODO: move all these SharedPreferences Editor commits - // outside this function to the end of logBootEvents - prefs.edit().putLong(tag, fileTime).apply(); - } - - String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); - StringBuilder sb = new StringBuilder(); - for (String line : log.split("\n")) { - if (line.contains("audit")) { - sb.append(line + "\n"); - } - } - Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); - db.addText(tag, headers + sb.toString()); - } - - private static void addFsckErrorsToDropBox(DropBoxManager db, SharedPreferences prefs, - String headers, int maxSize, String tag) throws IOException { - boolean upload_needed = false; - if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled - Slog.i(TAG, "Checking for fsck errors"); - - File file = new File("/dev/fscklogs/log"); - long fileTime = file.lastModified(); - if (fileTime <= 0) return; // File does not exist - - String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); - StringBuilder sb = new StringBuilder(); - for (String line : log.split("\n")) { - if (line.contains("FILE SYSTEM WAS MODIFIED")) { - upload_needed = true; - break; - } - } - - if (upload_needed) { - addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag); - } - - // Remove the file so we don't re-upload if the runtime restarts. - file.delete(); - } -} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index dd5a7ea..96f8324 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -264,6 +264,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private INetworkManagementService mNetd; private INetworkPolicyManager mPolicyManager; + private String mCurrentTcpBufferSizes; + private static final int ENABLED = 1; private static final int DISABLED = 0; @@ -754,10 +756,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mNextNetworkRequestId++; } - private synchronized int nextNetId() { - int netId = mNextNetId; - if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID; - return netId; + private void assignNextNetId(NetworkAgentInfo nai) { + synchronized (mNetworkForNetId) { + for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) { + int netId = mNextNetId; + if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID; + // Make sure NetID unused. http://b/16815182 + if (mNetworkForNetId.get(netId) == null) { + nai.network = new Network(netId); + mNetworkForNetId.put(netId, nai); + return; + } + } + } + throw new IllegalStateException("No free netIds"); } private int getConnectivityChangeDelay() { @@ -1534,11 +1546,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { return; } - if (mtu < 68 || mtu > 10000) { + if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address()) == false) { loge("Unexpected mtu value: " + mtu + ", " + iface); return; } + // Cannot set MTU without interface name + if (TextUtils.isEmpty(iface)) { + loge("Setting MTU size with null iface."); + return; + } + try { if (VDBG) log("Setting MTU size: " + iface + ", " + mtu); mNetd.setMtu(iface, mtu); @@ -1547,30 +1565,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Reads the network specific TCP buffer sizes from SystemProperties - * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system - * wide use - */ - private void updateNetworkSettings(NetworkStateTracker nt) { - String key = nt.getTcpBufferSizesPropName(); - String bufferSizes = key == null ? null : SystemProperties.get(key); + private static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208"; - if (TextUtils.isEmpty(bufferSizes)) { - if (VDBG) log(key + " not found in system properties. Using defaults"); + private void updateTcpBufferSizes(NetworkAgentInfo nai) { + if (isDefaultNetwork(nai) == false) { + return; + } - // Setting to default values so we won't be stuck to previous values - key = "net.tcp.buffersize.default"; - bufferSizes = SystemProperties.get(key); + String tcpBufferSizes = nai.linkProperties.getTcpBufferSizes(); + String[] values = null; + if (tcpBufferSizes != null) { + values = tcpBufferSizes.split(","); } - // Set values in kernel - if (bufferSizes.length() != 0) { - if (VDBG) { - log("Setting TCP values: [" + bufferSizes - + "] which comes from [" + key + "]"); - } - setBufferSize(bufferSizes); + if (values == null || values.length != 6) { + if (VDBG) log("Invalid tcpBufferSizes string: " + tcpBufferSizes +", using defaults"); + tcpBufferSizes = DEFAULT_TCP_BUFFER_SIZES; + values = tcpBufferSizes.split(","); + } + + if (tcpBufferSizes.equals(mCurrentTcpBufferSizes)) return; + + try { + if (VDBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes); + + final String prefix = "/sys/kernel/ipv4/tcp_"; + FileUtils.stringToFile(prefix + "rmem_min", values[0]); + FileUtils.stringToFile(prefix + "rmem_def", values[1]); + FileUtils.stringToFile(prefix + "rmem_max", values[2]); + FileUtils.stringToFile(prefix + "wmem_min", values[3]); + FileUtils.stringToFile(prefix + "wmem_def", values[4]); + FileUtils.stringToFile(prefix + "wmem_max", values[5]); + mCurrentTcpBufferSizes = tcpBufferSizes; + } catch (IOException e) { + loge("Can't set TCP buffer sizes:" + e); } final String defaultRwndKey = "net.tcp.default_init_rwnd"; @@ -1583,33 +1611,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max] - * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem - * - * @param bufferSizes in the format of "readMin, readInitial, readMax, - * writeMin, writeInitial, writeMax" - */ - private void setBufferSize(String bufferSizes) { - try { - String[] values = bufferSizes.split(","); - - if (values.length == 6) { - final String prefix = "/sys/kernel/ipv4/tcp_"; - FileUtils.stringToFile(prefix + "rmem_min", values[0]); - FileUtils.stringToFile(prefix + "rmem_def", values[1]); - FileUtils.stringToFile(prefix + "rmem_max", values[2]); - FileUtils.stringToFile(prefix + "wmem_min", values[3]); - FileUtils.stringToFile(prefix + "wmem_def", values[4]); - FileUtils.stringToFile(prefix + "wmem_max", values[5]); - } else { - loge("Invalid buffersize string: " + bufferSizes); - } - } catch (IOException e) { - loge("Can't set tcp buffer sizes:" + e); - } - } - private void flushVmDnsCache() { /* * Tell the VMs to toss their DNS caches @@ -1742,6 +1743,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private boolean isLiveNetworkAgent(NetworkAgentInfo nai, String msg) { + final NetworkAgentInfo officialNai; + synchronized (mNetworkForNetId) { + officialNai = mNetworkForNetId.get(nai.network.netId); + } + if (officialNai != null && officialNai.equals(nai)) return true; + if (officialNai != null || VDBG) { + loge(msg + " - validateNetworkAgent found mismatched netId: " + officialNai + + " - " + nai); + } + return false; + } + // must be stateless - things change under us. private class NetworkStateTrackerHandler extends Handler { public NetworkStateTrackerHandler(Looper looper) { @@ -1871,23 +1885,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { } case NetworkMonitor.EVENT_NETWORK_VALIDATED: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; - handleConnectionValidated(nai); + if (isLiveNetworkAgent(nai, "EVENT_NETWORK_VALIDATED")) { + handleConnectionValidated(nai); + } break; } case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: { NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; - handleLingerComplete(nai); + if (isLiveNetworkAgent(nai, "EVENT_NETWORK_LINGER_COMPLETE")) { + handleLingerComplete(nai); + } break; } case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { + NetworkAgentInfo nai = null; + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(msg.arg2); + } + if (nai == null) { + loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); + break; + } if (msg.arg1 == 0) { setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null); } else { - NetworkAgentInfo nai = mNetworkForNetId.get(msg.arg2); - if (nai == null) { - loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor"); - break; - } setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj); } @@ -1972,12 +1993,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ break; } - case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: { - info = (NetworkInfo) msg.obj; - int type = info.getType(); - if (mNetConfigs[type].isDefault()) updateNetworkSettings(mNetTrackers[type]); - break; - } } } } @@ -2104,13 +2119,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) { if (VDBG) log("apparently satisfied. currentScore=" + network.currentScore); if ((bestNetwork == null) || bestNetwork.currentScore < network.currentScore) { - bestNetwork = network; + if (!nri.isRequest) { + // Not setting bestNetwork here as a listening NetworkRequest may be + // satisfied by multiple Networks. Instead the request is added to + // each satisfying Network and notified about each. + network.addRequest(nri.request); + notifyNetworkCallback(network, nri); + } else { + bestNetwork = network; + } } } } if (bestNetwork != null) { if (VDBG) log("using " + bestNetwork.name()); - if (nri.isRequest && bestNetwork.networkInfo.isConnected()) { + if (bestNetwork.networkInfo.isConnected()) { // Cancel any lingering so the linger timeout doesn't teardown this network // even though we have a request for it. bestNetwork.networkLingered.clear(); @@ -2120,7 +2143,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetworkForRequestId.put(nri.request.requestId, bestNetwork); notifyNetworkCallback(bestNetwork, nri); score = bestNetwork.currentScore; - if (nri.isRequest && nri.request.legacyType != TYPE_NONE) { + if (nri.request.legacyType != TYPE_NONE) { mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork); } } @@ -2678,6 +2701,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** + * Set whether the current VPN package has the ability to launch VPNs without + * user intervention. This method is used by system UIs and not available + * in ConnectivityManager. Permissions are checked in Vpn class. + * @hide + */ + @Override + public void setVpnPackageAuthorization(boolean authorized) { + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + mVpns.get(user).setPackageAuthorization(authorized); + } + } + + /** * Configure a TUN interface and return its file descriptor. Parameters * are encoded and opaque to this class. This method is used by VpnBuilder * and not available in ConnectivityManager. Permissions are checked in @@ -3505,10 +3542,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { mIsProvisioningNetwork.set(false); // Check for apps that can handle provisioning first Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP); - provisioningIntent.addCategory(TelephonyIntents.CATEGORY_MCCMNC_PREFIX - + mTelephonyManager.getSimOperator()); - if (mContext.getPackageManager().resolveActivity(provisioningIntent, 0 /* flags */) - != null) { + List<String> carrierPackages = + mTelephonyManager.getCarrierPackageNamesForBroadcastIntent(provisioningIntent); + if (carrierPackages != null && !carrierPackages.isEmpty()) { + if (carrierPackages.size() != 1) { + if (DBG) log("Multiple matching carrier apps found, launching the first."); + } + provisioningIntent.setPackage(carrierPackages.get(0)); provisioningIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(provisioningIntent); @@ -3617,6 +3657,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { notification.icon = icon; notification.flags = Notification.FLAG_AUTO_CANCEL; notification.tickerText = title; + notification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); notification.contentIntent = intent; @@ -4117,7 +4159,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { int currentScore, NetworkMisc networkMisc) { enforceConnectivityInternalPermission(); - NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), nextNetId(), + NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new NetworkInfo(networkInfo), new LinkProperties(linkProperties), new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler, networkMisc); @@ -4131,9 +4173,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleRegisterNetworkAgent(NetworkAgentInfo na) { if (VDBG) log("Got NetworkAgent Messenger"); mNetworkAgentInfos.put(na.messenger, na); - synchronized (mNetworkForNetId) { - mNetworkForNetId.put(na.network.netId, na); - } + assignNextNetId(na); na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger); NetworkInfo networkInfo = na.networkInfo; na.networkInfo = null; @@ -4146,6 +4186,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateInterfaces(newLp, oldLp, netId); updateMtu(newLp, oldLp); + updateTcpBufferSizes(networkAgent); // TODO - figure out what to do for clat // for (LinkProperties lp : newLp.getStackedLinks()) { // updateMtu(lp, null); @@ -4371,6 +4412,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Exception setting default network :" + e); } handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy()); + updateTcpBufferSizes(newNetwork); } private void handleConnectionValidated(NetworkAgentInfo newNetwork) { @@ -4397,6 +4439,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (VDBG) log(" checking if request is satisfied: " + nri.request); if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities( newNetwork.networkCapabilities)) { + if (!nri.isRequest) { + newNetwork.addRequest(nri.request); + continue; + } // next check if it's better than any current network we're using for // this request if (VDBG) { @@ -4428,6 +4474,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { isNewDefault = true; updateActiveDefaultNetwork(newNetwork); if (newNetwork.linkProperties != null) { + updateTcpBufferSizes(newNetwork); setDefaultDnsSystemProperties( newNetwork.linkProperties.getDnsServers()); } else { @@ -4486,8 +4533,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { 1000); } } - // TODO - read the tcp buffer size config string from somewhere - // updateNetworkSettings(); } // Notify battery stats service about this network, both the normal diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 4687e3f..122786f 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -66,6 +66,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.inputmethodservice.InputMethodService; +import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -392,6 +393,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private InputMethodInfo[] mIms; private int[] mSubtypeIds; private Locale mLastSystemLocale; + private boolean mShowImeWithHardKeyboard; private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); private final IPackageManager mIPackageManager; @@ -407,17 +409,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.ENABLED_INPUT_METHODS), false, this); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this); } - @Override public void onChange(boolean selfChange) { + @Override public void onChange(boolean selfChange, Uri uri) { + final Uri showImeUri = + Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); synchronized (mMethodMap) { - boolean enabledChanged = false; - String newEnabled = mSettings.getEnabledInputMethodsStr(); - if (!mLastEnabled.equals(newEnabled)) { - mLastEnabled = newEnabled; - enabledChanged = true; + if (showImeUri.equals(uri)) { + updateKeyboardFromSettingsLocked(); + } else { + boolean enabledChanged = false; + String newEnabled = mSettings.getEnabledInputMethodsStr(); + if (!mLastEnabled.equals(newEnabled)) { + mLastEnabled = newEnabled; + enabledChanged = true; + } + updateInputMethodsFromSettingsLocked(enabledChanged); } - updateFromSettingsLocked(enabledChanged); } } } @@ -598,15 +608,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private class HardKeyboardListener implements WindowManagerService.OnHardKeyboardStatusChangeListener { @Override - public void onHardKeyboardStatusChange(boolean available, boolean enabled) { - mHandler.sendMessage(mHandler.obtainMessage( - MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0)); + public void onHardKeyboardStatusChange(boolean available) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, + available ? 1 : 0)); } - public void handleHardKeyboardStatusChange(boolean available, boolean enabled) { + public void handleHardKeyboardStatusChange(boolean available) { if (DEBUG) { - Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = " - + enabled); + Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); } synchronized(mMethodMap) { if (mSwitchingDialog != null && mSwitchingDialogTitleView != null @@ -1579,6 +1588,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName( mContext, imi, mCurrentSubtype); + mImeSwitcherNotification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); mImeSwitcherNotification.setLatestEventInfo( mContext, title, summary, mImeSwitchPendingIntent); if ((mNotificationManager != null) @@ -1656,6 +1667,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } void updateFromSettingsLocked(boolean enabledMayChange) { + updateInputMethodsFromSettingsLocked(enabledMayChange); + updateKeyboardFromSettingsLocked(); + } + + void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { if (enabledMayChange) { List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); for (int i=0; i<enabled.size(); i++) { @@ -1709,6 +1725,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Make sure that mSwitchingController and mSettings are sharing the // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); + + } + + public void updateKeyboardFromSettingsLocked() { + mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled(); + if (mSwitchingDialog != null + && mSwitchingDialogTitleView != null + && mSwitchingDialog.isShowing()) { + final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_switch); + hardKeySwitch.setChecked(mShowImeWithHardKeyboard); + } } /* package */ void setInputMethodLocked(String id, int subtypeId) { @@ -2593,8 +2621,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: - mHardKeyboardListener.handleHardKeyboardStatusChange( - msg.arg1 == 1, msg.arg2 == 1); + mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1); return true; } return false; @@ -2683,7 +2710,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!map.containsKey(defaultImiId)) { Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); if (chooseNewDefaultIMELocked()) { - updateFromSettingsLocked(true); + updateInputMethodsFromSettingsLocked(true); } } else { // Double check that the default IME is certainly enabled. @@ -2810,11 +2837,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ? View.VISIBLE : View.GONE); final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById( com.android.internal.R.id.hard_keyboard_switch); - hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled()); + hardKeySwitch.setChecked(mShowImeWithHardKeyboard); hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mWindowManagerService.setHardKeyboardEnabled(isChecked); + mSettings.setShowImeWithHardKeyboard(isChecked); // Ensure that the input method dialog is dismissed when changing // the hardware keyboard state. hideInputMethodMenu(); diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 64b0487..07cc864 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -69,6 +69,124 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } } + private boolean filterEquals(IntentFilter f1, IntentFilter f2) { + int s1 = f1.countActions(); + int s2 = f2.countActions(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasAction(f1.getAction(i))) { + return false; + } + } + s1 = f1.countCategories(); + s2 = f2.countCategories(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasCategory(f1.getCategory(i))) { + return false; + } + } + s1 = f1.countDataTypes(); + s2 = f2.countDataTypes(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasExactDataType(f1.getDataType(i))) { + return false; + } + } + s1 = f1.countDataSchemes(); + s2 = f2.countDataSchemes(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataScheme(f1.getDataScheme(i))) { + return false; + } + } + s1 = f1.countDataAuthorities(); + s2 = f2.countDataAuthorities(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataAuthority(f1.getDataAuthority(i))) { + return false; + } + } + s1 = f1.countDataPaths(); + s2 = f2.countDataPaths(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataPath(f1.getDataPath(i))) { + return false; + } + } + s1 = f1.countDataSchemeSpecificParts(); + s2 = f2.countDataSchemeSpecificParts(); + if (s1 != s2) { + return false; + } + for (int i=0; i<s1; i++) { + if (!f2.hasDataSchemeSpecificPart(f1.getDataSchemeSpecificPart(i))) { + return false; + } + } + return true; + } + + private ArrayList<F> collectFilters(F[] array, IntentFilter matching) { + ArrayList<F> res = null; + if (array != null) { + for (int i=0; i<array.length; i++) { + F cur = array[i]; + if (cur == null) { + break; + } + if (filterEquals(cur, matching)) { + if (res == null) { + res = new ArrayList<>(); + } + res.add(cur); + } + } + } + return res; + } + + public ArrayList<F> findFilters(IntentFilter matching) { + if (matching.countDataSchemes() == 1) { + // Fast case. + return collectFilters(mSchemeToFilter.get(matching.getDataScheme(0)), matching); + } else if (matching.countDataTypes() != 0 && matching.countActions() == 1) { + // Another fast case. + return collectFilters(mTypedActionToFilter.get(matching.getAction(0)), matching); + } else if (matching.countDataTypes() == 0 && matching.countDataSchemes() == 0 + && matching.countActions() == 1) { + // Last fast case. + return collectFilters(mActionToFilter.get(matching.getAction(0)), matching); + } else { + ArrayList<F> res = null; + for (F cur : mFilters) { + if (filterEquals(cur, matching)) { + if (res == null) { + res = new ArrayList<>(); + } + res.add(cur); + } + } + return res; + } + } + public void removeFilter(F f) { removeFilterInternal(f); mFilters.remove(f); diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index ab4d4dc..395e365 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -17,9 +17,9 @@ package com.android.server; import android.Manifest.permission; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.INetworkScoreCache; import android.net.INetworkScoreService; @@ -30,6 +30,7 @@ import android.net.ScoredNetwork; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -51,9 +52,6 @@ import java.util.Set; public class NetworkScoreService extends INetworkScoreService.Stub { private static final String TAG = "NetworkScoreService"; - /** SharedPreference bit set to true after the service is first initialized. */ - private static final String PREF_SCORING_PROVISIONED = "is_provisioned"; - private final Context mContext; private final Map<Integer, INetworkScoreCache> mScoreCaches; @@ -65,8 +63,8 @@ public class NetworkScoreService extends INetworkScoreService.Stub { /** Called when the system is ready to run third-party code but before it actually does so. */ void systemReady() { - SharedPreferences prefs = mContext.getSharedPreferences(TAG, Context.MODE_PRIVATE); - if (!prefs.getBoolean(PREF_SCORING_PROVISIONED, false)) { + ContentResolver cr = mContext.getContentResolver(); + if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) { // On first run, we try to initialize the scorer to the one configured at build time. // This will be a no-op if the scorer isn't actually valid. String defaultPackage = mContext.getResources().getString( @@ -74,7 +72,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { if (!TextUtils.isEmpty(defaultPackage)) { NetworkScorerAppManager.setActiveScorer(mContext, defaultPackage); } - prefs.edit().putBoolean(PREF_SCORING_PROVISIONED, true).apply(); + Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1); } } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java new file mode 100644 index 0000000..6e67970 --- /dev/null +++ b/services/core/java/com/android/server/SystemService.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013 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; + +import android.content.Context; +import android.os.IBinder; +import android.os.ServiceManager; + +/** + * The base class for services running in the system process. Override and implement + * the lifecycle event callback methods as needed. + * <p> + * The lifecycle of a SystemService: + * </p><ul> + * <li>The constructor is called and provided with the system {@link Context} + * to initialize the system service. + * <li>{@link #onStart()} is called to get the service running. The service should + * publish its binder interface at this point using + * {@link #publishBinderService(String, IBinder)}. It may also publish additional + * local interfaces that other services within the system server may use to access + * privileged internal functions. + * <li>Then {@link #onBootPhase(int)} is called as many times as there are boot phases + * until {@link #PHASE_BOOT_COMPLETE} is sent, which is the last boot phase. Each phase + * is an opportunity to do special work, like acquiring optional service dependencies, + * waiting to see if SafeMode is enabled, or registering with a service that gets + * started after this one. + * </ul><p> + * NOTE: All lifecycle methods are called from the system server's main looper thread. + * </p> + * + * {@hide} + */ +public abstract class SystemService { + /* + * Boot Phases + */ + public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency? + + /** + * After receiving this boot phase, services can obtain lock settings data. + */ + public static final int PHASE_LOCK_SETTINGS_READY = 480; + + /** + * After receiving this boot phase, services can safely call into core system services + * such as the PowerManager or PackageManager. + */ + public static final int PHASE_SYSTEM_SERVICES_READY = 500; + + /** + * After receiving this boot phase, services can broadcast Intents. + */ + public static final int PHASE_ACTIVITY_MANAGER_READY = 550; + + /** + * After receiving this boot phase, services can start/bind to third party apps. + * Apps will be able to make Binder calls into services at this point. + */ + public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; + + /** + * After receiving this boot phase, services can allow user interaction with the device. + * This phase occurs when boot has completed and the home application has started. + * System services may prefer to listen to this phase rather than registering a + * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency. + */ + public static final int PHASE_BOOT_COMPLETED = 1000; + + private final Context mContext; + + /** + * Initializes the system service. + * <p> + * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + * </p> + * + * @param context The system server context. + */ + public SystemService(Context context) { + mContext = context; + } + + /** + * Gets the system context. + */ + public final Context getContext() { + return mContext; + } + + /** + * Returns true if the system is running in safe mode. + * TODO: we should define in which phase this becomes valid + */ + public final boolean isSafeMode() { + return getManager().isSafeMode(); + } + + /** + * Called when the dependencies listed in the @Service class-annotation are available + * and after the chosen start phase. + * When this method returns, the service should be published. + */ + public abstract void onStart(); + + /** + * Called on each phase of the boot process. Phases before the service's start phase + * (as defined in the @Service annotation) are never received. + * + * @param phase The current boot phase. + */ + public void onBootPhase(int phase) {} + + /** + * Called when a new user is starting, for system services to initialize any per-user + * state they maintain for running users. + * @param userHandle The identifier of the user. + */ + public void onStartUser(int userHandle) {} + + /** + * Called when switching to a different foreground user, for system services that have + * special behavior for whichever user is currently in the foreground. This is called + * before any application processes are aware of the new user. + * @param userHandle The identifier of the user. + */ + public void onSwitchUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called prior to sending the SHUTDOWN + * broadcast to the user; it is a good place to stop making use of any resources of that + * user (such as binding to a service running in the user). + * @param userHandle The identifier of the user. + */ + public void onStopUser(int userHandle) {} + + /** + * Called when an existing user is stopping, for system services to finalize any per-user + * state they maintain for running users. This is called after all application process + * teardown of the user is complete. + * @param userHandle The identifier of the user. + */ + public void onCleanupUser(int userHandle) {} + + /** + * Publish the service so it is accessible to other services and apps. + */ + protected final void publishBinderService(String name, IBinder service) { + publishBinderService(name, service, false); + } + + /** + * Publish the service so it is accessible to other services and apps. + */ + protected final void publishBinderService(String name, IBinder service, + boolean allowIsolated) { + ServiceManager.addService(name, service, allowIsolated); + } + + /** + * Get a binder service by its name. + */ + protected final IBinder getBinderService(String name) { + return ServiceManager.getService(name); + } + + /** + * Publish the service so it is only accessible to the system process. + */ + protected final <T> void publishLocalService(Class<T> type, T service) { + LocalServices.addService(type, service); + } + + /** + * Get a local service by interface. + */ + protected final <T> T getLocalService(Class<T> type) { + return LocalServices.getService(type); + } + + private SystemServiceManager getManager() { + return LocalServices.getService(SystemServiceManager.class); + } +} diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java new file mode 100644 index 0000000..fda6479 --- /dev/null +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2013 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; + +import android.content.Context; +import android.util.Slog; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; + +/** + * Manages creating, starting, and other lifecycle events of + * {@link com.android.server.SystemService system services}. + * + * {@hide} + */ +public class SystemServiceManager { + private static final String TAG = "SystemServiceManager"; + + private final Context mContext; + private boolean mSafeMode; + + // Services that should receive lifecycle events. + private final ArrayList<SystemService> mServices = new ArrayList<SystemService>(); + + private int mCurrentPhase = -1; + + public SystemServiceManager(Context context) { + mContext = context; + } + + /** + * Starts a service by class name. + * + * @return The service instance. + */ + @SuppressWarnings("unchecked") + public SystemService startService(String className) { + final Class<SystemService> serviceClass; + try { + serviceClass = (Class<SystemService>)Class.forName(className); + } catch (ClassNotFoundException ex) { + Slog.i(TAG, "Starting " + className); + throw new RuntimeException("Failed to create service " + className + + ": service class not found, usually indicates that the caller should " + + "have called PackageManager.hasSystemFeature() to check whether the " + + "feature is available on this device before trying to start the " + + "services that implement it", ex); + } + return startService(serviceClass); + } + + /** + * Creates and starts a system service. The class must be a subclass of + * {@link com.android.server.SystemService}. + * + * @param serviceClass A Java class that implements the SystemService interface. + * @return The service instance, never null. + * @throws RuntimeException if the service fails to start. + */ + @SuppressWarnings("unchecked") + public <T extends SystemService> T startService(Class<T> serviceClass) { + final String name = serviceClass.getName(); + Slog.i(TAG, "Starting " + name); + + // Create the service. + if (!SystemService.class.isAssignableFrom(serviceClass)) { + throw new RuntimeException("Failed to create " + name + + ": service must extend " + SystemService.class.getName()); + } + final T service; + try { + Constructor<T> constructor = serviceClass.getConstructor(Context.class); + service = constructor.newInstance(mContext); + } catch (InstantiationException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service could not be instantiated", ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service must have a public constructor with a Context argument", ex); + } catch (NoSuchMethodException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service must have a public constructor with a Context argument", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service constructor threw an exception", ex); + } + + // Register it. + mServices.add(service); + + // Start it. + try { + service.onStart(); + } catch (RuntimeException ex) { + throw new RuntimeException("Failed to start service " + name + + ": onStart threw an exception", ex); + } + return service; + } + + /** + * Starts the specified boot phase for all system services that have been started up to + * this point. + * + * @param phase The boot phase to start. + */ + public void startBootPhase(final int phase) { + if (phase <= mCurrentPhase) { + throw new IllegalArgumentException("Next phase must be larger than previous"); + } + mCurrentPhase = phase; + + Slog.i(TAG, "Starting phase " + mCurrentPhase); + + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onBootPhase(mCurrentPhase); + } catch (Exception ex) { + throw new RuntimeException("Failed to boot service " + + service.getClass().getName() + + ": onBootPhase threw an exception during phase " + + mCurrentPhase, ex); + } + } + } + + public void startUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStartUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting start of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void switchUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onSwitchUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting switch of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void stopUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onStopUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting stop of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + public void cleanupUser(final int userHandle) { + final int serviceLen = mServices.size(); + for (int i = 0; i < serviceLen; i++) { + final SystemService service = mServices.get(i); + try { + service.onCleanupUser(userHandle); + } catch (Exception ex) { + Slog.wtf(TAG, "Failure reporting cleanup of user " + userHandle + + " to service " + service.getClass().getName(), ex); + } + } + } + + /** Sets the safe mode flag for services to query. */ + public void setSafeMode(boolean safeMode) { + mSafeMode = safeMode; + } + + /** + * Returns whether we are booting into safe mode. + * @return safe mode flag + */ + public boolean isSafeMode() { + return mSafeMode; + } + + /** + * Outputs the state of this manager to the System log. + */ + public void dump() { + StringBuilder builder = new StringBuilder(); + builder.append("Current phase: ").append(mCurrentPhase).append("\n"); + builder.append("Services:\n"); + final int startedLen = mServices.size(); + for (int i = 0; i < startedLen; i++) { + final SystemService service = mServices.get(i); + builder.append("\t") + .append(service.getClass().getSimpleName()) + .append("\n"); + } + + Slog.e(TAG, builder.toString()); + } +} diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 6ab52c6..d1b4569 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -599,6 +599,8 @@ final class UiModeManagerService extends SystemService { n.defaults = Notification.DEFAULT_LIGHTS; n.flags = Notification.FLAG_ONGOING_EVENT; n.when = 0; + n.color = context.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo( context, context.getString(R.string.car_mode_disable_notification_title), diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 2a66baf..94d979e 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -1700,6 +1700,8 @@ public class AccountManagerService subtitle = titleAndSubtitle.substring(index + 1); } UserHandle user = new UserHandle(userId); + n.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo(mContext, title, subtitle, PendingIntent.getActivityAsUser(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, user)); @@ -2968,6 +2970,8 @@ public class AccountManagerService UserHandle user = new UserHandle(userId); final String notificationTitleFormat = mContext.getText(R.string.notification_title).toString(); + n.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo(mContext, String.format(notificationTitleFormat, account.name), message, PendingIntent.getActivityAsUser( diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ad2704a..b1d84f5 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1472,6 +1472,8 @@ public final class ActivityManagerService extends ActivityManagerNative notification.defaults = 0; // please be quiet notification.sound = null; notification.vibrate = null; + notification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(context, text, mContext.getText(R.string.heavy_weight_notification_detail), PendingIntent.getActivityAsUser(mContext, 0, root.intent, @@ -2031,7 +2033,7 @@ public final class ActivityManagerService extends ActivityManagerNative ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); - mSystemThread.installSystemApplicationInfo(info); + mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader()); synchronized (this) { ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0); @@ -3434,6 +3436,70 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public final int startActivityAsCaller(IApplicationThread caller, String callingPackage, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, Bundle options) { + + // This is very dangerous -- it allows you to perform a start activity (including + // permission grants) as any app that may launch one of your own activities. So + // we will only allow this to be done from activities that are part of the core framework, + // and then only when they are running as the system. + final ActivityRecord sourceRecord; + final int targetUid; + final String targetPackage; + synchronized (this) { + if (resultTo == null) { + throw new SecurityException("Must be called from an activity"); + } + sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo); + if (sourceRecord == null) { + throw new SecurityException("Called with bad activity token: " + resultTo); + } + if (!sourceRecord.info.packageName.equals("android")) { + throw new SecurityException( + "Must be called from an activity that is declared in the android package"); + } + if (sourceRecord.app == null) { + throw new SecurityException("Called without a process attached to activity"); + } + if (UserHandle.getAppId(sourceRecord.app.uid) != Process.SYSTEM_UID) { + // This is still okay, as long as this activity is running under the + // uid of the original calling activity. + if (sourceRecord.app.uid != sourceRecord.launchedFromUid) { + throw new SecurityException( + "Calling activity in uid " + sourceRecord.app.uid + + " must be system uid or original calling uid " + + sourceRecord.launchedFromUid); + } + } + targetUid = sourceRecord.launchedFromUid; + targetPackage = sourceRecord.launchedFromPackage; + } + + // TODO: Switch to user app stacks here. + try { + int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent, + resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, + null, null, null, null, options, UserHandle.getUserId(targetUid), null); + return ret; + } catch (SecurityException e) { + // XXX need to figure out how to propagate to original app. + // A SecurityException here is generally actually a fault of the original + // calling activity (such as a fairly granting permissions), so propagate it + // back to them. + /* + StringBuilder msg = new StringBuilder(); + msg.append("While launching"); + msg.append(intent.toString()); + msg.append(": "); + msg.append(e.getMessage()); + */ + throw e; + } + } + + @Override public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, @@ -11402,6 +11468,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" [-a] [-c] [-h] [cmd] ..."); pw.println(" cmd may be one of:"); pw.println(" a[ctivities]: activity stack state"); + pw.println(" r[recents]: recent activities state"); pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state"); pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state"); pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); @@ -11435,6 +11502,10 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient, null); } + } else if ("recents".equals(cmd) || "r".equals(cmd)) { + synchronized (this) { + dumpRecentsLocked(fd, pw, args, opti, true, null); + } } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { String[] newArgs; String name; @@ -11578,6 +11649,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } + dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage); + pw.println(); + if (dumpAll) { + pw.println("-------------------------------------------------------------------------------"); + } dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); pw.println(); if (dumpAll) { @@ -11612,6 +11688,17 @@ public final class ActivityManagerService extends ActivityManagerNative mStackSupervisor.dump(pw, " "); } + if (!printedAnything) { + pw.println(" (nothing)"); + } + } + + void dumpRecentsLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage) { + pw.println("ACTIVITY MANAGER RECENT ACTIVITIES (dumpsys activity recents)"); + + boolean printedAnything = false; + if (mRecentTasks.size() > 0) { boolean printedHeader = false; @@ -11625,9 +11712,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (!printedHeader) { - if (needSep) { - pw.println(); - } pw.println(" Recent tasks:"); printedHeader = true; printedAnything = true; diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index e528d57..abacb2d 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -575,6 +575,10 @@ final class ActivityRecord { void makeFinishing() { if (!finishing) { + if (this == task.stack.getVisibleBehindActivity()) { + // A finishing activity should not remain as visible in the background + mStackSupervisor.requestVisibleBehindLocked(this, false); + } finishing = true; if (stopped) { clearOptionsLocked(); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 1107139..8aec392 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2659,13 +2659,20 @@ public final class ActivityStackSupervisor implements DisplayListener { } // A non-top activity is reporting a visibility change. - if (top.fullscreen || top.state != ActivityState.RESUMED || top.app == null || - top.app.thread == null) { + if ((visible && (top.fullscreen || top.state != ActivityState.RESUMED)) || + top.app == null || top.app.thread == null) { // Can't carry out this request. if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG, "requestVisibleBehind: returning top.fullscreen=" - + top.fullscreen+ " top.state=" + top.state + " top.app=" + top.app + + + top.fullscreen + " top.state=" + top.state + " top.app=" + top.app + " top.app.thread=" + top.app.thread); return false; + } else if (!visible && stack.getVisibleBehindActivity() != r) { + // Only the activity set as currently visible behind should actively reset its + // visible behind state. + if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG, "requestVisibleBehind: returning visible=" + + visible + " stack.getVisibleBehindActivity()=" + + stack.getVisibleBehindActivity() + " r=" + r); + return false; } stack.setVisibleBehindActivity(visible ? r : null); @@ -3182,7 +3189,11 @@ public final class ActivityStackSupervisor implements DisplayListener { void endLockTaskModeIfTaskEnding(TaskRecord task) { if (mLockTaskModeTask != null && mLockTaskModeTask == task) { + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.arg1 = mLockTaskModeTask.userId; + lockTaskMsg.what = LOCK_TASK_END_MSG; mLockTaskModeTask = null; + mHandler.sendMessage(lockTaskMsg); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index d8da700..786196f 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -39,6 +39,7 @@ import android.telephony.TelephonyManager; import android.util.Slog; import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.PowerProfile; import com.android.server.LocalServices; @@ -868,6 +869,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (noOutput) { return; } + if (BatteryStatsHelper.checkWifiOnly(mContext)) { + flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; + } if (useCheckinFormat) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); if (isRealCheckin) { diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 4c887dd..0dc163b 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -41,12 +41,17 @@ final class CoreSettingsObserver extends ContentObserver { String, Class<?>>(); private static final Map<String, Class<?>> sSystemSettingToTypeMap = new HashMap< String, Class<?>>(); + private static final Map<String, Class<?>> sGlobalSettingToTypeMap = new HashMap< + String, Class<?>>(); static { sSecureSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class); // add other secure settings here... sSystemSettingToTypeMap.put(Settings.System.TIME_12_24, String.class); // add other system settings here... + + sGlobalSettingToTypeMap.put(Settings.Global.DEBUG_VIEW_ATTRIBUTES, int.class); + // add other global settings here... } private final Bundle mCoreSettings = new Bundle(); @@ -74,6 +79,7 @@ final class CoreSettingsObserver extends ContentObserver { private void sendCoreSettings() { populateSettings(mCoreSettings, sSecureSettingToTypeMap); populateSettings(mCoreSettings, sSystemSettingToTypeMap); + populateSettings(mCoreSettings, sGlobalSettingToTypeMap); mActivityManagerService.onCoreSettingsChange(mCoreSettings); } @@ -89,6 +95,12 @@ final class CoreSettingsObserver extends ContentObserver { mActivityManagerService.mContext.getContentResolver().registerContentObserver( uri, false, this); } + + for (String setting : sGlobalSettingToTypeMap.keySet()) { + Uri uri = Settings.Global.getUriFor(setting); + mActivityManagerService.mContext.getContentResolver().registerContentObserver( + uri, false, this); + } } private void populateSettings(Bundle snapshot, Map<String, Class<?>> map) { @@ -101,32 +113,40 @@ final class CoreSettingsObserver extends ContentObserver { final String value; if (map == sSecureSettingToTypeMap) { value = Settings.Secure.getString(context.getContentResolver(), setting); - } else { + } else if (map == sSystemSettingToTypeMap) { value = Settings.System.getString(context.getContentResolver(), setting); + } else { + value = Settings.Global.getString(context.getContentResolver(), setting); } snapshot.putString(setting, value); } else if (type == int.class) { final int value; if (map == sSecureSettingToTypeMap) { value = Settings.Secure.getInt(context.getContentResolver(), setting); - } else { + } else if (map == sSystemSettingToTypeMap) { value = Settings.System.getInt(context.getContentResolver(), setting); + } else { + value = Settings.Global.getInt(context.getContentResolver(), setting); } snapshot.putInt(setting, value); } else if (type == float.class) { final float value; if (map == sSecureSettingToTypeMap) { value = Settings.Secure.getFloat(context.getContentResolver(), setting); - } else { + } else if (map == sSystemSettingToTypeMap) { value = Settings.System.getFloat(context.getContentResolver(), setting); + } else { + value = Settings.Global.getFloat(context.getContentResolver(), setting); } snapshot.putFloat(setting, value); } else if (type == long.class) { final long value; if (map == sSecureSettingToTypeMap) { value = Settings.Secure.getLong(context.getContentResolver(), setting); - } else { + } else if (map == sSystemSettingToTypeMap) { value = Settings.System.getLong(context.getContentResolver(), setting); + } else { + value = Settings.Global.getLong(context.getContentResolver(), setting); } snapshot.putLong(setting, value); } diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java index 6f9b23d..cf65243 100644 --- a/services/core/java/com/android/server/am/LockTaskNotify.java +++ b/services/core/java/com/android/server/am/LockTaskNotify.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.content.Context; import android.os.Handler; import android.os.Message; +import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.R; @@ -32,9 +33,12 @@ public class LockTaskNotify { private final Context mContext; private final H mHandler; + private AccessibilityManager mAccessibilityManager; public LockTaskNotify(Context context) { mContext = context; + mAccessibilityManager = (AccessibilityManager) + mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mHandler = new H(); } @@ -45,6 +49,9 @@ public class LockTaskNotify { public void handleShowToast(boolean isLocked) { String text = mContext.getString(isLocked ? R.string.lock_to_app_toast_locked : R.string.lock_to_app_toast); + if (!isLocked && mAccessibilityManager.isEnabled()) { + text = mContext.getString(R.string.lock_to_app_toast_accessible); + } Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); } diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java index 0847b52..12dcf7e 100644 --- a/services/core/java/com/android/server/am/LockToAppRequestDialog.java +++ b/services/core/java/com/android/server/am/LockToAppRequestDialog.java @@ -13,6 +13,7 @@ import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Slog; import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; import android.widget.CheckBox; import com.android.internal.R; @@ -33,8 +34,12 @@ public class LockToAppRequestDialog implements OnClickListener { private ILockSettings mLockSettingsService; + private AccessibilityManager mAccessibilityService; + public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) { mContext = context; + mAccessibilityService = (AccessibilityManager) + mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); mService = activityManagerService; } @@ -78,7 +83,9 @@ public class LockToAppRequestDialog implements OnClickListener { final int unlockStringId = getLockString(task.userId); final Resources r = Resources.getSystem(); - final String description= r.getString(R.string.lock_to_app_description); + final String description= r.getString(mAccessibilityService.isEnabled() + ? R.string.lock_to_app_description_accessible + : R.string.lock_to_app_description); AlertDialog.Builder builder = new AlertDialog.Builder(mContext) .setTitle(r.getString(R.string.lock_to_app_title)) .setMessage(description) diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index f79c026..0a66a5c 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -450,6 +450,9 @@ final class ServiceRecord extends Binder { appInfo.packageName, null)); PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0, runningIntent, PendingIntent.FLAG_UPDATE_CURRENT); + localForegroundNoti.color = ams.mContext.getResources().getColor( + com.android.internal + .R.color.system_notification_accent_color); localForegroundNoti.setLatestEventInfo(ctx, ams.mContext.getString( com.android.internal.R.string diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 4eb2ef1..5a97aee 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -40,7 +40,7 @@ import java.util.ArrayList; */ public class NetworkAgentInfo { public NetworkInfo networkInfo; - public final Network network; + public Network network; public LinkProperties linkProperties; public NetworkCapabilities networkCapabilities; public int currentScore; @@ -55,12 +55,12 @@ public class NetworkAgentInfo { public final Messenger messenger; public final AsyncChannel asyncChannel; - public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, int netId, NetworkInfo info, + public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc) { this.messenger = messenger; asyncChannel = ac; - network = new Network(netId); + network = null; networkInfo = info; linkProperties = lp; networkCapabilities = nc; @@ -87,6 +87,7 @@ public class NetworkAgentInfo { public String name() { return "NetworkAgentInfo [" + networkInfo.getTypeName() + " (" + - networkInfo.getSubtypeName() + ") - " + network.toString() + "]"; + networkInfo.getSubtypeName() + ") - " + + (network == null ? "null" : network.toString()) + "]"; } } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 5057994..b09298c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -272,23 +272,22 @@ public class NetworkMonitor extends StateMachine { case CMD_NETWORK_LINGER: if (DBG) log("Lingering"); transitionTo(mLingeringState); - break; + return HANDLED; case CMD_NETWORK_CONNECTED: if (DBG) log("Connected"); transitionTo(mEvaluatingState); - break; + return HANDLED; case CMD_NETWORK_DISCONNECTED: if (DBG) log("Disconnected - quitting"); quit(); - break; + return HANDLED; case CMD_FORCE_REEVALUATION: if (DBG) log("Forcing reevaluation"); transitionTo(mEvaluatingState); - break; + return HANDLED; default: - break; + return HANDLED; } - return HANDLED; } } @@ -314,11 +313,10 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_NETWORK_CONNECTED: transitionTo(mValidatedState); - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } } @@ -347,23 +345,25 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) - break; + return HANDLED; if (mNetworkAgentInfo.isVPN()) { transitionTo(mValidatedState); + return HANDLED; } // If network provides no internet connectivity adjust evaluation. if (!mNetworkAgentInfo.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET)) { // TODO: Try to verify something works. Do all gateways respond to pings? transitionTo(mValidatedState); + return HANDLED; } // Kick off a thread to perform internet connectivity evaluation. Thread thread = new EvaluateInternetConnectivity(mReevaluateToken); thread.run(); - break; + return HANDLED; case EVENT_REEVALUATION_COMPLETE: if (message.arg1 != mReevaluateToken) - break; + return HANDLED; int httpResponseCode = message.arg2; if (httpResponseCode == 204) { transitionTo(mValidatedState); @@ -375,11 +375,10 @@ public class NetworkMonitor extends StateMachine { Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); } - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } } @@ -432,17 +431,16 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: transitionTo(mValidatedState); - break; + return HANDLED; case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: transitionTo(mOfflineState); - break; + return HANDLED; case EVENT_NO_APP_RESPONSE: transitionTo(mUserPromptedState); - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } public void exit() { mReceiver.cancel(); @@ -488,13 +486,12 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_USER_WANTS_SIGN_IN: if (message.arg1 != mUserPromptedToken) - break; + return HANDLED; transitionTo(mInteractiveAppsPromptedState); - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } @Override @@ -520,17 +517,16 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case EVENT_APP_BYPASSED_CAPTIVE_PORTAL: transitionTo(mValidatedState); - break; + return HANDLED; case EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE: transitionTo(mOfflineState); - break; + return HANDLED; case EVENT_NO_APP_RESPONSE: transitionTo(mCaptivePortalState); - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } public void exit() { mReceiver.cancel(); @@ -581,18 +577,17 @@ public class NetworkMonitor extends StateMachine { switch (message.what) { case CMD_CAPTIVE_PORTAL_LOGGED_IN: if (message.arg1 != mCaptivePortalLoggedInToken) - break; + return HANDLED; if (message.arg2 == 0) { // TODO: Should teardown network. transitionTo(mOfflineState); } else { transitionTo(mValidatedState); } - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } @Override @@ -616,17 +611,16 @@ public class NetworkMonitor extends StateMachine { case CMD_NETWORK_CONNECTED: // Go straight to active as we've already evaluated. transitionTo(mValidatedState); - break; + return HANDLED; case CMD_LINGER_EXPIRED: if (message.arg1 != mLingerToken) - break; + return HANDLED; mConnectivityServiceHandler.sendMessage( obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo)); - break; + return HANDLED; default: return NOT_HANDLED; } - return HANDLED; } } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index fa8626f..1fd114c 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -471,6 +471,8 @@ public class Tethering extends BaseNetworkObserver { mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT; mTetheredNotification.tickerText = title; mTetheredNotification.visibility = Notification.VISIBILITY_PUBLIC; + mTetheredNotification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); mTetheredNotification.setLatestEventInfo(mContext, title, message, pi); mTetheredNotification.category = Notification.CATEGORY_STATUS; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index eddf414..69caab9 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,9 +21,7 @@ import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import android.app.AppGlobals; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -35,10 +33,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.net.BaseNetworkStateTracker; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; @@ -51,7 +45,6 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; -import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.UidRange; import android.os.Binder; @@ -70,15 +63,15 @@ import android.os.UserManager; import android.security.Credentials; import android.security.KeyStore; import android.util.Log; -import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.server.net.BaseNetworkObserver; +import libcore.io.IoUtils; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -92,8 +85,6 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import libcore.io.IoUtils; - /** * @hide */ @@ -114,8 +105,6 @@ public class Vpn { private boolean mAllowIPv6; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; - private PendingIntent mStatusIntent; - private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; private final INetworkManagementService mNetd; @@ -180,14 +169,6 @@ public class Vpn { } /** - * Set if this object is responsible for showing its own notifications. When - * {@code false}, notifications are handled externally by someone else. - */ - public void setEnableNotifications(boolean enableNotif) { - mEnableNotif = enableNotif; - } - - /** * Set if this object is responsible for watching for {@link NetworkInfo} * teardown. When {@code false}, teardown is handled externally by someone * else. @@ -228,6 +209,20 @@ public class Vpn { public synchronized boolean prepare(String oldPackage, String newPackage) { // Return false if the package does not match. if (oldPackage != null && !oldPackage.equals(mPackage)) { + // The package doesn't match. If this VPN was not previously authorized, return false + // to force user authorization. Otherwise, revoke the VPN anyway. + if (!oldPackage.equals(VpnConfig.LEGACY_VPN) && isVpnUserPreConsented(oldPackage)) { + long token = Binder.clearCallingIdentity(); + try { + // This looks bizarre, but it is what ConfirmDialog in VpnDialogs is doing when + // the user clicks through to allow the VPN to consent. So we are emulating the + // action of the dialog without actually showing it. + prepare(null, oldPackage); + } finally { + Binder.restoreCallingIdentity(token); + } + return true; + } return false; } @@ -240,11 +235,8 @@ public class Vpn { // Check if the caller is authorized. enforceControlPermission(); - // Reset the interface and hide the notification. + // Reset the interface. if (mInterface != null) { - for (UidRange uidRange : mVpnUsers) { - hideNotification(uidRange.getStartUser()); - } agentDisconnect(); jniReset(mInterface); mInterface = null; @@ -287,12 +279,46 @@ public class Vpn { Binder.restoreCallingIdentity(token); } mConfig = null; + updateState(DetailedState.IDLE, "prepare"); return true; } + /** + * Set whether the current package has the ability to launch VPNs without user intervention. + */ + public void setPackageAuthorization(boolean authorized) { + // Check if the caller is authorized. + enforceControlPermission(); + + if (mPackage == null || VpnConfig.LEGACY_VPN.equals(mPackage)) { + return; + } + + long token = Binder.clearCallingIdentity(); + try { + AppOpsManager appOps = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, mOwnerUID, mPackage, + authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); + } catch (Exception e) { + Log.wtf(TAG, "Failed to set app ops for package " + mPackage, e); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private boolean isVpnUserPreConsented(String packageName) { + AppOpsManager appOps = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + + // Verify that the caller matches the given package and has permission to activate VPNs. + return appOps.noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Binder.getCallingUid(), + packageName) == AppOpsManager.MODE_ALLOWED; + } + private int getAppUid(String app) { - if (app == VpnConfig.LEGACY_VPN) { + if (VpnConfig.LEGACY_VPN.equals(app)) { return Process.myUid(); } PackageManager pm = mContext.getPackageManager(); @@ -355,9 +381,10 @@ public class Vpn { try { mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE, mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) { + @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. - }; + } }; } finally { Binder.restoreCallingIdentity(token); @@ -540,39 +567,6 @@ public class Vpn { // add the user mVpnUsers.add(UidRange.createForUser(user)); - - // show the notification - if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { - // Load everything for the user's notification - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = null; - final long token = Binder.clearCallingIdentity(); - try { - app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); - } catch (RemoteException e) { - throw new IllegalStateException("Invalid application"); - } finally { - Binder.restoreCallingIdentity(token); - } - String label = app.loadLabel(pm).toString(); - // Load the icon and convert it into a bitmap. - Drawable icon = app.loadIcon(pm); - Bitmap bitmap = null; - if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { - int width = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - int height = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_height); - icon.setBounds(0, 0, width, height); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - icon.draw(c); - c.setBitmap(null); - } - showNotification(label, bitmap, user); - } else { - showNotification(null, null, user); - } } private void removeVpnUserLocked(int user) { @@ -584,7 +578,6 @@ public class Vpn { mNetworkAgent.removeUidRanges(new UidRange[] { uidRange }); } mVpnUsers.remove(uidRange); - hideNotification(user); } private void onUserAdded(int userId) { @@ -652,9 +645,6 @@ public class Vpn { public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - for (UidRange uidRange : mVpnUsers) { - hideNotification(uidRange.getStartUser()); - } mVpnUsers = null; mInterface = null; if (mConnection != null) { @@ -712,54 +702,6 @@ public class Vpn { } } - private void showNotification(String label, Bitmap icon, int user) { - if (!mEnableNotif) return; - final long token = Binder.clearCallingIdentity(); - try { - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - String title = (label == null) ? mContext.getString(R.string.vpn_title) : - mContext.getString(R.string.vpn_title_long, label); - String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, mConfig.session); - - Notification notification = new Notification.Builder(mContext) - .setSmallIcon(R.drawable.vpn_connected) - .setLargeIcon(icon) - .setContentTitle(title) - .setContentText(text) - .setContentIntent(mStatusIntent) - .setDefaults(0) - .setOngoing(true) - .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); - } - } finally { - Binder.restoreCallingIdentity(token); - } - } - - private void hideNotification(int user) { - if (!mEnableNotif) return; - mStatusIntent = null; - - NotificationManager nm = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (nm != null) { - final long token = Binder.clearCallingIdentity(); - try { - nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - public synchronized boolean addAddress(String address, int prefixLength) { if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { return false; @@ -969,9 +911,6 @@ public class Vpn { final LegacyVpnInfo info = new LegacyVpnInfo(); info.key = mConfig.user; info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo); - if (mNetworkInfo.isConnected()) { - info.intent = mStatusIntent; - } return info; } @@ -1261,6 +1200,7 @@ public class Vpn { } } catch (Exception e) { Log.i(TAG, "Aborting", e); + updateState(DetailedState.FAILED, e.getMessage()); exit(); } finally { // Kill the daemons if they fail to stop. diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 08d6fc9..949019e 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -3064,6 +3064,8 @@ public class SyncManager { new Notification(R.drawable.stat_notify_sync_error, mContext.getString(R.string.contentServiceSync), System.currentTimeMillis()); + notification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, mContext.getString(R.string.contentServiceSyncNotificationTitle), String.format(tooManyDeletesDescFormat.toString(), authorityName), diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 9a67321..2a1ceaa 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -37,7 +37,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; -import android.text.format.DateUtils; +import android.os.Trace; import android.util.MathUtils; import android.util.Slog; import android.util.Spline; @@ -74,6 +74,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static boolean DEBUG = false; private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; + private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked"; + // If true, uses the electron beam on animation. // We might want to turn this off if we cannot get a guarantee that the screen // actually turns on and starts showing new content after the call to set the @@ -85,7 +87,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10; private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250; - private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 600; + private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400; private static final int MSG_UPDATE_POWER_STATE = 1; private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; @@ -714,11 +716,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void blockScreenOn() { if (!mScreenOnWasBlocked) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); mScreenOnWasBlocked = true; mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime(); - if (DEBUG) { - Slog.d(TAG, "Blocked screen on."); - } + Slog.i(TAG, "Blocking screen on until initial contents have been drawn."); } } @@ -726,9 +727,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mScreenOnWasBlocked) { mScreenOnWasBlocked = false; long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime; - if (delay > 1000 || DEBUG) { - Slog.d(TAG, "Unblocked screen on after " + delay + " ms"); - } + Slog.i(TAG, "Unblocked screen on after " + delay + " ms"); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); } } diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 6522b89..a7651e4 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -23,6 +23,7 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; +import android.os.Trace; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Slog; @@ -405,19 +406,38 @@ final class DisplayPowerState { } boolean suspending = Display.isSuspendedState(state); if (stateChanged && !suspending) { - mBlanker.requestDisplayState(state); + requestDisplayState(state); } if (backlightChanged) { - mBacklight.setBrightness(backlight); + setBrightness(backlight); } if (stateChanged && suspending) { - mBlanker.requestDisplayState(state); + requestDisplayState(state); } } // Let the outer class know that all changes have been applied. postScreenUpdateThreadSafe(); } + + private void requestDisplayState(int state) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState(" + + Display.stateToString(state) + ")"); + try { + mBlanker.requestDisplayState(state); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } + + private void setBrightness(int backlight) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setBrightness(" + backlight + ")"); + try { + mBacklight.setBrightness(backlight); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } }; } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 4fd006d..9c91ab5 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -21,7 +21,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.SystemProperties; -import android.util.Pair; +import android.os.Trace; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -224,8 +224,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public void requestDisplayStateLocked(int state) { if (mState != state) { - SurfaceControl.setDisplayPowerMode(getDisplayTokenLocked(), - getPowerModeForState(state)); + final int mode = getPowerModeForState(state); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState(" + + Display.stateToString(state) + ", id=" + mBuiltInDisplayId + ")"); + try { + SurfaceControl.setDisplayPowerMode(getDisplayTokenLocked(), mode); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } mState = state; updateDeviceInfoLocked(); } diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index a05bf2c..9d008b9 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -502,6 +502,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { .addAction(android.R.drawable.ic_menu_close_clear_cancel, r.getString(R.string.wifi_display_notification_disconnect), mDisconnectPendingIntent) + .setColor(r.getColor( + com.android.internal.R.color.system_notification_accent_color)) .build(); } else { notification = new Notification.Builder(context) @@ -516,6 +518,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { .addAction(android.R.drawable.ic_menu_close_clear_cancel, r.getString(R.string.wifi_display_notification_disconnect), mDisconnectPendingIntent) + .setColor(r.getColor( + com.android.internal.R.color.system_notification_accent_color)) .build(); } mNotificationManager.notifyAsUser(null, diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 334f0ac..768ccf2 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.IBinder.DeathRecipient; +import android.os.Trace; import android.os.UserHandle; import android.service.dreams.DreamService; import android.service.dreams.IDreamService; @@ -111,41 +112,46 @@ final class DreamController { boolean isTest, boolean canDoze, int userId) { stopDream(true /*immediate*/); - // Close the notification shade. Don't need to send to all, but better to be explicit. - mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); + try { + // Close the notification shade. Don't need to send to all, but better to be explicit. + mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); - Slog.i(TAG, "Starting dream: name=" + name - + ", isTest=" + isTest + ", canDoze=" + canDoze - + ", userId=" + userId); + Slog.i(TAG, "Starting dream: name=" + name + + ", isTest=" + isTest + ", canDoze=" + canDoze + + ", userId=" + userId); - mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId); + mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId); - try { - mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM); - } catch (RemoteException ex) { - Slog.e(TAG, "Unable to add window token for dream.", ex); - stopDream(true /*immediate*/); - return; - } + try { + mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM); + } catch (RemoteException ex) { + Slog.e(TAG, "Unable to add window token for dream.", ex); + stopDream(true /*immediate*/); + return; + } - Intent intent = new Intent(DreamService.SERVICE_INTERFACE); - intent.setComponent(name); - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - try { - if (!mContext.bindServiceAsUser(intent, mCurrentDream, - Context.BIND_AUTO_CREATE, new UserHandle(userId))) { - Slog.e(TAG, "Unable to bind dream service: " + intent); + Intent intent = new Intent(DreamService.SERVICE_INTERFACE); + intent.setComponent(name); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + try { + if (!mContext.bindServiceAsUser(intent, mCurrentDream, + Context.BIND_AUTO_CREATE, new UserHandle(userId))) { + Slog.e(TAG, "Unable to bind dream service: " + intent); + stopDream(true /*immediate*/); + return; + } + } catch (SecurityException ex) { + Slog.e(TAG, "Unable to bind dream service: " + intent, ex); stopDream(true /*immediate*/); return; } - } catch (SecurityException ex) { - Slog.e(TAG, "Unable to bind dream service: " + intent, ex); - stopDream(true /*immediate*/); - return; - } - mCurrentDream.mBound = true; - mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); + mCurrentDream.mBound = true; + mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } public void stopDream(boolean immediate) { @@ -153,71 +159,76 @@ final class DreamController { return; } - if (!immediate) { - if (mCurrentDream.mWakingGently) { - return; // already waking gently - } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); + try { + if (!immediate) { + if (mCurrentDream.mWakingGently) { + return; // already waking gently + } - if (mCurrentDream.mService != null) { - // Give the dream a moment to wake up and finish itself gently. - mCurrentDream.mWakingGently = true; - try { - mCurrentDream.mService.wakeUp(); - mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); - return; - } catch (RemoteException ex) { - // oh well, we tried, finish immediately instead + if (mCurrentDream.mService != null) { + // Give the dream a moment to wake up and finish itself gently. + mCurrentDream.mWakingGently = true; + try { + mCurrentDream.mService.wakeUp(); + mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); + return; + } catch (RemoteException ex) { + // oh well, we tried, finish immediately instead + } } } - } - final DreamRecord oldDream = mCurrentDream; - mCurrentDream = null; - Slog.i(TAG, "Stopping dream: name=" + oldDream.mName - + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze - + ", userId=" + oldDream.mUserId); + final DreamRecord oldDream = mCurrentDream; + mCurrentDream = null; + Slog.i(TAG, "Stopping dream: name=" + oldDream.mName + + ", isTest=" + oldDream.mIsTest + ", canDoze=" + oldDream.mCanDoze + + ", userId=" + oldDream.mUserId); - mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); - mHandler.removeCallbacks(mStopStubbornDreamRunnable); + mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); + mHandler.removeCallbacks(mStopStubbornDreamRunnable); - if (oldDream.mSentStartBroadcast) { - mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); - } + if (oldDream.mSentStartBroadcast) { + mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); + } - if (oldDream.mService != null) { - // Tell the dream that it's being stopped so that - // it can shut down nicely before we yank its window token out from - // under it. - try { - oldDream.mService.detach(); - } catch (RemoteException ex) { - // we don't care; this thing is on the way out + if (oldDream.mService != null) { + // Tell the dream that it's being stopped so that + // it can shut down nicely before we yank its window token out from + // under it. + try { + oldDream.mService.detach(); + } catch (RemoteException ex) { + // we don't care; this thing is on the way out + } + + try { + oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); + } catch (NoSuchElementException ex) { + // don't care + } + oldDream.mService = null; } - try { - oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); - } catch (NoSuchElementException ex) { - // don't care + if (oldDream.mBound) { + mContext.unbindService(oldDream); } - oldDream.mService = null; - } - if (oldDream.mBound) { - mContext.unbindService(oldDream); - } + try { + mIWindowManager.removeWindowToken(oldDream.mToken); + } catch (RemoteException ex) { + Slog.w(TAG, "Error removing window token for dream.", ex); + } - try { - mIWindowManager.removeWindowToken(oldDream.mToken); - } catch (RemoteException ex) { - Slog.w(TAG, "Error removing window token for dream.", ex); + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDreamStopped(oldDream.mToken); + } + }); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); } - - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDreamStopped(oldDream.mToken); - } - }); } private void attach(IDreamService service) { diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 2407253..4b812cf 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -173,6 +173,10 @@ final class Constants { static final int SYSTEM_AUDIO_STATUS_OFF = 0; static final int SYSTEM_AUDIO_STATUS_ON = 1; + // [Menu State] + static final int MENU_STATE_ACTIVATED = 0; + static final int MENU_STATE_DEACTIVATED = 1; + // Bit mask used to get the routing path of the top level device. // When &'d with the path 1.2.2.0 (0x1220), for instance, gives 1.0.0.0. static final int ROUTING_PATH_TOP_MASK = 0xF000; @@ -276,5 +280,10 @@ final class Constants { // values which denotes the device type in HDMI Spec 1.4. static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // MHL RCPE messages + static final int MHL_RCPE_NO_ERROR = 0x00; + static final int MHL_RCPE_INEFFECTIVE_KEYCODE = 0x01; + static final int MHL_RCPE_RESPONDER_BUSY = 0x02; + private Constants() { /* cannot be instantiated */ } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java index c0c8424..aed5dcc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java @@ -21,7 +21,7 @@ import android.view.KeyEvent; /** * Helper class to translate android keycode to hdmi cec keycode and vice versa. */ -public class HdmiCecKeycode { +final class HdmiCecKeycode { public static final int UNSUPPORTED_KEYCODE = -1; public static final int NO_PARAM = -1; @@ -155,7 +155,7 @@ public class HdmiCecKeycode { } /** - * A mapping between andorid and cec keycode. + * A mapping between Android and CEC keycode. * * <p>Normal implementation of this looks like * <pre> @@ -174,43 +174,28 @@ public class HdmiCecKeycode { private static class KeycodeEntry { private final int mAndroidKeycode; private final int mCecKeycode; - private final int mParam; private final boolean mIsRepeatable; - private KeycodeEntry(int androidKeycode, int cecKeycode, int param, boolean isRepeatable) { + private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable) { mAndroidKeycode = androidKeycode; mCecKeycode = cecKeycode; - mParam = param; mIsRepeatable = isRepeatable; } private KeycodeEntry(int androidKeycode, int cecKeycode) { - this(androidKeycode, cecKeycode, NO_PARAM, true); - } - - private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable) { - this(androidKeycode, cecKeycode, NO_PARAM, isRepeatable); + this(androidKeycode, cecKeycode, true); } - private byte[] toCecKeycodeIfMatched(int androidKeycode) { + private int toCecKeycodeIfMatched(int androidKeycode) { if (mAndroidKeycode == androidKeycode) { - if (mParam == NO_PARAM) { - return new byte[] { - (byte) (mCecKeycode & 0xFF) - }; - } else { - return new byte[] { - (byte) (mCecKeycode & 0xFF), - (byte) (mParam & 0xFF) - }; - } + return mCecKeycode; } else { - return null; + return UNSUPPORTED_KEYCODE; } } - private int toAndroidKeycodeIfMatched(int cecKeycode, int param) { - if (cecKeycode == mCecKeycode && mParam == param) { + private int toAndroidKeycodeIfMatched(int cecKeycode) { + if (cecKeycode == mCecKeycode) { return mAndroidKeycode; } else { return UNSUPPORTED_KEYCODE; @@ -365,29 +350,28 @@ public class HdmiCecKeycode { * Translate Android keycode to Hdmi Cec keycode. * * @param keycode Android keycode. For details, refer {@link KeyEvent} - * @return array of byte which contains cec keycode and param if it has; - * return null if failed to find matched cec keycode + * @return single byte CEC keycode if matched. */ - static byte[] androidKeyToCecKey(int keycode) { + static int androidKeyToCecKey(int keycode) { for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) { - byte[] cecKeycode = KEYCODE_ENTRIES[i].toCecKeycodeIfMatched(keycode); - if (cecKeycode != null) { + int cecKeycode = KEYCODE_ENTRIES[i].toCecKeycodeIfMatched(keycode); + if (cecKeycode != UNSUPPORTED_KEYCODE) { return cecKeycode; } } - return null; + return UNSUPPORTED_KEYCODE; } /** * Translate Hdmi CEC keycode to Android keycode. * - * @param keycode Cec keycode. If has no param, put {@link #NO_PARAM} + * @param keycode CEC keycode * @return cec keycode corresponding to the given android keycode. * If finds no matched keycode, return {@link #UNSUPPORTED_KEYCODE} */ - static int cecKeyToAndroidKey(int keycode, int param) { + static int cecKeyToAndroidKey(int keycode) { for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) { - int androidKey = KEYCODE_ENTRIES[i].toAndroidKeycodeIfMatched(keycode, param); + int androidKey = KEYCODE_ENTRIES[i].toAndroidKeycodeIfMatched(keycode); if (androidKey != UNSUPPORTED_KEYCODE) { return androidKey; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index b894fd7..b43ad1b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -17,10 +17,15 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.Slog; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import com.android.internal.annotations.GuardedBy; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; @@ -39,15 +44,21 @@ abstract class HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevice"; private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; + private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2; // Timeout in millisecond for device clean up (5s). // Normal actions timeout is 2s but some of them would have several sequence of timeout. private static final int DEVICE_CLEANUP_TIMEOUT = 5000; + // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. + // When it expires, we can assume <User Control Release> is received. + private static final int FOLLOWER_SAFETY_TIMEOUT = 550; protected final HdmiControlService mService; protected final int mDeviceType; protected int mAddress; protected int mPreferredAddress; protected HdmiDeviceInfo mDeviceInfo; + protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; + protected int mLastKeyRepeatCount = 0; static class ActiveSource { int logicalAddress; @@ -111,6 +122,9 @@ abstract class HdmiCecLocalDevice { case MSG_DISABLE_DEVICE_TIMEOUT: handleDisableDeviceTimeout(); break; + case MSG_USER_CONTROL_RELEASE_TIMEOUT: + handleUserControlReleased(); + break; } } }; @@ -230,10 +244,14 @@ abstract class HdmiCecLocalDevice { return handleImageViewOn(message); case Constants.MESSAGE_USER_CONTROL_PRESSED: return handleUserControlPressed(message); + case Constants.MESSAGE_USER_CONTROL_RELEASED: + return handleUserControlReleased(); case Constants.MESSAGE_SET_STREAM_PATH: return handleSetStreamPath(message); case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: return handleGiveDevicePowerStatus(message); + case Constants.MESSAGE_MENU_REQUEST: + return handleGiveDeviceMenuStatus(message); case Constants.MESSAGE_VENDOR_COMMAND: return handleVendorCommand(message); case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID: @@ -376,6 +394,7 @@ abstract class HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); + mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { mService.standby(); return true; @@ -383,9 +402,55 @@ abstract class HdmiCecLocalDevice { mService.wakeUp(); return true; } + + final long downTime = SystemClock.uptimeMillis(); + final byte[] params = message.getParams(); + // Note that we don't support parameterized keycode now. + // TODO: translate parameterized keycode as well. + final int keycode = HdmiCecKeycode.cecKeyToAndroidKey(params[0]); + int keyRepeatCount = 0; + if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { + if (keycode == mLastKeycode) { + keyRepeatCount = mLastKeyRepeatCount + 1; + } else { + injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0); + } + } + mLastKeycode = keycode; + mLastKeyRepeatCount = keyRepeatCount; + + if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { + injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount); + mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT), + FOLLOWER_SAFETY_TIMEOUT); + return true; + } + return false; + } + + @ServiceThreadOnly + protected boolean handleUserControlReleased() { + assertRunOnServiceThread(); + mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); + mLastKeyRepeatCount = 0; + if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { + final long upTime = SystemClock.uptimeMillis(); + injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); + mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; + return true; + } return false; } + static void injectKeyEvent(long time, int action, int keycode, int repeat) { + KeyEvent keyEvent = KeyEvent.obtain(time, time, action, keycode, + repeat, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, + InputDevice.SOURCE_HDMI, null); + InputManager.getInstance().injectInputEvent(keyEvent, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + keyEvent.recycle(); + } + static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { byte[] params = message.getParams(); return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED @@ -420,6 +485,13 @@ abstract class HdmiCecLocalDevice { return true; } + protected boolean handleGiveDeviceMenuStatus(HdmiCecMessage message) { + // Always report menu active to receive Remote Control. + mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus( + mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED)); + return true; + } + protected boolean handleVendorCommand(HdmiCecMessage message) { mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), message.getParams(), false); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 47b0794..73f5cfd 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -100,7 +100,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); // Map-like container of all cec devices including local ones. - // A logical address of device is used as key of container. + // device id is used as key of container. // This is not thread-safe. For external purpose use mSafeDeviceInfos. private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); @@ -167,12 +167,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { /** * Performs the action 'device select', or 'one touch play' initiated by TV. * - * @param targetAddress logical address of the device to select + * @param id id of HDMI device to select * @param callback callback object to report the result with */ @ServiceThreadOnly - void deviceSelect(int targetAddress, IHdmiControlCallback callback) { + void deviceSelect(int id, IHdmiControlCallback callback) { assertRunOnServiceThread(); + HdmiDeviceInfo targetDevice = mDeviceInfos.get(id); + if (targetDevice == null) { + invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); + return; + } + int targetAddress = targetDevice.getLogicalAddress(); ActiveSource active = getActiveSource(); if (active.isValid() && targetAddress == active.logicalAddress) { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); @@ -187,18 +193,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } if (!mService.isControlEnabled()) { - HdmiDeviceInfo info = getDeviceInfo(targetAddress); - if (info != null) { - setActiveSource(info); - } + setActiveSource(targetDevice); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - HdmiDeviceInfo targetDevice = getDeviceInfo(targetAddress); - if (targetDevice == null) { - invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); - return; - } removeAction(DeviceSelectAction.class); addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); } @@ -231,7 +229,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } setActiveSource(newActive); int logicalAddress = newActive.logicalAddress; - if (getDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { + if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { setPrevPortId(getActivePortId()); } @@ -279,7 +277,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Show OSD port change banner if (notifyInputChange) { ActiveSource activeSource = getActiveSource(); - HdmiDeviceInfo info = getDeviceInfo(activeSource.logicalAddress); + HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress); if (info == null) { info = new HdmiDeviceInfo(Constants.ADDR_INVALID, path, portId, HdmiDeviceInfo.DEVICE_RESERVED, 0, null); @@ -363,7 +361,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - if (getDeviceInfo(logicalAddress) == null) { + if (getCecDeviceInfo(logicalAddress) == null) { handleNewDeviceAtTheTailOfActivePath(physicalAddress); } else { ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); @@ -389,7 +387,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (portId != Constants.INVALID_PORT_ID) { // TODO: Do this only if TV is not showing multiview like PIP/PAP. - HdmiDeviceInfo inactiveSource = getDeviceInfo(message.getSource()); + HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource()); if (inactiveSource == null) { return true; } @@ -420,17 +418,25 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleGetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); - HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( - mAddress, Locale.getDefault().getISO3Language()); // TODO: figure out how to handle failed to get language code. - if (command != null) { - mService.sendCecCommand(command); - } else { + if (!broadcastMenuLanguage(Locale.getDefault().getISO3Language())) { Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); } return true; } + @ServiceThreadOnly + boolean broadcastMenuLanguage(String language) { + assertRunOnServiceThread(); + HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( + mAddress, language); + if (command != null) { + mService.sendCecCommand(command); + return true; + } + return false; + } + @Override @ServiceThreadOnly protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { @@ -577,7 +583,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSetOsdName(HdmiCecMessage message) { int source = message.getSource(); - HdmiDeviceInfo deviceInfo = getDeviceInfo(source); + HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); // If the device is not in device list, ignore it. if (deviceInfo == null) { Slog.e(TAG, "No source device info for <Set Osd Name>." + message); @@ -952,11 +958,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { assertRunOnServiceThread(); - HdmiDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); + HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); if (oldDeviceInfo != null) { - removeDeviceInfo(deviceInfo.getLogicalAddress()); + removeDeviceInfo(deviceInfo.getId()); } - mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); + mDeviceInfos.append(deviceInfo.getId(), deviceInfo); updateSafeDeviceInfoList(); return oldDeviceInfo; } @@ -967,15 +973,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { * * <p>Declared as package-private. accessed by {@link HdmiControlService} only. * - * @param logicalAddress logical address of device to be removed + * @param id id of device to be removed * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} */ @ServiceThreadOnly - private HdmiDeviceInfo removeDeviceInfo(int logicalAddress) { + private HdmiDeviceInfo removeDeviceInfo(int id) { assertRunOnServiceThread(); - HdmiDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); + HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); if (deviceInfo != null) { - mDeviceInfos.remove(logicalAddress); + mDeviceInfos.remove(id); } updateSafeDeviceInfoList(); return deviceInfo; @@ -1008,10 +1014,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { /** * Return external input devices. */ - List<HdmiDeviceInfo> getSafeExternalInputs() { - synchronized (mLock) { - return mSafeExternalInputs; - } + List<HdmiDeviceInfo> getSafeExternalInputsLocked() { + return mSafeExternalInputs; } @ServiceThreadOnly @@ -1035,7 +1039,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); for (int i = 0; i < mDeviceInfos.size(); ++i) { HdmiDeviceInfo info = mDeviceInfos.valueAt(i); - if (isLocalDeviceAddress(i)) { + if (isLocalDeviceAddress(info.getLogicalAddress())) { continue; } if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { @@ -1095,23 +1099,22 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly HdmiDeviceInfo getAvrDeviceInfo() { assertRunOnServiceThread(); - return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); + return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } /** * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. * - * <p>Declared as package-private. accessed by {@link HdmiControlService} only. - * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}. + * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. * - * @param logicalAddress logical address to be retrieved + * @param address logical address of the device to be retrieved * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. * Returns null if no logical address matched */ @ServiceThreadOnly - HdmiDeviceInfo getDeviceInfo(int logicalAddress) { + HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); - return mDeviceInfos.get(logicalAddress); + return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); } boolean hasSystemAudioDevice() { @@ -1119,19 +1122,24 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } HdmiDeviceInfo getSafeAvrDeviceInfo() { - return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); + return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); } /** - * Thread safe version of {@link #getDeviceInfo(int)}. + * Thread safe version of {@link #getCecDeviceInfo(int)}. * * @param logicalAddress logical address to be retrieved * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. * Returns null if no logical address matched */ - HdmiDeviceInfo getSafeDeviceInfo(int logicalAddress) { + HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { synchronized (mLock) { - return mSafeAllDeviceInfos.get(logicalAddress); + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { + return info; + } + } + return null; } } @@ -1160,7 +1168,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly final void removeCecDevice(int address) { assertRunOnServiceThread(); - HdmiDeviceInfo info = removeDeviceInfo(address); + HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); mCecMessageCache.flushMessagesFrom(address); invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); @@ -1240,7 +1248,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly boolean isInDeviceList(int logicalAddress, int physicalAddress) { assertRunOnServiceThread(); - HdmiDeviceInfo device = getDeviceInfo(logicalAddress); + HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); if (device == null) { return false; } @@ -1427,7 +1435,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } private boolean checkRecorder(int recorderAddress) { - HdmiDeviceInfo device = getDeviceInfo(recorderAddress); + HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress); return (device != null) && (HdmiUtils.getTypeFromAddress(recorderAddress) == HdmiDeviceInfo.DEVICE_RECORDER); @@ -1528,7 +1536,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { - HdmiDeviceInfo info = getDeviceInfo(logicalAddress); + HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); if (info == null) { Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); return; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 0855bfa..b53cd45 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -338,6 +338,21 @@ public class HdmiCecMessageBuilder { } /** + * Build <Report Menu Status> command. + * + * @param src source address of command + * @param dest destination address of command + * @param menuStatus menu status of the device + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildReportMenuStatus(int src, int dest, int menuStatus) { + byte[] param = new byte[] { + (byte) (menuStatus) + }; + return buildCommand(src, dest, Constants.MESSAGE_MENU_STATUS, param); + } + + /** * Build <System Audio Mode Request> command. * * @param src source address of command diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 2818ea7..5b99f14 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; +import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; import static com.android.server.hdmi.Constants.DISABLED; import static com.android.server.hdmi.Constants.ENABLED; import static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP; @@ -76,6 +78,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; /** * Provides a service for sending and processing HDMI control messages, @@ -121,7 +124,7 @@ public final class HdmiControlService extends SystemService { void onPollingFinished(List<Integer> ackedAddress); } - private class PowerStateReceiver extends BroadcastReceiver { + private class HdmiControlBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { @@ -135,6 +138,12 @@ public final class HdmiControlService extends SystemService { onWakeUp(); } break; + case Intent.ACTION_CONFIGURATION_CHANGED: + String language = Locale.getDefault().getISO3Language(); + if (!mLanguage.equals(language)) { + onLanguageChanged(language); + } + break; } } } @@ -202,6 +211,9 @@ public final class HdmiControlService extends SystemService { @GuardedBy("mLock") private boolean mMhlInputChangeEnabled; + @GuardedBy("mLock") + private List<HdmiDeviceInfo> mMhlDevices; + // List of listeners registered by callers that want to get notified of // system audio mode changes. private final ArrayList<IHdmiSystemAudioModeChangeListener> @@ -233,12 +245,16 @@ public final class HdmiControlService extends SystemService { private HdmiCecMessageValidator mMessageValidator; - private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver(); + private final HdmiControlBroadcastReceiver + mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver(); @ServiceThreadOnly private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; @ServiceThreadOnly + private String mLanguage = Locale.getDefault().getISO3Language(); + + @ServiceThreadOnly private boolean mStandbyMessageReceived = false; @ServiceThreadOnly @@ -293,6 +309,7 @@ public final class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support MHL-control."); } initPortInfo(); + mMhlDevices = Collections.emptyList(); mMessageValidator = new HdmiCecMessageValidator(this); publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); @@ -301,7 +318,8 @@ public final class HdmiControlService extends SystemService { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); - getContext().registerReceiver(mPowerStateReceiver, filter); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); } } @@ -320,7 +338,6 @@ public final class HdmiControlService extends SystemService { } } - private void registerContentObserver() { ContentResolver resolver = getContext().getContentResolver(); String[] settings = new String[] { @@ -330,7 +347,7 @@ public final class HdmiControlService extends SystemService { Global.MHL_INPUT_SWITCHING_ENABLED, Global.MHL_POWER_CHARGE_ENABLED }; - for (String s: settings) { + for (String s : settings) { resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver, UserHandle.USER_ALL); } @@ -351,7 +368,7 @@ public final class HdmiControlService extends SystemService { break; case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED: tv().setAutoWakeup(enabled); - setOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled)); + setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled)); break; case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED: tv().setAutoDeviceOff(enabled); @@ -490,6 +507,10 @@ public final class HdmiControlService extends SystemService { } } + List<HdmiPortInfo> getPortInfo() { + return mPortInfo; + } + /** * Returns HDMI port information for the given port id. * @@ -568,7 +589,7 @@ public final class HdmiControlService extends SystemService { if (tv == null) { return null; } - return tv.getDeviceInfo(logicalAddress); + return tv.getCecDeviceInfo(logicalAddress); } /** @@ -684,16 +705,16 @@ public final class HdmiControlService extends SystemService { /** * Called when a new hotplug event is issued. * - * @param portNo hdmi port number where hot plug event issued. + * @param portId hdmi port number where hot plug event issued. * @param connected whether to be plugged in or not */ @ServiceThreadOnly - void onHotplug(int portNo, boolean connected) { + void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.onHotplug(portNo, connected); + device.onHotplug(portId, connected); } - announceHotplugEvent(portNo, connected); + announceHotplugEvent(portId, connected); } /** @@ -794,10 +815,15 @@ public final class HdmiControlService extends SystemService { HdmiMhlLocalDevice device = mMhlController.removeLocalDevice(portId); if (device != null) { device.onDeviceRemoved(); + // There is no explicit event for device removal unlike capability register event + // used for device addition . Hence we remove the device on hotplug event. + invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); + updateSafeMhlInput(); } else { Slog.w(TAG, "No device to remove:[portId=" + portId); } } + announceHotplugEvent(portId, connected); } @ServiceThreadOnly @@ -824,18 +850,45 @@ public final class HdmiControlService extends SystemService { } @ServiceThreadOnly - void handleCapabilityRegisterChanged(int portId, int adopterId, int deviceId) { + void handleMhlCapabilityRegisterChanged(int portId, int adopterId, int deviceId) { assertRunOnServiceThread(); HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); - // Hot plug event should be called before capability register change event. + + // Hotplug event should already have been called before capability register change event. if (device != null) { device.setCapabilityRegister(adopterId, deviceId); + invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE); + updateSafeMhlInput(); } else { Slog.w(TAG, "No mhl device exists for capability register change event[portId:" + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); } } + @ServiceThreadOnly + private void updateSafeMhlInput() { + assertRunOnServiceThread(); + List<HdmiDeviceInfo> inputs = Collections.emptyList(); + SparseArray<HdmiMhlLocalDevice> devices = mMhlController.getAllLocalDevices(); + for (int i = 0; i < devices.size(); ++i) { + HdmiMhlLocalDevice device = devices.valueAt(i); + HdmiDeviceInfo info = device.getInfo(); + if (info != null) { + if (inputs.isEmpty()) { + inputs = new ArrayList<>(); + } + inputs.add(device.getInfo()); + } + } + synchronized (mLock) { + mMhlDevices = inputs; + } + } + + private List<HdmiDeviceInfo> getMhlDevicesLocked() { + return mMhlDevices; + } + // Record class that monitors the event of the caller of being killed. Used to clean up // the listener list and record list accordingly. private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { @@ -949,7 +1002,7 @@ public final class HdmiControlService extends SystemService { } @Override - public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { + public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override @@ -964,7 +1017,18 @@ public final class HdmiControlService extends SystemService { invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); return; } - tv.deviceSelect(logicalAddress, callback); + if (mMhlController != null) { + HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId); + if (device != null) { + // Upon selecting MHL device, we send RAP[Content On] to wake up + // the connected mobile device, start routing control to switch ports. + // callback is handled by MHL action. + device.turnOn(callback); + tv.doManualPortSwitching(device.getInfo().getPortId(), null); + return; + } + } + tv.deviceSelect(deviceId, callback); } }); } @@ -996,12 +1060,21 @@ public final class HdmiControlService extends SystemService { runOnServiceThread(new Runnable() { @Override public void run() { - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); - if (localDevice == null) { - Slog.w(TAG, "Local device not available"); - return; + if (mMhlController != null) { + HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); + if (device != null) { + device.sendKeyEvent(keyCode, isPressed); + return; + } + } + if (mCecController != null) { + HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); + if (localDevice == null) { + Slog.w(TAG, "Local device not available"); + return; + } + localDevice.sendKeyEvent(keyCode, isPressed); } - localDevice.sendKeyEvent(keyCode, isPressed); } }); } @@ -1064,7 +1137,7 @@ public final class HdmiControlService extends SystemService { @Override public List<HdmiPortInfo> getPortInfo() { enforceAccessPermission(); - return mPortInfo; + return HdmiControlService.this.getPortInfo(); } @Override @@ -1130,10 +1203,12 @@ public final class HdmiControlService extends SystemService { // No need to hold the lock for obtaining TV device as the local device instance // is preserved while the HDMI control is enabled. HdmiCecLocalDeviceTv tv = tv(); - if (tv == null) { - return Collections.emptyList(); + synchronized (mLock) { + List<HdmiDeviceInfo> cecDevices = (tv == null) + ? Collections.<HdmiDeviceInfo>emptyList() + : tv.getSafeExternalInputsLocked(); + return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); } - return tv.getSafeExternalInputs(); } @Override @@ -1634,12 +1709,28 @@ public final class HdmiControlService extends SystemService { }); } + @ServiceThreadOnly + private void onLanguageChanged(String language) { + assertRunOnServiceThread(); + mLanguage = language; + + if (isTvDevice()) { + tv().broadcastMenuLanguage(language); + } + } + private void disableDevices(PendingActionClearedCallback callback) { - for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { - device.disableDevice(mStandbyMessageReceived, callback); + if (mCecController != null) { + for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { + device.disableDevice(mStandbyMessageReceived, callback); + } + if (isTvDevice()) { + unregisterSettingsObserver(); + } } - if (isTvDevice()) { - unregisterSettingsObserver(); + + if (mMhlController != null) { + mMhlController.clearAllLocalDevices(); } } @@ -1711,7 +1802,7 @@ public final class HdmiControlService extends SystemService { } @ServiceThreadOnly - void setOption(int key, int value) { + void setCecOption(int key, int value) { assertRunOnServiceThread(); mCecController.setOption(key, value); } diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 23f19ff..22a519b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -206,6 +206,22 @@ final class HdmiUtils { return list; } + static <T> List<T> mergeToUnmodifiableList(List<T> a, List<T> b) { + if (a.isEmpty() && b.isEmpty()) { + return Collections.emptyList(); + } + if (a.isEmpty()) { + return Collections.unmodifiableList(b); + } + if (b.isEmpty()) { + return Collections.unmodifiableList(a); + } + List<T> newList = new ArrayList<>(); + newList.addAll(a); + newList.addAll(b); + return Collections.unmodifiableList(newList); + } + /** * See if the new path is affecting the active path. * diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index 51e68b6..722be71 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -207,7 +207,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } private void mayChangeRoutingPath(int address) { - HdmiDeviceInfo info = tv().getDeviceInfo(address); + HdmiDeviceInfo info = tv().getCecDeviceInfo(address); if (info != null) { tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress()); } diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 9f09eb4..ed978e0 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -109,12 +109,12 @@ final class SendKeyAction extends HdmiCecFeatureAction { } private void sendKeyDown(int keycode) { - byte[] keycodeAndParam = getCecKeycodeAndParam(keycode); - if (keycodeAndParam == null) { + int cecKeycode = HdmiCecKeycode.androidKeyToCecKey(keycode); + if (cecKeycode == HdmiCecKeycode.UNSUPPORTED_KEYCODE) { return; } sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), - mTargetAddress, keycodeAndParam)); + mTargetAddress, new byte[] { (byte) (cecKeycode & 0xFF) })); } private void sendKeyUp() { @@ -141,13 +141,4 @@ final class SendKeyAction extends HdmiCecFeatureAction { sendKeyDown(mLastKeycode); addTimer(mState, IRT_MS); } - - // Converts the Android key code to corresponding CEC key code definition. Those CEC keys - // with additional parameters should be mapped from individual Android key code. 'Select - // Broadcast' with the parameter 'cable', for instance, shall have its counterpart such as - // KeyEvent.KEYCODE_TV_BROADCAST_CABLE. - // The return byte array contains both UI command (keycode) and optional parameter. - private byte[] getCecKeycodeAndParam(int keycode) { - return HdmiCecKeycode.androidKeyToCecKey(keycode); - } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 7c1681c..93dceff 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -774,6 +774,8 @@ public class InputManagerService extends IInputManager.Stub .setContentIntent(mKeyboardLayoutIntent) .setSmallIcon(R.drawable.ic_settings_language) .setPriority(Notification.PRIORITY_LOW) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)) .build(); mNotificationManager.notifyAsUser(null, R.string.select_keyboard_layout_notification_title, diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index 94cf668..9dcc529 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -23,6 +23,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.IHardwareService; import android.os.Message; +import android.os.Trace; import android.util.Slog; import java.io.FileInputStream; @@ -105,7 +106,12 @@ public class LightsService extends SystemService { mMode = mode; mOnMS = onMS; mOffMS = offMS; - setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")"); + try { + setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index e549ead..978a9f4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -22,17 +22,16 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.AudioManager; +import android.media.AudioManagerInternal; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; -import android.media.routing.IMediaRouter; -import android.media.routing.IMediaRouterDelegate; -import android.media.routing.IMediaRouterStateCallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionController; import android.media.session.ISessionControllerCallback; import android.media.session.MediaController; +import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession; import android.media.session.ParcelableVolumeInfo; import android.media.session.PlaybackState; @@ -52,6 +51,8 @@ import android.util.Log; import android.util.Slog; import android.view.KeyEvent; +import com.android.server.LocalServices; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.UUID; @@ -93,7 +94,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { new ArrayList<ISessionControllerCallback>(); private long mFlags; - private IMediaRouter mMediaRouter; private PendingIntent mMediaButtonReceiver; private PendingIntent mLaunchIntent; @@ -111,7 +111,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { // Volume handling fields private AudioAttributes mAudioAttrs; private AudioManager mAudioManager; - private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL; + private AudioManagerInternal mAudioManagerInternal; + private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; private int mMaxVolume = 0; private int mCurrentVolume = 0; @@ -134,6 +135,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mService = service; mHandler = new MessageHandler(handler.getLooper()); mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE); + mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); } @@ -227,7 +229,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { * * @param direction The direction to adjust volume in. */ - public void adjustVolume(int direction, int flags) { + public void adjustVolume(int direction, int flags, String packageName, int uid) { if (isPlaybackActive(false)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } @@ -236,9 +238,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } else if (direction < -1) { direction = -1; } - if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { + if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); - mAudioManager.adjustStreamVolume(stream, direction, flags); + mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, packageName, + uid); } else { if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { // Nothing to do, the volume cannot be changed @@ -262,10 +265,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } - public void setVolumeTo(int value, int flags) { - if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) { + public void setVolumeTo(int value, int flags, String packageName, int uid) { + if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); - mAudioManager.setStreamVolume(stream, value, flags); + mAudioManagerInternal.setStreamVolumeForUid(stream, value, flags, packageName, uid); } else { if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) { // Nothing to do. The volume can't be set directly. @@ -397,6 +400,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { return; } mDestroyed = true; + mHandler.post(MessageHandler.MSG_DESTROYED); } } @@ -575,6 +579,29 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } + private void pushSessionDestroyed() { + synchronized (mLock) { + // This is the only method that may be (and can only be) called + // after the session is destroyed. + if (!mDestroyed) { + return; + } + for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) { + ISessionControllerCallback cb = mControllerCallbacks.get(i); + try { + cb.onSessionDestroyed(); + } catch (DeadObjectException e) { + Log.w(TAG, "Removing dead callback in pushEvent.", e); + mControllerCallbacks.remove(i); + } catch (RemoteException e) { + Log.w(TAG, "unexpected exception in pushEvent.", e); + } + } + // After notifying clear all listeners + mControllerCallbacks.clear(); + } + } + private PlaybackState getStateWithUpdatedPosition() { PlaybackState state = mPlaybackState; long duration = -1; @@ -662,12 +689,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void setMediaRouter(IMediaRouter router) { - mMediaRouter = router; - mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); - } - - @Override public void setMediaButtonReceiver(PendingIntent pi) { mMediaButtonReceiver = pi; } @@ -728,8 +749,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public void setPlaybackToLocal(AudioAttributes attributes) { boolean typeChanged; synchronized (mLock) { - typeChanged = mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE; - mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL; + typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE; + mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; if (attributes != null) { mAudioAttrs = attributes; } else { @@ -745,8 +766,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public void setPlaybackToRemote(int control, int max) { boolean typeChanged; synchronized (mLock) { - typeChanged = mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL; - mVolumeType = MediaSession.PLAYBACK_TYPE_REMOTE; + typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL; + mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE; mVolumeControlType = control; mMaxVolume = max; } @@ -919,6 +940,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void registerCallbackListener(ISessionControllerCallback cb) { synchronized (mLock) { + // If this session is already destroyed tell the caller and + // don't add them. + if (mDestroyed) { + try { + cb.onSessionDestroyed(); + } catch (Exception e) { + // ignored + } + return; + } if (getControllerCbIndexForCb(cb) < 0) { mControllerCallbacks.add(cb); if (DEBUG) { @@ -968,7 +999,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { int type; int max; int current; - if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) { + if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { type = mVolumeControlType; max = mMaxVolume; current = mOptimisticVolume != -1 ? mOptimisticVolume @@ -984,20 +1015,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void adjustVolume(int direction, int flags) { + public void adjustVolume(int direction, int flags, String packageName) { + int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaSessionRecord.this.adjustVolume(direction, flags); + MediaSessionRecord.this.adjustVolume(direction, flags, packageName, uid); } finally { Binder.restoreCallingIdentity(token); } } @Override - public void setVolumeTo(int value, int flags) { + public void setVolumeTo(int value, int flags, String packageName) { + int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaSessionRecord.this.setVolumeTo(value, flags); + MediaSessionRecord.this.setVolumeTo(value, flags, packageName, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -1105,13 +1138,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public boolean isTransportControlEnabled() { return MediaSessionRecord.this.isTransportControlEnabled(); } - - @Override - public IMediaRouterDelegate createMediaRouterDelegate( - IMediaRouterStateCallback callback) { - // todo - return null; - } } private class MessageHandler extends Handler { @@ -1123,6 +1149,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private static final int MSG_SEND_EVENT = 6; private static final int MSG_UPDATE_SESSION_STATE = 7; private static final int MSG_UPDATE_VOLUME = 8; + private static final int MSG_DESTROYED = 9; public MessageHandler(Looper looper) { super(looper); @@ -1154,6 +1181,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { case MSG_UPDATE_VOLUME: pushVolumeUpdate(); break; + case MSG_DESTROYED: + pushSessionDestroyed(); } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 0514f48..0c6d46c 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -37,6 +37,7 @@ import android.media.session.IActiveSessionsListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionManager; +import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession; import android.net.Uri; import android.os.Binder; @@ -812,8 +813,9 @@ public class MediaSessionService extends SystemService implements Monitor { Log.e(TAG, "Error adjusting default volume.", e); } } else { - session.adjustVolume(direction, flags); - if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE + session.adjustVolume(direction, flags, getContext().getPackageName(), + UserHandle.myUserId()); + if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE && mRvc != null) { try { mRvc.remoteVolumeChanged(session.getControllerBinder(), flags); diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index fec11fb..a9bc818 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -16,6 +16,7 @@ package com.android.server.media; +import android.media.session.MediaController.PlaybackInfo; import android.media.session.PlaybackState; import android.media.session.MediaSession; import android.os.UserHandle; @@ -209,7 +210,7 @@ public class MediaSessionStack { int size = records.size(); for (int i = 0; i < size; i++) { MediaSessionRecord record = records.get(i); - if (record.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE) { + if (record.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { return record; } } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 289b5aa..69d1dc9 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -26,6 +26,8 @@ import android.hardware.display.DisplayManager; import android.media.projection.IMediaProjectionManager; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; +import android.media.projection.IMediaProjectionWatcherCallback; +import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.os.Binder; import android.os.Handler; @@ -34,6 +36,7 @@ import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; @@ -59,15 +62,20 @@ public final class MediaProjectionManagerService extends SystemService private static final String TAG = "MediaProjectionManagerService"; private final Object mLock = new Object(); // Protects the list of media projections - private final Map<IBinder, MediaProjection> mProjectionGrants; + private final Map<IBinder, IBinder.DeathRecipient> mDeathEaters; + private final CallbackDelegate mCallbackDelegate; private final Context mContext; private final AppOpsManager mAppOps; + private IBinder mProjectionToken; + private MediaProjection mProjectionGrant; + public MediaProjectionManagerService(Context context) { super(context); mContext = context; - mProjectionGrants = new ArrayMap<IBinder, MediaProjection>(); + mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); + mCallbackDelegate = new CallbackDelegate(); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); Watchdog.getInstance().addMonitor(this); } @@ -83,13 +91,97 @@ public final class MediaProjectionManagerService extends SystemService synchronized (mLock) { /* check for deadlock */ } } + private void startProjectionLocked(final MediaProjection projection) { + if (mProjectionGrant != null) { + mProjectionGrant.stop(); + } + mProjectionToken = projection.asBinder(); + mProjectionGrant = projection; + dispatchStart(projection); + } + + private void stopProjectionLocked(final MediaProjection projection) { + mProjectionToken = null; + mProjectionGrant = null; + dispatchStop(projection); + } + + private void addCallback(final IMediaProjectionWatcherCallback callback) { + IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + unlinkDeathRecipientLocked(callback); + removeCallback(callback); + } + } + }; + synchronized (mLock) { + mCallbackDelegate.add(callback); + linkDeathRecipientLocked(callback, deathRecipient); + } + } + + private void removeCallback(IMediaProjectionWatcherCallback callback) { + synchronized (mLock) { + unlinkDeathRecipientLocked(callback); + removeCallback(callback); + } + } + + private void linkDeathRecipientLocked(IMediaProjectionWatcherCallback callback, + IBinder.DeathRecipient deathRecipient) { + try { + final IBinder token = callback.asBinder(); + token.linkToDeath(deathRecipient, 0); + mDeathEaters.put(token, deathRecipient); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to link to death for media projection monitoring callback", e); + } + } + + private void unlinkDeathRecipientLocked(IMediaProjectionWatcherCallback callback) { + final IBinder token = callback.asBinder(); + IBinder.DeathRecipient deathRecipient = mDeathEaters.remove(token); + if (deathRecipient != null) { + token.unlinkToDeath(deathRecipient, 0); + } + } + + private void dispatchStart(MediaProjection projection) { + mCallbackDelegate.dispatchStart(projection); + } + + private void dispatchStop(MediaProjection projection) { + mCallbackDelegate.dispatchStop(projection); + } + + private boolean isValidMediaProjection(IBinder token) { + synchronized (mLock) { + if (mProjectionToken != null) { + return mProjectionToken.equals(token); + } + return false; + } + } + + private MediaProjectionInfo getActiveProjectionInfo() { + synchronized (mLock) { + if (mProjectionGrant == null) { + return null; + } + return mProjectionGrant.getProjectionInfo(); + } + } + private void dump(final PrintWriter pw) { pw.println("MEDIA PROJECTION MANAGER (dumpsys media_projection)"); synchronized (mLock) { - Collection<MediaProjection> projections = mProjectionGrants.values(); - pw.println("Media Projections: size=" + projections.size()); - for (MediaProjection mp : projections) { - mp.dump(pw, " "); + pw.println("Media Projection: "); + if (mProjectionGrant != null ) { + mProjectionGrant.dump(pw); + } else { + pw.println("null"); } } } @@ -115,11 +207,14 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call public IMediaProjection createProjection(int uid, String packageName, int type, boolean isPermanentGrant) { - if (mContext.checkCallingPermission(Manifest.permission.CREATE_MEDIA_PROJECTION) + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires CREATE_MEDIA_PROJECTION in order to grant " + throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to grant " + "projection permission"); } + if (packageName == null || packageName.isEmpty()) { + throw new IllegalArgumentException("package name must not be empty"); + } long callingToken = Binder.clearCallingIdentity(); MediaProjection projection; try { @@ -136,7 +231,71 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call public boolean isValidMediaProjection(IMediaProjection projection) { - return mProjectionGrants.containsKey(projection.asBinder()); + return MediaProjectionManagerService.this.isValidMediaProjection( + projection.asBinder()); + } + + @Override // Binder call + public MediaProjectionInfo getActiveProjectionInfo() { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " + + "projection callbacks"); + } + final long token = Binder.clearCallingIdentity(); + try { + return MediaProjectionManagerService.this.getActiveProjectionInfo(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void stopActiveProjection() { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " + + "projection callbacks"); + } + final long token = Binder.clearCallingIdentity(); + try { + if (mProjectionGrant != null) { + mProjectionGrant.stop(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + + } + + @Override //Binder call + public void addCallback(final IMediaProjectionWatcherCallback callback) { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to add " + + "projection callbacks"); + } + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.addCallback(callback); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void removeCallback(IMediaProjectionWatcherCallback callback) { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to remove " + + "projection callbacks"); + } + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.removeCallback(callback); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override // Binder call @@ -157,25 +316,27 @@ public final class MediaProjectionManagerService extends SystemService } } + private boolean checkPermission(String packageName, String permission) { return mContext.getPackageManager().checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED; } } - private final class MediaProjection extends IMediaProjection.Stub implements DeathRecipient { - public int uid; - public String packageName; + private final class MediaProjection extends IMediaProjection.Stub { + public final int uid; + public final String packageName; + public final UserHandle userHandle; private IBinder mToken; + private IBinder.DeathRecipient mDeathEater; private int mType; - private CallbackDelegate mCallbackDelegate; public MediaProjection(int type, int uid, String packageName) { mType = type; this.uid = uid; this.packageName = packageName; - mCallbackDelegate = new CallbackDelegate(); + userHandle = new UserHandle(UserHandle.getUserId(uid)); } @Override // Binder call @@ -220,49 +381,50 @@ public final class MediaProjectionManagerService extends SystemService } @Override // Binder call - public void start(IMediaProjectionCallback callback) { + public void start(final IMediaProjectionCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } synchronized (mLock) { - if (mProjectionGrants.containsKey(asBinder())) { + if (isValidMediaProjection(asBinder())) { throw new IllegalStateException( "Cannot start already started MediaProjection"); } addCallback(callback); try { mToken = callback.asBinder(); - mToken.linkToDeath(this, 0); + mDeathEater = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + mCallbackDelegate.remove(callback); + stop(); + } + }; + mToken.linkToDeath(mDeathEater, 0); } catch (RemoteException e) { Slog.w(TAG, "MediaProjectionCallbacks must be valid, aborting MediaProjection", e); return; } - mProjectionGrants.put(asBinder(), this); + startProjectionLocked(this); } } @Override // Binder call public void stop() { synchronized (mLock) { - if (!mProjectionGrants.containsKey(asBinder())) { + if (!isValidMediaProjection(asBinder())) { Slog.w(TAG, "Attempted to stop inactive MediaProjection " + "(uid=" + Binder.getCallingUid() + ", " + "pid=" + Binder.getCallingPid() + ")"); return; } - mToken.unlinkToDeath(this, 0); - mCallbackDelegate.dispatchStop(); - mProjectionGrants.remove(asBinder()); + mToken.unlinkToDeath(mDeathEater, 0); + stopProjectionLocked(this); } } @Override - public void binderDied() { - stop(); - } - - @Override public void addCallback(IMediaProjectionCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); @@ -278,52 +440,143 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.remove(callback); } - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "(" + packageName + ", uid=" + uid + "): " + typeToString(mType)); + public MediaProjectionInfo getProjectionInfo() { + return new MediaProjectionInfo(packageName, userHandle); + } + + public void dump(PrintWriter pw) { + pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType)); } } + private static class CallbackDelegate { - private static final int MSG_ON_STOP = 0; - private List<IMediaProjectionCallback> mCallbacks; + private Map<IBinder, IMediaProjectionCallback> mClientCallbacks; + private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks; private Handler mHandler; private Object mLock = new Object(); public CallbackDelegate() { mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); - mCallbacks = new ArrayList<IMediaProjectionCallback>(); + mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>(); + mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>(); } public void add(IMediaProjectionCallback callback) { synchronized (mLock) { - mCallbacks.add(callback); + mClientCallbacks.put(callback.asBinder(), callback); + } + } + + public void add(IMediaProjectionWatcherCallback callback) { + synchronized (mLock) { + mWatcherCallbacks.put(callback.asBinder(), callback); } } public void remove(IMediaProjectionCallback callback) { synchronized (mLock) { - mCallbacks.remove(callback); + mClientCallbacks.remove(callback.asBinder()); } } - public void dispatchStop() { + public void remove(IMediaProjectionWatcherCallback callback) { synchronized (mLock) { - for (final IMediaProjectionCallback callback : mCallbacks) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - callback.onStop(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify media projection has stopped", e); - } - } - }); + mWatcherCallbacks.remove(callback.asBinder()); + } + } + + public void dispatchStart(MediaProjection projection) { + if (projection == null) { + Slog.e(TAG, "Tried to dispatch start notification for a null media projection." + + " Ignoring!"); + return; + } + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + MediaProjectionInfo info = projection.getProjectionInfo(); + mHandler.post(new WatcherStartCallback(info, callback)); } } } + + public void dispatchStop(MediaProjection projection) { + if (projection == null) { + Slog.e(TAG, "Tried to dispatch stop notification for a null media projection." + + " Ignoring!"); + return; + } + synchronized (mLock) { + for (IMediaProjectionCallback callback : mClientCallbacks.values()) { + mHandler.post(new ClientStopCallback(callback)); + } + + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + MediaProjectionInfo info = projection.getProjectionInfo(); + mHandler.post(new WatcherStopCallback(info, callback)); + } + } + } + } + + private static final class WatcherStartCallback implements Runnable { + private IMediaProjectionWatcherCallback mCallback; + private MediaProjectionInfo mInfo; + + public WatcherStartCallback(MediaProjectionInfo info, + IMediaProjectionWatcherCallback callback) { + mInfo = info; + mCallback = callback; + } + + @Override + public void run() { + try { + mCallback.onStart(mInfo); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify media projection has stopped", e); + } + } + } + + private static final class WatcherStopCallback implements Runnable { + private IMediaProjectionWatcherCallback mCallback; + private MediaProjectionInfo mInfo; + + public WatcherStopCallback(MediaProjectionInfo info, + IMediaProjectionWatcherCallback callback) { + mInfo = info; + mCallback = callback; + } + + @Override + public void run() { + try { + mCallback.onStop(mInfo); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify media projection has stopped", e); + } + } } + private static final class ClientStopCallback implements Runnable { + private IMediaProjectionCallback mCallback; + + public ClientStopCallback(IMediaProjectionCallback callback) { + mCallback = callback; + } + + @Override + public void run() { + try { + mCallback.onStop(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify media projection has stopped", e); + } + } + } + + private static String typeToString(int type) { switch (type) { case MediaProjectionManager.TYPE_SCREEN_CAPTURE: diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index 907eeb2..857b9e9 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -20,10 +20,10 @@ import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; -import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; +import android.net.StaticIpConfiguration; import android.os.Handler; import android.os.HandlerThread; import android.text.TextUtils; @@ -41,6 +41,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; +import java.net.Inet4Address; import java.util.Map; public class IpConfigStore { @@ -69,40 +70,32 @@ public class IpConfigStore { } private boolean writeConfig(DataOutputStream out, int configKey, - IpConfiguration config) throws IOException { + IpConfiguration config) throws IOException { boolean written = false; try { - LinkProperties linkProperties = config.linkProperties; switch (config.ipAssignment) { case STATIC: out.writeUTF(IP_ASSIGNMENT_KEY); out.writeUTF(config.ipAssignment.toString()); - for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) { - out.writeUTF(LINK_ADDRESS_KEY); - out.writeUTF(linkAddr.getAddress().getHostAddress()); - out.writeInt(linkAddr.getPrefixLength()); - } - for (RouteInfo route : linkProperties.getRoutes()) { - out.writeUTF(GATEWAY_KEY); - LinkAddress dest = route.getDestinationLinkAddress(); - if (dest != null) { - out.writeInt(1); - out.writeUTF(dest.getAddress().getHostAddress()); - out.writeInt(dest.getPrefixLength()); - } else { - out.writeInt(0); + StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration; + if (staticIpConfiguration != null) { + if (staticIpConfiguration.ipAddress != null) { + LinkAddress ipAddress = staticIpConfiguration.ipAddress; + out.writeUTF(LINK_ADDRESS_KEY); + out.writeUTF(ipAddress.getAddress().getHostAddress()); + out.writeInt(ipAddress.getPrefixLength()); } - if (route.getGateway() != null) { - out.writeInt(1); - out.writeUTF(route.getGateway().getHostAddress()); - } else { - out.writeInt(0); + if (staticIpConfiguration.gateway != null) { + out.writeUTF(GATEWAY_KEY); + out.writeInt(0); // Default route. + out.writeInt(1); // Have a gateway. + out.writeUTF(staticIpConfiguration.gateway.getHostAddress()); + } + for (InetAddress inetAddr : staticIpConfiguration.dnsServers) { + out.writeUTF(DNS_KEY); + out.writeUTF(inetAddr.getHostAddress()); } - } - for (InetAddress inetAddr : linkProperties.getDnsServers()) { - out.writeUTF(DNS_KEY); - out.writeUTF(inetAddr.getHostAddress()); } written = true; break; @@ -121,7 +114,7 @@ public class IpConfigStore { switch (config.proxySettings) { case STATIC: - ProxyInfo proxyProperties = linkProperties.getHttpProxy(); + ProxyInfo proxyProperties = config.httpProxy; String exclusionList = proxyProperties.getExclusionListAsString(); out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); @@ -134,7 +127,7 @@ public class IpConfigStore { written = true; break; case PAC: - ProxyInfo proxyPacProperties = linkProperties.getHttpProxy(); + ProxyInfo proxyPacProperties = config.httpProxy; out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); out.writeUTF(PROXY_PAC_FILE); @@ -159,7 +152,7 @@ public class IpConfigStore { out.writeInt(configKey); } } catch (NullPointerException e) { - loge("Failure in writing " + config.linkProperties + e); + loge("Failure in writing " + config + e); } out.writeUTF(EOS); @@ -196,7 +189,7 @@ public class IpConfigStore { // Default is DHCP with no proxy IpAssignment ipAssignment = IpAssignment.DHCP; ProxySettings proxySettings = ProxySettings.NONE; - LinkProperties linkProperties = new LinkProperties(); + StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); String proxyHost = null; String pacFileUrl = null; int proxyPort = -1; @@ -213,13 +206,23 @@ public class IpConfigStore { } else if (key.equals(LINK_ADDRESS_KEY)) { LinkAddress linkAddr = new LinkAddress( NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt()); - linkProperties.addLinkAddress(linkAddr); + if (linkAddr.getAddress() instanceof Inet4Address && + staticIpConfiguration.ipAddress == null) { + staticIpConfiguration.ipAddress = linkAddr; + } else { + loge("Non-IPv4 or duplicate address: " + linkAddr); + } } else if (key.equals(GATEWAY_KEY)) { LinkAddress dest = null; InetAddress gateway = null; if (version == 1) { // only supported default gateways - leave the dest/prefix empty gateway = NetworkUtils.numericToInetAddress(in.readUTF()); + if (staticIpConfiguration.gateway == null) { + staticIpConfiguration.gateway = gateway; + } else { + loge("Duplicate gateway: " + gateway.getHostAddress()); + } } else { if (in.readInt() == 1) { dest = new LinkAddress( @@ -229,10 +232,16 @@ public class IpConfigStore { if (in.readInt() == 1) { gateway = NetworkUtils.numericToInetAddress(in.readUTF()); } + RouteInfo route = new RouteInfo(dest, gateway); + if (route.isIPv4Default() && + staticIpConfiguration.gateway == null) { + staticIpConfiguration.gateway = gateway; + } else { + loge("Non-IPv4 default or duplicate route: " + route); + } } - linkProperties.addRoute(new RouteInfo(dest, gateway)); } else if (key.equals(DNS_KEY)) { - linkProperties.addDnsServer( + staticIpConfiguration.dnsServers.add( NetworkUtils.numericToInetAddress(in.readUTF())); } else if (key.equals(PROXY_SETTINGS_KEY)) { proxySettings = ProxySettings.valueOf(in.readUTF()); @@ -258,9 +267,11 @@ public class IpConfigStore { IpConfiguration config = new IpConfiguration(); networks.put(id, config); - config.linkProperties = linkProperties; switch (ipAssignment) { case STATIC: + config.staticIpConfiguration = staticIpConfiguration; + config.ipAssignment = ipAssignment; + break; case DHCP: config.ipAssignment = ipAssignment; break; @@ -276,16 +287,15 @@ public class IpConfigStore { switch (proxySettings) { case STATIC: - config.proxySettings = proxySettings; - ProxyInfo ProxyInfo = + ProxyInfo proxyInfo = new ProxyInfo(proxyHost, proxyPort, exclusionList); - linkProperties.setHttpProxy(ProxyInfo); + config.proxySettings = proxySettings; + config.httpProxy = proxyInfo; break; case PAC: + ProxyInfo proxyPacProperties = new ProxyInfo(pacFileUrl); config.proxySettings = proxySettings; - ProxyInfo proxyPacProperties = - new ProxyInfo(pacFileUrl); - linkProperties.setHttpProxy(proxyPacProperties); + config.httpProxy = proxyPacProperties; break; case NONE: config.proxySettings = proxySettings; diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index a2e9d67..04df3e7 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -205,7 +205,6 @@ public class LockdownVpnTracker { private void initLocked() { Slog.d(TAG, "initLocked()"); - mVpn.setEnableNotifications(false); mVpn.setEnableTeardown(false); final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET); @@ -249,7 +248,6 @@ public class LockdownVpnTracker { hideNotification(); mContext.unregisterReceiver(mResetReceiver); - mVpn.setEnableNotifications(true); mVpn.setEnableTeardown(true); } @@ -304,16 +302,18 @@ public class LockdownVpnTracker { } private void showNotification(int titleRes, int iconRes) { - final Notification.Builder builder = new Notification.Builder(mContext); - builder.setWhen(0); - builder.setSmallIcon(iconRes); - builder.setContentTitle(mContext.getString(titleRes)); - builder.setContentText(mContext.getString(R.string.vpn_lockdown_config)); - builder.setContentIntent(mConfigIntent); - builder.setPriority(Notification.PRIORITY_LOW); - builder.setOngoing(true); - builder.addAction( - R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent); + final Notification.Builder builder = new Notification.Builder(mContext) + .setWhen(0) + .setSmallIcon(iconRes) + .setContentTitle(mContext.getString(titleRes)) + .setContentText(mContext.getString(R.string.vpn_lockdown_config)) + .setContentIntent(mConfigIntent) + .setPriority(Notification.PRIORITY_LOW) + .setOngoing(true) + .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), + mResetIntent) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); NotificationManager.from(mContext).notify(TAG, 0, builder.build()); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ad2bb92..9e5fa41 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -791,6 +791,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final Notification.Builder builder = new Notification.Builder(mContext); builder.setOnlyAlertOnce(true); builder.setWhen(0L); + builder.setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); final Resources res = mContext.getResources(); switch (type) { @@ -916,6 +918,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setTicker(title); builder.setContentTitle(title); builder.setContentText(body); + builder.setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); final Intent intent = buildAllowBackgroundDataIntent(); builder.setContentIntent( @@ -1060,9 +1064,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state); final String baseIface = state.linkProperties.getInterfaceName(); - connIdents.add(Pair.create(baseIface, ident)); - if (powerSave) { - connIfaces.add(baseIface); + if (baseIface != null) { + connIdents.add(Pair.create(baseIface, ident)); + if (powerSave) { + connIfaces.add(baseIface); + } } // Stacked interfaces are considered to have same identity as @@ -1070,9 +1076,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); - connIdents.add(Pair.create(stackedIface, ident)); - if (powerSave) { - connIfaces.add(stackedIface); + if (stackedIface != null) { + connIdents.add(Pair.create(stackedIface, ident)); + if (powerSave) { + connIfaces.add(stackedIface); + } } } } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index e35ca46..f995dee 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -896,10 +896,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Traffic occurring on the base interface is always counted for // both total usage and UID details. final String baseIface = state.linkProperties.getInterfaceName(); - findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); - if (isMobile) { - mobileIfaces.add(baseIface); + if (baseIface != null) { + findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); + if (isMobile) { + mobileIfaces.add(baseIface); + } } // Traffic occurring on stacked interfaces is usually clatd, @@ -909,15 +911,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); - if (isMobile) { - mobileIfaces.add(stackedIface); + if (stackedIface != null) { + findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); + if (isMobile) { + mobileIfaces.add(stackedIface); + } } } } } - mobileIfaces.remove(null); mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]); } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index a06daf6..189131c 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -51,8 +51,9 @@ public class ConditionProviders extends ManagedServices { = new ArrayMap<IBinder, IConditionListener>(); private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); private final CountdownConditionProvider mCountdown = new CountdownConditionProvider(); + private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider(); - private Uri mExitConditionId; + private Condition mExitCondition; private ComponentName mExitConditionComponent; public ConditionProviders(Context context, Handler handler, @@ -97,6 +98,7 @@ public class ConditionProviders extends ManagedServices { } } mCountdown.dump(pw, filter); + mDowntime.dump(pw, filter); } @Override @@ -110,6 +112,10 @@ public class ConditionProviders extends ManagedServices { mCountdown.attachBase(mContext); registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, UserHandle.USER_OWNER); + mDowntime.attachBase(mContext); + registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, + UserHandle.USER_OWNER); + mDowntime.setCallback(new DowntimeCallback()); } @Override @@ -125,7 +131,7 @@ public class ConditionProviders extends ManagedServices { if (info.component.equals(mExitConditionComponent)) { // ensure record exists, we'll wire it up and subscribe below final ConditionRecord manualRecord = - getRecordLocked(mExitConditionId, mExitConditionComponent); + getRecordLocked(mExitCondition.id, mExitConditionComponent); manualRecord.isManual = true; } final int N = mRecords.size(); @@ -149,11 +155,11 @@ public class ConditionProviders extends ManagedServices { if (!r.component.equals(removed.component)) continue; if (r.isManual) { // removing the current manual condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved"); } if (r.isAutomatic) { // removing an automatic condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved"); } mRecords.remove(i); } @@ -249,7 +255,8 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: manual condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "manualConditionExit"); unsubscribeLocked(r); r.isManual = false; } @@ -263,33 +270,46 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: automatic condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "automaticConditionExit"); } else if (c.state == Condition.STATE_TRUE) { Slog.d(TAG, "Enter zen: automatic condition true: " + c); - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + "automaticConditionEnter"); } } } } } - public void setZenModeCondition(Uri conditionId, String reason) { - if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId); + public void setZenModeCondition(Condition condition, String reason) { + if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition); synchronized(mMutex) { ComponentName conditionComponent = null; - if (ZenModeConfig.isValidCountdownConditionId(conditionId)) { - // constructed by the client, make sure the record exists... - final ConditionRecord r = getRecordLocked(conditionId, - CountdownConditionProvider.COMPONENT); - if (r.info == null) { - // ... and is associated with the in-process service - r.info = checkServiceTokenLocked(mCountdown.asInterface()); + if (condition != null) { + if (ZenModeConfig.isValidCountdownConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + CountdownConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mCountdown.asInterface()); + } + } + if (ZenModeConfig.isValidDowntimeConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + DowntimeConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mDowntime.asInterface()); + } } } final int N = mRecords.size(); for (int i = 0; i < N; i++) { final ConditionRecord r = mRecords.get(i); - final boolean idEqual = r.id.equals(conditionId); + final boolean idEqual = condition != null && r.id.equals(condition.id); if (r.isManual && !idEqual) { // was previous manual condition, unsubscribe unsubscribeLocked(r); @@ -303,10 +323,10 @@ public class ConditionProviders extends ManagedServices { conditionComponent = r.component; } } - if (!Objects.equals(mExitConditionId, conditionId)) { - mExitConditionId = conditionId; + if (!Objects.equals(mExitCondition, condition)) { + mExitCondition = condition; mExitConditionComponent = conditionComponent; - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, reason); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason); saveZenConfigLocked(); } } @@ -318,6 +338,7 @@ public class ConditionProviders extends ManagedServices { RemoteException re = null; if (provider != null) { try { + Slog.d(TAG, "Subscribing to " + r.id + " with " + provider); provider.onSubscribe(r.id); } catch (RemoteException e) { Slog.w(TAG, "Error subscribing to " + r, e); @@ -436,12 +457,13 @@ public class ConditionProviders extends ManagedServices { return; } synchronized (mMutex) { - final boolean changingExit = !Objects.equals(mExitConditionId, config.exitConditionId); - mExitConditionId = config.exitConditionId; + final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition); + mExitCondition = config.exitCondition; mExitConditionComponent = config.exitConditionComponent; if (changingExit) { - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, "config"); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config"); } + mDowntime.setConfig(config); if (config.conditionComponents == null || config.conditionIds == null || config.conditionComponents.length != config.conditionIds.length) { if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); @@ -488,7 +510,7 @@ public class ConditionProviders extends ManagedServices { config.conditionIds[i] = r.id; } } - config.exitConditionId = mExitConditionId; + config.exitCondition = mExitCondition; config.exitConditionComponent = mExitConditionComponent; if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); mZenModeHelper.setConfig(config); @@ -510,6 +532,26 @@ public class ConditionProviders extends ManagedServices { } } + private class DowntimeCallback implements DowntimeConditionProvider.Callback { + @Override + public void onDowntimeChanged(boolean inDowntime) { + final int mode = mZenModeHelper.getZenMode(); + final ZenModeConfig config = mZenModeHelper.getConfig(); + // enter downtime + if (inDowntime && mode == Global.ZEN_MODE_OFF && config != null) { + final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(), + Condition.STATE_TRUE); + mZenModeHelper.setZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtimeEnter"); + setZenModeCondition(condition, "downtime"); + } + // exit downtime + if (!inDowntime && mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && mDowntime.isDowntimeCondition(mExitCondition)) { + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); + } + } + } + private static class ConditionRecord { public final Uri id; public final ComponentName component; diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index aaf7cfc..37aacaa 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -29,6 +29,7 @@ import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; +import android.util.Log; import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; @@ -38,8 +39,8 @@ import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ public class CountdownConditionProvider extends ConditionProviderService { - private static final String TAG = "CountdownConditionProvider"; - private static final boolean DEBUG = false; + private static final String TAG = "CountdownConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", CountdownConditionProvider.class.getName()); diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java new file mode 100644 index 0000000..317ebef --- /dev/null +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -0,0 +1,289 @@ +/** + * Copyright (c) 2014, 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.notification; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.service.notification.Condition; +import android.service.notification.ConditionProviderService; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.DowntimeInfo; +import android.text.format.DateFormat; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** Built-in zen condition provider for managing downtime */ +public class DowntimeConditionProvider extends ConditionProviderService { + private static final String TAG = "DowntimeConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final ComponentName COMPONENT = + new ComponentName("android", DowntimeConditionProvider.class.getName()); + + private static final String ENTER_ACTION = TAG + ".enter"; + private static final int ENTER_CODE = 100; + private static final String EXIT_ACTION = TAG + ".exit"; + private static final int EXIT_CODE = 101; + private static final String EXTRA_TIME = "time"; + + private final Calendar mCalendar = Calendar.getInstance(); + private final Context mContext = this; + private final ArraySet<Integer> mDays = new ArraySet<Integer>(); + + private boolean mConnected; + private boolean mInDowntime; + private ZenModeConfig mConfig; + private Callback mCallback; + + public DowntimeConditionProvider() { + if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()"); + } + + public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" DowntimeConditionProvider:"); + pw.print(" mConnected="); pw.println(mConnected); + pw.print(" mInDowntime="); pw.println(mInDowntime); + } + + public void attachBase(Context base) { + attachBaseContext(base); + } + + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void onConnected() { + if (DEBUG) Slog.d(TAG, "onConnected"); + mConnected = true; + final IntentFilter filter = new IntentFilter(); + filter.addAction(ENTER_ACTION); + filter.addAction(EXIT_ACTION); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mReceiver, filter); + init(); + } + + @Override + public void onDestroy() { + if (DEBUG) Slog.d(TAG, "onDestroy"); + mConnected = false; + } + + @Override + public void onRequestConditions(int relevance) { + if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); + if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) { + if (mInDowntime && mConfig != null) { + notifyCondition(createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE)); + } + } + } + + @Override + public void onSubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); + final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); + if (downtime != null && mConfig != null) { + final int state = mConfig.toDowntimeInfo().equals(downtime) && mInDowntime + ? Condition.STATE_TRUE : Condition.STATE_FALSE; + if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state)); + notifyCondition(createCondition(downtime, state)); + } + } + + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId); + } + + public void setConfig(ZenModeConfig config) { + if (Objects.equals(mConfig, config)) return; + if (DEBUG) Slog.d(TAG, "setConfig"); + mConfig = config; + if (mConnected) { + init(); + } + } + + public boolean isInDowntime() { + return mInDowntime; + } + + public Condition createCondition(DowntimeInfo downtime, int state) { + if (downtime == null) return null; + final Uri id = ZenModeConfig.toDowntimeConditionId(downtime); + final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"; + final Locale locale = Locale.getDefault(); + final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton); + final long time = getTime(System.currentTimeMillis(), downtime.endHour, downtime.endMinute); + final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(time)); + final String summary = mContext.getString(R.string.downtime_condition_summary, formatted); + return new Condition(id, summary, "", "", 0, state, Condition.FLAG_RELEVANT_NOW); + } + + public boolean isDowntimeCondition(Condition condition) { + return condition != null && ZenModeConfig.isValidDowntimeConditionId(condition.id); + } + + private void init() { + updateDays(); + reevaluateDowntime(); + updateAlarms(); + } + + private void updateDays() { + mDays.clear(); + if (mConfig != null) { + final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); + for (int i = 0; days != null && i < days.length; i++) { + mDays.add(days[i]); + } + } + } + + private boolean isInDowntime(long time) { + if (mConfig == null || mDays.size() == 0) return false; + final long start = getTime(time, mConfig.sleepStartHour, mConfig.sleepStartMinute); + long end = getTime(time, mConfig.sleepEndHour, mConfig.sleepEndMinute); + if (start == end) return false; + if (end < start) { + end = addDays(end, 1); + } + return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end); + } + + private boolean isInDowntime(int daysOffset, long time, long start, long end) { + final int day = ((getDayOfWeek(time) + daysOffset - 1) % Calendar.SATURDAY) + 1; + start = addDays(start, daysOffset); + end = addDays(end, daysOffset); + return mDays.contains(day) && time >= start && time < end; + } + + private void reevaluateDowntime() { + final boolean inDowntime = isInDowntime(System.currentTimeMillis()); + if (DEBUG) Slog.d(TAG, "inDowntime=" + inDowntime); + if (inDowntime == mInDowntime) return; + Slog.i(TAG, (inDowntime ? "Entering" : "Exiting" ) + " downtime"); + mInDowntime = inDowntime; + ZenLog.traceDowntime(mInDowntime, getDayOfWeek(System.currentTimeMillis()), mDays); + fireDowntimeChanged(); + } + + private void fireDowntimeChanged() { + if (mCallback != null) { + mCallback.onDowntimeChanged(mInDowntime); + } + } + + private void updateAlarms() { + if (mConfig == null) return; + updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); + updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); + } + + private int getDayOfWeek(long time) { + mCalendar.setTimeInMillis(time); + return mCalendar.get(Calendar.DAY_OF_WEEK); + } + + private long getTime(long millis, int hour, int min) { + mCalendar.setTimeInMillis(millis); + mCalendar.set(Calendar.HOUR_OF_DAY, hour); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + return mCalendar.getTimeInMillis(); + } + + private long addDays(long time, int days) { + mCalendar.setTimeInMillis(time); + mCalendar.add(Calendar.DATE, days); + return mCalendar.getTimeInMillis(); + } + + private void updateAlarm(String action, int requestCode, int hr, int min) { + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final long now = System.currentTimeMillis(); + mCalendar.setTimeInMillis(now); + mCalendar.set(Calendar.HOUR_OF_DAY, hr); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + long time = mCalendar.getTimeInMillis(); + if (time <= now) { + time = addDays(time, 1); + } + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, + new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + if (mConfig.sleepMode != null) { + if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", + action, ts(time), time - now, ts(now))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final long now = System.currentTimeMillis(); + if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { + final long schTime = intent.getLongExtra(EXTRA_TIME, 0); + if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", + action, ts(schTime), ts(now), now - schTime)); + } else { + if (DEBUG) Slog.d(TAG, action + " fired at " + now); + } + reevaluateDowntime(); + updateAlarms(); + } + }; + + public interface Callback { + void onDowntimeChanged(boolean inDowntime); + } +} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 36be21f..f647037 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -41,6 +41,7 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -64,7 +65,7 @@ import java.util.Set; */ abstract public class ManagedServices { protected final String TAG = getClass().getSimpleName(); - protected static final boolean DEBUG = true; + protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String ENABLED_SERVICES_SEPARATOR = ":"; diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 1b59f52..97f0a1e 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -16,8 +16,6 @@ package com.android.server.notification; -import android.os.IBinder; - public interface NotificationDelegate { void onSetDisabled(int status); void onClearAll(int callingUid, int callingPid, int userId); @@ -29,7 +27,6 @@ public interface NotificationDelegate { int uid, int initialPid, String message, int userId); void onPanelRevealed(); void onPanelHidden(); - boolean allowDisable(int what, IBinder token, String pkg); void onNotificationVisibilityChanged( String[] newlyVisibleKeys, String[] noLongerVisibleKeys); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d6afe68..45bd812 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -29,7 +29,6 @@ import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; -import android.app.Notification.Builder; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; @@ -50,7 +49,6 @@ import android.media.AudioManager; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; @@ -585,11 +583,6 @@ public class NotificationManagerService extends SystemService { } @Override - public boolean allowDisable(int what, IBinder token, String pkg) { - return mZenModeHelper.allowDisable(what, token, pkg); - } - - @Override public void onNotificationVisibilityChanged( String[] newlyVisibleKeys, String[] noLongerVisibleKeys) { // Using ';' as separator since eventlogs uses ',' to separate @@ -1351,11 +1344,11 @@ public class NotificationManagerService extends SystemService { } @Override - public void setZenModeCondition(Uri conditionId) { + public void setZenModeCondition(Condition condition) { enforceSystemOrSystemUI("INotificationManager.setZenModeCondition"); final long identity = Binder.clearCallingIdentity(); try { - mConditionProviders.setZenModeCondition(conditionId, "binderCall"); + mConditionProviders.setZenModeCondition(condition, "binderCall"); } finally { Binder.restoreCallingIdentity(identity); } @@ -1535,14 +1528,15 @@ public class NotificationManagerService extends SystemService { } checkCallerIsSystemOrSameApp(pkg); final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); + final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg); final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); // Limit the number of notifications that any given package except the android - // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!isSystemNotification) { + // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. + if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationList) { int count = 0; final int N = mNotificationList.size(); @@ -2727,6 +2721,21 @@ public class NotificationManagerService extends SystemService { Log.e(TAG, "unable to notify listener (listener hints): " + listener, ex); } } + + private boolean isListenerPackage(String packageName) { + if (packageName == null) { + return false; + } + // TODO: clean up locking object later + synchronized (mNotificationList) { + for (final ManagedServiceInfo serviceInfo : mServices) { + if (packageName.equals(serviceInfo.component.getPackageName())) { + return true; + } + } + } + return false; + } } public static final class DumpFilter { diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index b22ed2d..f84409e 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -22,16 +22,15 @@ import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.provider.Settings.Global; +import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; +import android.util.ArraySet; import android.util.Slog; import java.io.PrintWriter; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; -import java.util.HashSet; -import java.util.Set; public class ZenLog { private static final String TAG = "ZenLog"; @@ -43,22 +42,19 @@ public class ZenLog { private static final String[] MSGS = new String[SIZE]; private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); - private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>(Arrays.asList( - "android", - "com.android.systemui" - )); private static final int TYPE_INTERCEPTED = 1; private static final int TYPE_ALLOW_DISABLE = 2; private static final int TYPE_SET_RINGER_MODE = 3; private static final int TYPE_DOWNTIME = 4; - private static final int TYPE_ZEN_MODE = 5; - private static final int TYPE_EXIT_CONDITION = 6; - private static final int TYPE_SUBSCRIBE = 7; - private static final int TYPE_UNSUBSCRIBE = 8; - private static final int TYPE_CONFIG = 9; - private static final int TYPE_FOLLOW_RINGER_MODE = 10; - private static final int TYPE_NOT_INTERCEPTED = 11; + private static final int TYPE_SET_ZEN_MODE = 5; + private static final int TYPE_UPDATE_ZEN_MODE = 6; + private static final int TYPE_EXIT_CONDITION = 7; + private static final int TYPE_SUBSCRIBE = 8; + private static final int TYPE_UNSUBSCRIBE = 9; + private static final int TYPE_CONFIG = 10; + private static final int TYPE_FOLLOW_RINGER_MODE = 11; + private static final int TYPE_NOT_INTERCEPTED = 12; private static int sNext; private static int sSize; @@ -73,26 +69,24 @@ public class ZenLog { append(TYPE_NOT_INTERCEPTED, record.getKey() + "," + reason); } - public static void traceAllowDisable(String pkg, boolean allowDisable, String reason) { - if (SYSTEM_PACKAGES.contains(pkg)) return; - append(TYPE_ALLOW_DISABLE, allowDisable + "," + pkg + "," + reason); - } - public static void traceSetRingerMode(int ringerMode) { append(TYPE_SET_RINGER_MODE, ringerModeToString(ringerMode)); } - public static void traceDowntime(boolean enter, int day, int[] days) { - append(TYPE_DOWNTIME, enter + ",day=" + day + ",days=" + (days != null ? Arrays.asList(days) - : null)); + public static void traceDowntime(boolean inDowntime, int day, ArraySet<Integer> days) { + append(TYPE_DOWNTIME, inDowntime + ",day=" + day + ",days=" + days); + } + + public static void traceSetZenMode(int mode, String reason) { + append(TYPE_SET_ZEN_MODE, zenModeToString(mode) + "," + reason); } public static void traceUpdateZenMode(int fromMode, int toMode) { - append(TYPE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); + append(TYPE_UPDATE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); } - public static void traceExitCondition(Uri id, ComponentName component, String reason) { - append(TYPE_EXIT_CONDITION, id + "," + componentToString(component) + "," + reason); + public static void traceExitCondition(Condition c, ComponentName component, String reason) { + append(TYPE_EXIT_CONDITION, c + "," + componentToString(component) + "," + reason); } public static void traceSubscribe(Uri uri, IConditionProvider provider, RemoteException e) { @@ -122,7 +116,8 @@ public class ZenLog { case TYPE_ALLOW_DISABLE: return "allow_disable"; case TYPE_SET_RINGER_MODE: return "set_ringer_mode"; case TYPE_DOWNTIME: return "downtime"; - case TYPE_ZEN_MODE: return "zen_mode"; + case TYPE_SET_ZEN_MODE: return "set_zen_mode"; + case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode"; case TYPE_EXIT_CONDITION: return "exit_condition"; case TYPE_SUBSCRIBE: return "subscribe"; case TYPE_UNSUBSCRIBE: return "unsubscribe"; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 9282283..0b93690 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -20,10 +20,8 @@ import static android.media.AudioAttributes.USAGE_ALARM; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.AudioAttributes.USAGE_UNKNOWN; -import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.Notification; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -37,13 +35,13 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; -import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings.Global; import android.provider.Settings.Secure; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.telecomm.TelecommManager; +import android.util.Log; import android.util.Slog; import com.android.internal.R; @@ -57,8 +55,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.Objects; /** @@ -66,12 +62,7 @@ import java.util.Objects; */ public class ZenModeHelper { private static final String TAG = "ZenModeHelper"; - - private static final String ACTION_ENTER_ZEN = "enter_zen"; - private static final int REQUEST_CODE_ENTER = 100; - private static final String ACTION_EXIT_ZEN = "exit_zen"; - private static final int REQUEST_CODE_EXIT = 101; - private static final String EXTRA_TIME = "time"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final Handler mHandler; @@ -96,10 +87,8 @@ public class ZenModeHelper { mSettingsObserver.observe(); final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_ENTER_ZEN); - filter.addAction(ACTION_EXIT_ZEN); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(new ZenBroadcastReceiver(), filter); + mContext.registerReceiver(mReceiver, filter); } public static ZenModeConfig readDefaultConfig(Resources resources) { @@ -156,7 +145,7 @@ public class ZenModeHelper { public void requestFromListener(int hints) { final int newZen = zenFromListenerHint(hints, -1); if (newZen != -1) { - setZenMode(newZen); + setZenMode(newZen, "listener"); } } @@ -179,24 +168,19 @@ public class ZenModeHelper { return false; } } - // audience has veto power over all following rules - if (!audienceMatches(record)) { - ZenLog.traceIntercepted(record, "!audienceMatches"); - return true; - } if (isCall(record)) { if (!mConfig.allowCalls) { ZenLog.traceIntercepted(record, "!allowCalls"); return true; } - return false; + return shouldInterceptAudience(record); } if (isMessage(record)) { if (!mConfig.allowMessages) { ZenLog.traceIntercepted(record, "!allowMessages"); return true; } - return false; + return shouldInterceptAudience(record); } ZenLog.traceIntercepted(record, "!allowed"); return true; @@ -204,11 +188,20 @@ public class ZenModeHelper { return false; } + private boolean shouldInterceptAudience(NotificationRecord record) { + if (!audienceMatches(record)) { + ZenLog.traceIntercepted(record, "!audienceMatches"); + return true; + } + return false; + } + public int getZenMode() { return mZenMode; } - public void setZenMode(int zenModeValue) { + public void setZenMode(int zenModeValue, String reason) { + ZenLog.traceSetZenMode(zenModeValue, reason); Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); } @@ -216,9 +209,6 @@ public class ZenModeHelper { final int mode = Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF); if (mode != mZenMode) { - Slog.d(TAG, String.format("updateZenMode: %s -> %s", - Global.zenModeToString(mZenMode), - Global.zenModeToString(mode))); ZenLog.traceUpdateZenMode(mZenMode, mode); } mZenMode = mode; @@ -255,12 +245,12 @@ public class ZenModeHelper { if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { if (ringerMode != AudioManager.RINGER_MODE_SILENT) { mPreviousRingerMode = ringerMode; - Slog.d(TAG, "Silencing ringer"); + if (DEBUG) Slog.d(TAG, "Silencing ringer"); forcedRingerMode = AudioManager.RINGER_MODE_SILENT; } } else { if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - Slog.d(TAG, "Unsilencing ringer"); + if (DEBUG) Slog.d(TAG, "Unsilencing ringer"); forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode : AudioManager.RINGER_MODE_NORMAL; mPreviousRingerMode = -1; @@ -274,18 +264,6 @@ public class ZenModeHelper { dispatchOnZenModeChanged(); } - public boolean allowDisable(int what, IBinder token, String pkg) { - // TODO(cwren): delete this API before the next release. Bug:15344099 - boolean allowDisable = true; - String reason = null; - if (isDefaultPhoneApp(pkg)) { - allowDisable = mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; - reason = mZenMode == Global.ZEN_MODE_OFF ? "zenOff" : "allowCalls"; - } - ZenLog.traceAllowDisable(pkg, allowDisable, reason); - return allowDisable; - } - public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mZenMode="); pw.println(Global.zenModeToString(mZenMode)); @@ -318,7 +296,6 @@ public class ZenModeHelper { dispatchOnConfigChanged(); final String val = Integer.toString(mConfig.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - updateAlarms(); updateZenMode(); return true; } @@ -339,7 +316,7 @@ public class ZenModeHelper { } if (newZen != -1) { ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); - setZenMode(newZen); + setZenMode(newZen, "ringerMode"); } } } @@ -377,7 +354,7 @@ public class ZenModeHelper { final TelecommManager telecomm = (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE); mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; - Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); + if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); } return pkg != null && mDefaultPhoneApp != null && pkg.equals(mDefaultPhoneApp.getPackageName()); @@ -409,40 +386,6 @@ public class ZenModeHelper { } } - private void updateAlarms() { - updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, - mConfig.sleepStartHour, mConfig.sleepStartMinute); - updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, - mConfig.sleepEndHour, mConfig.sleepEndMinute); - } - - private void updateAlarm(String action, int requestCode, int hr, int min) { - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final long now = System.currentTimeMillis(); - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(now); - c.set(Calendar.HOUR_OF_DAY, hr); - c.set(Calendar.MINUTE, min); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - if (c.getTimeInMillis() <= now) { - c.add(Calendar.DATE, 1); - } - final long time = c.getTimeInMillis(); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, - new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - if (mConfig.sleepMode != null) { - Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", - action, ts(time), time - now, ts(now))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } - } - - private static String ts(long time) { - return new Date(time) + " (" + time + ")"; - } - private final Runnable mRingerModeChanged = new Runnable() { @Override public void run() { @@ -475,47 +418,12 @@ public class ZenModeHelper { } } - private class ZenBroadcastReceiver extends BroadcastReceiver { - private final Calendar mCalendar = Calendar.getInstance(); - + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (ACTION_ENTER_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_OFF); - } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { - mHandler.post(mRingerModeChanged); - } + mHandler.post(mRingerModeChanged); } - - private void setZenMode(Intent intent, int zenModeValue) { - final long schTime = intent.getLongExtra(EXTRA_TIME, 0); - final long now = System.currentTimeMillis(); - Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", - intent.getAction(), ts(schTime), ts(now), now - schTime)); - - final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); - boolean enter = false; - final int day = getDayOfWeek(schTime); - if (days != null) { - for (int i = 0; i < days.length; i++) { - if (days[i] == day) { - enter = true; - ZenModeHelper.this.setZenMode(zenModeValue); - break; - } - } - } - ZenLog.traceDowntime(enter, day, days); - updateAlarms(); - } - - private int getDayOfWeek(long time) { - mCalendar.setTimeInMillis(time); - return mCalendar.get(Calendar.DAY_OF_WEEK); - } - } + }; public static class Callback { void onConfigChanged() {} diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index b261ef5..d1e03ec 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -16,33 +16,23 @@ package com.android.server.pm; -import com.android.server.SystemService; - import android.content.Context; import android.content.pm.PackageStats; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; +import android.os.Build; import android.util.Slog; +import dalvik.system.VMRuntime; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; +import com.android.internal.os.InstallerConnection; +import com.android.server.SystemService; public final class Installer extends SystemService { private static final String TAG = "Installer"; - private static final boolean LOCAL_DEBUG = false; - - InputStream mIn; - OutputStream mOut; - LocalSocket mSocket; - - byte buf[] = new byte[1024]; - int buflen = 0; + private final InstallerConnection mInstaller; public Installer(Context context) { super(context); + mInstaller = new InstallerConnection(); } @Override @@ -51,154 +41,6 @@ public final class Installer extends SystemService { ping(); } - private boolean connect() { - if (mSocket != null) { - return true; - } - Slog.i(TAG, "connecting..."); - try { - mSocket = new LocalSocket(); - - LocalSocketAddress address = new LocalSocketAddress("installd", - LocalSocketAddress.Namespace.RESERVED); - - mSocket.connect(address); - - mIn = mSocket.getInputStream(); - mOut = mSocket.getOutputStream(); - } catch (IOException ex) { - disconnect(); - return false; - } - return true; - } - - private void disconnect() { - Slog.i(TAG, "disconnecting..."); - try { - if (mSocket != null) - mSocket.close(); - } catch (IOException ex) { - } - try { - if (mIn != null) - mIn.close(); - } catch (IOException ex) { - } - try { - if (mOut != null) - mOut.close(); - } catch (IOException ex) { - } - mSocket = null; - mIn = null; - mOut = null; - } - - private boolean readBytes(byte buffer[], int len) { - int off = 0, count; - if (len < 0) - return false; - while (off != len) { - try { - count = mIn.read(buffer, off, len - off); - if (count <= 0) { - Slog.e(TAG, "read error " + count); - break; - } - off += count; - } catch (IOException ex) { - Slog.e(TAG, "read exception"); - break; - } - } - if (LOCAL_DEBUG) { - Slog.i(TAG, "read " + len + " bytes"); - } - if (off == len) - return true; - disconnect(); - return false; - } - - private boolean readReply() { - int len; - buflen = 0; - if (!readBytes(buf, 2)) - return false; - len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if ((len < 1) || (len > 1024)) { - Slog.e(TAG, "invalid reply length (" + len + ")"); - disconnect(); - return false; - } - if (!readBytes(buf, len)) - return false; - buflen = len; - return true; - } - - private boolean writeCommand(String _cmd) { - byte[] cmd = _cmd.getBytes(); - int len = cmd.length; - if ((len < 1) || (len > 1024)) - return false; - buf[0] = (byte) (len & 0xff); - buf[1] = (byte) ((len >> 8) & 0xff); - try { - mOut.write(buf, 0, 2); - mOut.write(cmd, 0, len); - } catch (IOException ex) { - Slog.e(TAG, "write error"); - disconnect(); - return false; - } - return true; - } - - private synchronized String transaction(String cmd) { - if (!connect()) { - Slog.e(TAG, "connection failed"); - return "-1"; - } - - if (!writeCommand(cmd)) { - /* - * If installd died and restarted in the background (unlikely but - * possible) we'll fail on the next write (this one). Try to - * reconnect and write the command one more time before giving up. - */ - Slog.e(TAG, "write command failed? reconnect!"); - if (!connect() || !writeCommand(cmd)) { - return "-1"; - } - } - if (LOCAL_DEBUG) { - Slog.i(TAG, "send: '" + cmd + "'"); - } - if (readReply()) { - String s = new String(buf, 0, buflen); - if (LOCAL_DEBUG) { - Slog.i(TAG, "recv: '" + s + "'"); - } - return s; - } else { - if (LOCAL_DEBUG) { - Slog.i(TAG, "fail"); - } - return "-1"; - } - } - - private int execute(String cmd) { - String res = transaction(cmd); - try { - return Integer.parseInt(res); - } catch (NumberFormatException ex) { - return -1; - } - } - public int install(String name, int uid, int gid, String seinfo) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); @@ -209,11 +51,16 @@ public final class Installer extends SystemService { builder.append(gid); builder.append(' '); builder.append(seinfo != null ? seinfo : "!"); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("patchoat"); builder.append(' '); builder.append(apkPath); @@ -224,37 +71,34 @@ public final class Installer extends SystemService { builder.append(pkgName); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) { - StringBuilder builder = new StringBuilder("patchoat"); - builder.append(' '); - builder.append(apkPath); - builder.append(' '); - builder.append(uid); - builder.append(isPublic ? " 1" : " 0"); - builder.append(" *"); // No pkgName arg present - builder.append(' '); - builder.append(instructionSet); - return execute(builder.toString()); + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + + return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet); } public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) { - StringBuilder builder = new StringBuilder("dexopt"); - builder.append(' '); - builder.append(apkPath); - builder.append(' '); - builder.append(uid); - builder.append(isPublic ? " 1" : " 0"); - builder.append(" *"); // No pkgName arg present - builder.append(' '); - builder.append(instructionSet); - return execute(builder.toString()); + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + + return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet); } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("dexopt"); builder.append(' '); builder.append(apkPath); @@ -265,7 +109,7 @@ public final class Installer extends SystemService { builder.append(pkgName); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int idmap(String targetApkPath, String overlayApkPath, int uid) { @@ -276,10 +120,15 @@ public final class Installer extends SystemService { builder.append(overlayApkPath); builder.append(' '); builder.append(uid); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int movedex(String srcPath, String dstPath, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("movedex"); builder.append(' '); builder.append(srcPath); @@ -287,16 +136,21 @@ public final class Installer extends SystemService { builder.append(dstPath); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int rmdex(String codePath, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("rmdex"); builder.append(' '); builder.append(codePath); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int remove(String name, int userId) { @@ -305,7 +159,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int rename(String oldname, String newname) { @@ -314,7 +168,7 @@ public final class Installer extends SystemService { builder.append(oldname); builder.append(' '); builder.append(newname); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int fixUid(String name, int uid, int gid) { @@ -325,7 +179,7 @@ public final class Installer extends SystemService { builder.append(uid); builder.append(' '); builder.append(gid); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int deleteCacheFiles(String name, int userId) { @@ -334,7 +188,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int deleteCodeCacheFiles(String name, int userId) { @@ -343,7 +197,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int createUserData(String name, int uid, int userId, String seinfo) { @@ -356,21 +210,21 @@ public final class Installer extends SystemService { builder.append(userId); builder.append(' '); builder.append(seinfo != null ? seinfo : "!"); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int createUserConfig(int userId) { StringBuilder builder = new StringBuilder("mkuserconfig"); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int removeUserDataDirs(int userId) { StringBuilder builder = new StringBuilder("rmuser"); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int clearUserData(String name, int userId) { @@ -379,11 +233,11 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public boolean ping() { - if (execute("ping") < 0) { + if (mInstaller.execute("ping") < 0) { return false; } else { return true; @@ -391,18 +245,25 @@ public final class Installer extends SystemService { } public int pruneDexCache(String cacheSubDir) { - return execute("prunedexcache " + cacheSubDir); + return mInstaller.execute("prunedexcache " + cacheSubDir); } public int freeCache(long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); builder.append(String.valueOf(freeStorageSize)); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) { + for (String instructionSet : instructionSets) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + } + StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -423,7 +284,7 @@ public final class Installer extends SystemService { // just the primary. builder.append(instructionSets[0]); - String s = transaction(builder.toString()); + String s = mInstaller.transact(builder.toString()); String res[] = s.split(" "); if ((res == null) || (res.length != 5)) { @@ -441,7 +302,7 @@ public final class Installer extends SystemService { } public int moveFiles() { - return execute("movefiles"); + return mInstaller.execute("movefiles"); } /** @@ -467,7 +328,7 @@ public final class Installer extends SystemService { builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public boolean restoreconData(String pkgName, String seinfo, int uid) { @@ -478,6 +339,23 @@ public final class Installer extends SystemService { builder.append(seinfo != null ? seinfo : "!"); builder.append(' '); builder.append(uid); - return (execute(builder.toString()) == 0); + return (mInstaller.execute(builder.toString()) == 0); + } + + /** + * Returns true iff. {@code instructionSet} is a valid instruction set. + */ + private static boolean isValidInstructionSet(String instructionSet) { + if (instructionSet == null) { + return false; + } + + for (String abi : Build.SUPPORTED_ABIS) { + if (instructionSet.equals(VMRuntime.getInstructionSet(abi))) { + return true; + } + } + + return false; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index c7e3fb7..03cb2e9 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_ALL_USERS; import static android.content.pm.PackageManager.INSTALL_FROM_ADB; import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; +import static android.net.TrafficStats.MB_IN_BYTES; import static com.android.internal.util.XmlUtils.readBitmapAttribute; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -36,20 +37,26 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.app.ActivityManager; import android.app.AppOpsManager; +import android.app.PackageDeleteObserver; +import android.app.PackageInstallObserver; import android.content.Context; import android.content.Intent; -import android.content.pm.IPackageDeleteObserver2; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; -import android.content.pm.InstallSessionInfo; -import android.content.pm.InstallSessionParams; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Environment; +import android.os.Environment.UserEnvironment; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; @@ -61,8 +68,10 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; +import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.AtomicFile; @@ -70,13 +79,14 @@ import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; -import com.android.server.pm.PackageInstallerSession.Snapshot; import com.google.android.collect.Sets; import libcore.io.IoUtils; @@ -103,6 +113,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // TODO: remove outstanding sessions when installer package goes away // TODO: notify listeners in other users when package has been installed there + // TODO: purge expired sessions periodically in addition to at reboot /** XML constants used in {@link #mSessionsFile} */ private static final String TAG_SESSIONS = "sessions"; @@ -112,6 +123,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; private static final String ATTR_CREATED_MILLIS = "createdMillis"; private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; + private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_SEALED = "sealed"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; @@ -134,6 +146,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; private final AppOpsManager mAppOps; + private final StorageManager mStorage; private final File mStagingDir; private final HandlerThread mInstallThread; @@ -160,10 +173,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @GuardedBy("mSessions") private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); + /** Sessions allocated to legacy users */ + @GuardedBy("mSessions") + private final SparseBooleanArray mLegacySessions = new SparseBooleanArray(); + private static final FilenameFilter sStageFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return name.startsWith("vmdl") && name.endsWith(".tmp"); + return isStageName(name); } }; @@ -171,6 +188,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mContext = context; mPm = pm; mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mStorage = StorageManager.from(mContext); mStagingDir = stagingDir; @@ -185,13 +203,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { readSessionsLocked(); - // Clean up orphaned staging directories - final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + final ArraySet<File> unclaimed = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); + + // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - stages.remove(session.sessionStageDir); + unclaimed.remove(session.internalStageDir); } - for (File stage : stages) { + + // Clean up orphaned staging directories + for (File stage : unclaimed) { Slog.w(TAG, "Deleting orphan stage " + stage); if (stage.isDirectory()) { FileUtils.deleteContents(stage); @@ -201,22 +222,64 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - public static boolean isStageFile(File file) { - return sStageFilter.accept(null, file.getName()); + public void onSecureContainersAvailable() { + synchronized (mSessions) { + final ArraySet<String> unclaimed = new ArraySet<>(); + for (String cid : PackageHelper.getSecureContainerList()) { + if (isStageName(cid)) { + unclaimed.add(cid); + } + } + + // Ignore stages claimed by active sessions + for (int i = 0; i < mSessions.size(); i++) { + final PackageInstallerSession session = mSessions.valueAt(i); + final String cid = session.externalStageCid; + + if (unclaimed.remove(cid)) { + // Claimed by active session, mount it + PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID); + } + } + + // Clean up orphaned staging containers + for (String cid : unclaimed) { + Slog.w(TAG, "Deleting orphan container " + cid); + PackageHelper.destroySdDir(cid); + } + } + } + + public static boolean isStageName(String name) { + final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); + final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); + final boolean isLegacyContainer = name.startsWith("smdl2tmp"); + return isFile || isContainer || isLegacyContainer; } @Deprecated - public File allocateSessionDir() throws IOException { + public File allocateInternalStageDirLegacy() throws IOException { synchronized (mSessions) { try { final int sessionId = allocateSessionIdLocked(); - return prepareSessionStageDir(sessionId); + mLegacySessions.put(sessionId, true); + return prepareInternalStageDir(sessionId); } catch (IllegalStateException e) { throw new IOException(e); } } } + @Deprecated + public String allocateExternalStageCidLegacy() { + synchronized (mSessions) { + final int sessionId = allocateSessionIdLocked(); + mLegacySessions.put(sessionId, true); + return "smdl" + sessionId + ".tmp"; + } + } + private void readSessionsLocked() { if (LOGD) Slog.v(TAG, "readSessionsLocked()"); @@ -241,9 +304,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Slog.w(TAG, "Abandoning old session first created at " + session.createdMillis); valid = false; - } else if (!session.sessionStageDir.exists()) { - Slog.w(TAG, "Abandoning session with missing stage " - + session.sessionStageDir); + } else if (session.internalStageDir != null + && !session.internalStageDir.exists()) { + Slog.w(TAG, "Abandoning internal session with missing stage " + + session.internalStageDir); valid = false; } else { valid = true; @@ -276,11 +340,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final int userId = readIntAttribute(in, ATTR_USER_ID); final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); - final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR)); + final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); + final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; + final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); - final InstallSessionParams params = new InstallSessionParams( - InstallSessionParams.MODE_INVALID); + final SessionParams params = new SessionParams( + SessionParams.MODE_INVALID); params.mode = readIntAttribute(in, ATTR_MODE); params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); @@ -292,9 +358,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI); params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE); - return new PackageInstallerSession(mInternalCallback, mPm, mInstallThread.getLooper(), - sessionId, userId, installerPackageName, params, createdMillis, sessionStageDir, - sealed); + return new PackageInstallerSession(mInternalCallback, mContext, mPm, + mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, + createdMillis, stageDir, stageCid, sealed); } private void writeSessionsLocked() { @@ -326,8 +392,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session) throws IOException { - final InstallSessionParams params = session.params; - final Snapshot snapshot = session.snapshot(); + final SessionParams params = session.params; out.startTag(null, TAG_SESSION); @@ -336,9 +401,14 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, session.installerPackageName); writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); - writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, - session.sessionStageDir.getAbsolutePath()); - writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed); + if (session.internalStageDir != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, + session.internalStageDir.getAbsolutePath()); + } + if (session.externalStageCid != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.externalStageCid); + } + writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); writeIntAttribute(out, ATTR_MODE, params.mode); writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); @@ -366,7 +436,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public int createSession(InstallSessionParams params, String installerPackageName, int userId) { + public int createSession(SessionParams params, String installerPackageName, int userId) { + try { + return createSessionInternal(params, installerPackageName, userId); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } + } + + private int createSessionInternal(SessionParams params, String installerPackageName, int userId) + throws IOException { final int callingUid = Binder.getCallingUid(); mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); @@ -388,14 +467,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags |= INSTALL_REPLACE_EXISTING; } - switch (params.mode) { - case InstallSessionParams.MODE_FULL_INSTALL: - case InstallSessionParams.MODE_INHERIT_EXISTING: - break; - default: - throw new IllegalArgumentException("Params must have valid mode set"); - } - // Defensively resize giant app icons if (params.appIcon != null) { final ActivityManager am = (ActivityManager) mContext.getSystemService( @@ -408,13 +479,42 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // Sanity check that install could fit - if (params.sizeBytes > 0) { - try { - mPm.freeStorage(params.sizeBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + // Figure out where we're going to be staging session data + final boolean stageInternal; + + if (params.mode == SessionParams.MODE_FULL_INSTALL) { + // Brand new install, use best resolved location. This also verifies + // that target has enough free space for the install. + final int resolved = PackageHelper.resolveInstallLocation(mContext, + params.appPackageName, params.installLocation, params.sizeBytes, + params.installFlags); + if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { + stageInternal = true; + } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { + stageInternal = false; + } else { + throw new IOException("No storage with enough free space; res=" + resolved); + } + + } else if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // We always stage inheriting sessions on internal storage first, + // since we don't want to grow containers until we're sure that + // everything looks legit. + stageInternal = true; + checkInternalStorage(params.sizeBytes); + + // If we have a good hunch we'll end up on external storage, verify + // free space there too. + final ApplicationInfo info = mPm.getApplicationInfo(params.appPackageName, 0, + userId); + if (info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + checkExternalStorage(params.sizeBytes); + + throw new UnsupportedOperationException("TODO: finish fleshing out ASEC support"); } + + } else { + throw new IllegalArgumentException("Invalid install mode: " + params.mode); } final int sessionId; @@ -432,14 +532,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub { "Too many historical sessions for UID " + callingUid); } + final long createdMillis = System.currentTimeMillis(); sessionId = allocateSessionIdLocked(); - final long createdMillis = System.currentTimeMillis(); - final File sessionStageDir = prepareSessionStageDir(sessionId); + // We're staging to exactly one location + File stageDir = null; + String stageCid = null; + if (stageInternal) { + stageDir = prepareInternalStageDir(sessionId); + } else { + stageCid = prepareExternalStageCid(sessionId, params.sizeBytes); + } - session = new PackageInstallerSession(mInternalCallback, mPm, + session = new PackageInstallerSession(mInternalCallback, mContext, mPm, mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, - createdMillis, sessionStageDir, false); + createdMillis, stageDir, stageCid, false); mSessions.put(sessionId, session); } @@ -448,6 +555,30 @@ public class PackageInstallerService extends IPackageInstaller.Stub { return sessionId; } + private void checkInternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = Environment.getDataDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + mPm.freeStorage(targetBytes); + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough internal space to write " + sizeBytes + " bytes"); + } + } + + private void checkExternalStorage(long sizeBytes) throws IOException { + if (sizeBytes <= 0) return; + + final File target = new UserEnvironment(UserHandle.USER_OWNER) + .getExternalStorageDirectory(); + final long targetBytes = sizeBytes + mStorage.getStorageLowBytes(target); + + if (target.getUsableSpace() < targetBytes) { + throw new IOException("Not enough external space to write " + sizeBytes + " bytes"); + } + } + @Override public IPackageInstallerSession openSession(int sessionId) { synchronized (mSessions) { @@ -458,9 +589,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (!isCallingUidOwner(session)) { throw new SecurityException("Caller has no access to session " + sessionId); } - if (session.openCount.getAndIncrement() == 0) { - mCallbacks.notifySessionOpened(sessionId, session.userId); - } + session.open(); return session; } } @@ -470,7 +599,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { int sessionId; do { sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; - if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null) { + if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null + && !mLegacySessions.get(sessionId, false)) { return sessionId; } } while (n++ < 32); @@ -478,11 +608,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File prepareSessionStageDir(int sessionId) { + private File prepareInternalStageDir(int sessionId) throws IOException { final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); if (file.exists()) { - throw new IllegalStateException("Session dir already exists: " + file); + throw new IOException("Session dir already exists: " + file); } try { @@ -490,18 +620,36 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Os.chmod(file.getAbsolutePath(), 0755); } catch (ErrnoException e) { // This purposefully throws if directory already exists - throw new IllegalStateException("Failed to prepare session dir", e); + throw new IOException("Failed to prepare session dir", e); } if (!SELinux.restorecon(file)) { - throw new IllegalStateException("Failed to restorecon session dir"); + throw new IOException("Failed to restorecon session dir"); } return file; } + private String prepareExternalStageCid(int sessionId, long sizeBytes) throws IOException { + if (sizeBytes <= 0) { + throw new IOException("Session must provide valid size for ASEC"); + } + + final String cid = "smdl" + sessionId + ".tmp"; + + // Round up to nearest MB, plus another MB for filesystem overhead + final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; + + if (PackageHelper.createSdDir(sizeMb, cid, PackageManagerService.getEncryptKey(), + Process.SYSTEM_UID, true) == null) { + throw new IOException("Failed to create ASEC"); + } + + return cid; + } + @Override - public InstallSessionInfo getSessionInfo(int sessionId) { + public SessionInfo getSessionInfo(int sessionId) { synchronized (mSessions) { final PackageInstallerSession session = mSessions.get(sessionId); if (!isCallingUidOwner(session)) { @@ -512,11 +660,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public List<InstallSessionInfo> getAllSessions(int userId) { + public List<SessionInfo> getAllSessions(int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions"); enforceCallerCanReadSessions(); - final List<InstallSessionInfo> result = new ArrayList<>(); + final List<SessionInfo> result = new ArrayList<>(); synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); @@ -529,11 +677,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) { + public List<SessionInfo> getMySessions(String installerPackageName, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions"); mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName); - final List<InstallSessionInfo> result = new ArrayList<>(); + final List<SessionInfo> result = new ArrayList<>(); synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); @@ -547,37 +695,26 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public void uninstall(String packageName, int flags, IPackageDeleteObserver2 observer, - int userId) { + public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) { mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall"); + final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, + statusReceiver, packageName); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! - mPm.deletePackage(packageName, observer, userId, flags); + mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); } else { // Take a short detour to confirm with user final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); intent.setData(Uri.fromParts("package", packageName, null)); - intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder()); - try { - observer.onUserActionRequired(intent); - } catch (RemoteException ignored) { - } + intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder()); + adapter.onUserActionRequired(intent); } } @Override - public void uninstallSplit(String basePackageName, String overlayName, int flags, - IPackageDeleteObserver2 observer, int userId) { - mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit"); - - // TODO: flesh out once PM has split support - throw new UnsupportedOperationException(); - } - - @Override public void setPermissionsResult(int sessionId, boolean accepted) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG); @@ -636,6 +773,95 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + static class PackageDeleteObserverAdapter extends PackageDeleteObserver { + private final Context mContext; + private final IntentSender mTarget; + private final String mPackageName; + + public PackageDeleteObserverAdapter(Context context, IntentSender target, + String packageName) { + mContext = context; + mTarget = target; + mPackageName = packageName; + } + + @Override + public void onUserActionRequired(Intent intent) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_PENDING_USER_ACTION); + fillIn.putExtra(Intent.EXTRA_INTENT, intent); + try { + mTarget.sendIntent(mContext, 0, fillIn, null, null); + } catch (SendIntentException ignored) { + } + } + + @Override + public void onPackageDeleted(String basePackageName, int returnCode, String msg) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS, + PackageManager.deleteStatusToPublicStatus(returnCode)); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, + PackageManager.deleteStatusToString(returnCode, msg)); + fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); + try { + mTarget.sendIntent(mContext, 0, fillIn, null, null); + } catch (SendIntentException ignored) { + } + } + } + + static class PackageInstallObserverAdapter extends PackageInstallObserver { + private final Context mContext; + private final IntentSender mTarget; + private final int mSessionId; + + public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId) { + mContext = context; + mTarget = target; + mSessionId = sessionId; + } + + @Override + public void onUserActionRequired(Intent intent) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_PENDING_USER_ACTION); + fillIn.putExtra(Intent.EXTRA_INTENT, intent); + try { + mTarget.sendIntent(mContext, 0, fillIn, null, null); + } catch (SendIntentException ignored) { + } + } + + @Override + public void onPackageInstalled(String basePackageName, int returnCode, String msg, + Bundle extras) { + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS, + PackageManager.installStatusToPublicStatus(returnCode)); + fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, + PackageManager.installStatusToString(returnCode, msg)); + fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); + if (extras != null) { + final String existing = extras.getString( + PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); + if (!TextUtils.isEmpty(existing)) { + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, existing); + } + } + try { + mTarget.sendIntent(mContext, 0, fillIn, null, null); + } catch (SendIntentException ignored) { + } + } + } + private static class Callbacks extends Handler { private static final int MSG_SESSION_CREATED = 1; private static final int MSG_SESSION_OPENED = 2; @@ -742,6 +968,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } pw.println(); pw.decreaseIndent(); + + pw.println("Legacy install sessions:"); + pw.increaseIndent(); + pw.println(mLegacySessions.toString()); + pw.decreaseIndent(); } } @@ -750,6 +981,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); } + public void onSessionOpened(PackageInstallerSession session) { + mCallbacks.notifySessionOpened(session.sessionId, session.userId); + } + public void onSessionClosed(PackageInstallerSession session) { mCallbacks.notifySessionClosed(session.sessionId, session.userId); } @@ -762,5 +997,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } writeSessionsAsync(); } + + public void onSessionSealed(PackageInstallerSession session) { + // It's very important that we block until we've recorded the + // session as being sealed, since we never want to allow mutation + // after sealing. + writeSessionsLocked(); + } } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index a3184f0..38a2016 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; +import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED; @@ -25,13 +26,15 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; +import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; -import android.content.pm.InstallSessionInfo; -import android.content.pm.InstallSessionParams; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ApkLite; @@ -56,9 +59,11 @@ import android.util.MathUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; import libcore.io.Libcore; @@ -76,25 +81,37 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE - // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL // TODO: treat INHERIT_EXISTING as installExistingPackage() private final PackageInstallerService.InternalCallback mCallback; + private final Context mContext; private final PackageManagerService mPm; private final Handler mHandler; final int sessionId; final int userId; final String installerPackageName; - final InstallSessionParams params; + final SessionParams params; final long createdMillis; - final File sessionStageDir; + + /** Internal location where staged data is written. */ + final File internalStageDir; + /** External container where staged data is written. */ + final String externalStageCid; + + /** + * When a {@link SessionParams#MODE_INHERIT_EXISTING} session is installed + * into an ASEC, this is the container where the stage is combined with the + * existing install. + */ + // TODO: persist this cid once we start splicing + String combinedCid; /** Note that UID is not persisted; it's always derived at runtime. */ final int installerUid; - AtomicInteger openCount = new AtomicInteger(); + private final AtomicInteger mOpenCount = new AtomicInteger(); private final Object mLock = new Object(); @@ -115,6 +132,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private int mFinalStatus; private String mFinalMessage; + @GuardedBy("mLock") + private File mResolvedStageDir; + /** * Path to the resolved base APK for this session, which may point at an APK * inside the session (when the session defines the base), or it may point @@ -159,10 +179,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; public PackageInstallerSession(PackageInstallerService.InternalCallback callback, - PackageManagerService pm, Looper looper, int sessionId, int userId, - String installerPackageName, InstallSessionParams params, long createdMillis, - File sessionStageDir, boolean sealed) { + Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, + String installerPackageName, SessionParams params, long createdMillis, + File internalStageDir, String externalStageCid, boolean sealed) { mCallback = callback; + mContext = context; mPm = pm; mHandler = new Handler(looper, mHandlerCallback); @@ -171,7 +192,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.installerPackageName = installerPackageName; this.params = params; this.createdMillis = createdMillis; - this.sessionStageDir = sessionStageDir; + this.internalStageDir = internalStageDir; + this.externalStageCid = externalStageCid; + + if ((internalStageDir == null) == (externalStageCid == null)) { + throw new IllegalArgumentException( + "Exactly one of internal or external stage must be set"); + } mSealed = sealed; @@ -188,25 +215,31 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { computeProgressLocked(); } - public InstallSessionInfo generateInfo() { - final InstallSessionInfo info = new InstallSessionInfo(); - - info.sessionId = sessionId; - info.installerPackageName = installerPackageName; - info.resolvedBaseCodePath = mResolvedBaseCodePath; - info.progress = mProgress; - info.sealed = mSealed; - info.open = openCount.get() > 0; - - info.mode = params.mode; - info.sizeBytes = params.sizeBytes; - info.appPackageName = params.appPackageName; - info.appIcon = params.appIcon; - info.appLabel = params.appLabel; - + public SessionInfo generateInfo() { + final SessionInfo info = new SessionInfo(); + synchronized (mLock) { + info.sessionId = sessionId; + info.installerPackageName = installerPackageName; + info.resolvedBaseCodePath = mResolvedBaseCodePath; + info.progress = mProgress; + info.sealed = mSealed; + info.open = mOpenCount.get() > 0; + + info.mode = params.mode; + info.sizeBytes = params.sizeBytes; + info.appPackageName = params.appPackageName; + info.appIcon = params.appIcon; + info.appLabel = params.appLabel; + } return info; } + public boolean isSealed() { + synchronized (mLock) { + return mSealed; + } + } + private void assertNotSealed(String cookie) { synchronized (mLock) { if (mSealed) { @@ -215,6 +248,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Resolve the actual location where staged data should be written. This + * might point at an ASEC mount point, which is why we delay path resolution + * until someone actively works with the session. + */ + private File getStageDir() throws IOException { + synchronized (mLock) { + if (mResolvedStageDir == null) { + if (internalStageDir != null) { + mResolvedStageDir = internalStageDir; + } else { + final String path = PackageHelper.getSdDir(externalStageCid); + if (path != null) { + mResolvedStageDir = new File(path); + } else { + throw new IOException( + "Failed to resolve container path for " + externalStageCid); + } + } + } + return mResolvedStageDir; + } + } + @Override public void setClientProgress(float progress) { synchronized (mLock) { @@ -246,9 +303,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public String[] list() { - assertNotSealed("list"); - return sessionStageDir.list(); + public String[] getNames() { + assertNotSealed("getNames"); + try { + return getStageDir().list(); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } } @Override @@ -262,8 +323,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes) throws IOException { - // TODO: relay over to DCS when installing to ASEC - // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. @@ -280,7 +339,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_CREAT | O_WRONLY, 0644); @@ -326,7 +385,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.isValidExtFilename(name)) { throw new IllegalArgumentException("Invalid name: " + name); } - final File target = new File(sessionStageDir, name); + final File target = new File(getStageDir(), name); final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0); return new ParcelFileDescriptor(targetFd); @@ -337,9 +396,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override - public void commit(IPackageInstallObserver2 observer) { - Preconditions.checkNotNull(observer); - mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget(); + public void commit(IntentSender statusReceiver) { + Preconditions.checkNotNull(statusReceiver); + + final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, + statusReceiver, sessionId); + mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); } private void commitLocked() throws PackageManagerException { @@ -357,14 +419,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mSealed = true; - // TODO: persist disabled mutations before going forward, since - // beyond this point we may have hardlinks to the valid install + // Persist the fact that we've sealed ourselves to prevent mutations + // of any hard links we create below. + mCallback.onSessionSealed(this); + } + + final File stageDir; + try { + stageDir = getStageDir(); + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, + "Failed to resolve stage dir", e); } // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. - validateInstallLocked(); + validateInstallLocked(stageDir); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); @@ -385,8 +456,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Inherit any packages and native libraries from existing install that // haven't been overridden. - if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) { - spliceExistingFilesIntoStage(); + if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { + // TODO: implement splicing into existing ASEC + spliceExistingFilesIntoStage(stageDir); } // TODO: surface more granular state from dexopt @@ -396,7 +468,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // We've reached point of no return; call into PMS to install the stage. // Regardless of success or failure we always destroy session. - final IPackageInstallObserver2 remoteObserver = mRemoteObserver; final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { @Override public void onUserActionRequired(Intent intent) { @@ -411,8 +482,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; - mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, - installerPackageName, installerUid, new UserHandle(userId)); + mPm.installStage(mPackageName, this.internalStageDir, this.externalStageCid, localObserver, + params, installerPackageName, installerUid, new UserHandle(userId)); } /** @@ -420,25 +491,32 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * consistent package name, version code, and signing certificates. * <p> * Renames package files in stage to match split names defined inside. + * <p> + * Note that upgrade compatibility is still performed by + * {@link PackageManagerService}. */ - private void validateInstallLocked() throws PackageManagerException { + private void validateInstallLocked(File stageDir) throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; mResolvedBaseCodePath = null; - final File[] files = sessionStageDir.listFiles(); + final File[] files = stageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); } - final ArraySet<String> seenSplits = new ArraySet<>(); - // Verify that all staged packages are internally consistent + final ArraySet<String> seenSplits = new ArraySet<>(); for (File file : files) { + + // Installers can't stage directories, so it's fine to ignore + // entries like "lost+found". + if (file.isDirectory()) continue; + final ApkLite info; try { - info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES); + info = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Failed to parse " + file + ": " + e); @@ -473,7 +551,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Invalid filename: " + targetName); } - final File targetFile = new File(sessionStageDir, targetName); + final File targetFile = new File(stageDir, targetName); if (!file.equals(targetFile)) { file.renameTo(targetFile); } @@ -484,11 +562,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - // TODO: shift package signature verification to installer; we're - // currently relying on PMS to do this. - // TODO: teach about compatible upgrade keysets. - - if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) { + if (params.mode == SessionParams.MODE_FULL_INSTALL) { // Full installs must include a base package if (!seenSplits.contains(null)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, @@ -511,7 +585,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final ApkLite info; try { info = PackageParser.parseApkLite(new File(app.getBaseCodePath()), - PackageParser.PARSE_GET_SIGNATURES); + PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Failed to parse existing base " + app.getBaseCodePath() + ": " + e); @@ -543,7 +617,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Application is already installed; splice existing files that haven't been * overridden into our stage. */ - private void spliceExistingFilesIntoStage() throws PackageManagerException { + private void spliceExistingFilesIntoStage(File stageDir) throws PackageManagerException { final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); int n = 0; @@ -552,7 +626,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { for (File oldFile : oldFiles) { if (!PackageParser.isApkFile(oldFile)) continue; - final File newFile = new File(sessionStageDir, oldFile.getName()); + final File newFile = new File(stageDir, oldFile.getName()); try { Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath()); n++; @@ -581,9 +655,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + public void open() { + if (mOpenCount.getAndIncrement() == 0) { + mCallback.onSessionOpened(this); + } + } + @Override public void close() { - if (openCount.decrementAndGet() == 0) { + if (mOpenCount.decrementAndGet() == 0) { mCallback.onSessionClosed(this); } } @@ -614,11 +694,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mSealed = true; mDestroyed = true; } - FileUtils.deleteContents(sessionStageDir); - sessionStageDir.delete(); + if (internalStageDir != null) { + FileUtils.deleteContents(internalStageDir); + internalStageDir.delete(); + } + if (externalStageCid != null) { + PackageHelper.destroySdDir(externalStageCid); + } } void dump(IndentingPrintWriter pw) { + synchronized (mLock) { + dumpLocked(pw); + } + } + + private void dumpLocked(IndentingPrintWriter pw) { pw.println("Session " + sessionId + ":"); pw.increaseIndent(); @@ -626,7 +717,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("installerPackageName", installerPackageName); pw.printPair("installerUid", installerUid); pw.printPair("createdMillis", createdMillis); - pw.printPair("sessionStageDir", sessionStageDir); + pw.printPair("internalStageDir", internalStageDir); + pw.printPair("externalStageCid", externalStageCid); pw.println(); params.dump(pw); @@ -643,18 +735,4 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } - - Snapshot snapshot() { - return new Snapshot(this); - } - - static class Snapshot { - final float clientProgress; - final boolean sealed; - - public Snapshot(PackageInstallerSession session) { - clientProgress = session.mClientProgress; - sealed = session.mSealed; - } - } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 89bd1d4..aa49b27 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -48,11 +48,6 @@ import static android.os.Process.PACKAGE_INFO_GID; import static android.os.Process.SYSTEM_UID; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; -import static android.system.OsConstants.S_IRGRP; -import static android.system.OsConstants.S_IROTH; -import static android.system.OsConstants.S_IRWXU; -import static android.system.OsConstants.S_IXGRP; -import static android.system.OsConstants.S_IXOTH; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_USER_OWNER; import static com.android.internal.util.ArrayUtils.appendInt; @@ -70,7 +65,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.IntentResolver; import com.android.server.LocalServices; @@ -107,12 +101,12 @@ import android.content.pm.IPackageInstaller; import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; -import android.content.pm.InstallSessionParams; import android.content.pm.InstrumentationInfo; import android.content.pm.ManifestDigest; import android.content.pm.PackageCleanItem; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageParser.ActivityIntentInfo; @@ -140,6 +134,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Environment.UserEnvironment; +import android.os.storage.StorageManager; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -211,6 +206,7 @@ import dalvik.system.StaleDexCacheError; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.util.EmptyArray; /** * Keep track of all those .apks everywhere. @@ -253,10 +249,6 @@ public class PackageManagerService extends IPackageManager.Stub { // package apks to install directory. private static final String INSTALL_PACKAGE_SUFFIX = "-"; - // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate - // that the cpuAbiOverride must be clear. - private static final String CLEAR_ABI_OVERRIDE = "-"; - static final int SCAN_MONITOR = 1<<0; static final int SCAN_NO_DEX = 1<<1; static final int SCAN_FORCE_DEX = 1<<2; @@ -311,8 +303,6 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - static final String mTempContainerPrefix = "smdl2tmp"; - private static String sPreferredInstructionSet; final ServiceThread mHandlerThread; @@ -1394,16 +1384,27 @@ public class PackageManagerService extends IPackageManager.Stub { * list of process files because dexopt will have been run * if necessary during zygote startup. */ - String bootClassPath = System.getProperty("java.boot.class.path"); + final String bootClassPath = System.getenv("BOOTCLASSPATH"); + final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); + if (bootClassPath != null) { - String[] paths = splitString(bootClassPath, ':'); - for (int i=0; i<paths.length; i++) { - alreadyDexOpted.add(paths[i]); + String[] bootClassPathElements = splitString(bootClassPath, ':'); + for (String element : bootClassPathElements) { + alreadyDexOpted.add(element); } } else { Slog.w(TAG, "No BOOTCLASSPATH found!"); } + if (systemServerClassPath != null) { + String[] systemServerClassPathElements = splitString(systemServerClassPath, ':'); + for (String element : systemServerClassPathElements) { + alreadyDexOpted.add(element); + } + } else { + Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!"); + } + boolean didDexOptLibraryOrTool = false; final List<String> allInstructionSets = getAllInstructionSets(); @@ -2920,7 +2921,8 @@ public class PackageManagerService extends IPackageManager.Stub { findPreferredActivity(intent, resolvedType, flags, query, 0, false, true, false, userId); // Add the new activity as the last chosen for this filter - addPreferredActivityInternal(filter, match, null, activity, false, userId); + addPreferredActivityInternal(filter, match, null, activity, false, userId, + "Setting last chosen"); } @Override @@ -3192,24 +3194,22 @@ public class PackageManagerService extends IPackageManager.Stub { if (matches.get(i).getTargetUserId() == targetUserId) return true; } } - ArrayList<String> packageNames = null; SparseArray<ArrayList<String>> fromSource = mSettings.mCrossProfilePackageInfo.get(sourceUserId); if (fromSource != null) { packageNames = fromSource.get(targetUserId); - } - if (packageNames != null && packageNames.contains(intent.getPackage())) { - return true; - } - // We need the package name, so we try to resolve with the loosest flags possible - List<ResolveInfo> resolveInfos = mActivities.queryIntent( - intent, resolvedType, PackageManager.GET_UNINSTALLED_PACKAGES, targetUserId); - int count = resolveInfos.size(); - for (int i = 0; i < count; i++) { - ResolveInfo resolveInfo = resolveInfos.get(i); - if (packageNames.contains(resolveInfo.activityInfo.packageName)) { - return true; + if (packageNames != null) { + // We need the package name, so we try to resolve with the loosest flags possible + List<ResolveInfo> resolveInfos = mActivities.queryIntent(intent, resolvedType, + PackageManager.GET_UNINSTALLED_PACKAGES, targetUserId); + int count = resolveInfos.size(); + for (int i = 0; i < count; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + if (packageNames.contains(resolveInfo.activityInfo.packageName)) { + return true; + } + } } } return false; @@ -3282,6 +3282,7 @@ public class PackageManagerService extends IPackageManager.Stub { intent, resolvedType, flags, userId); if (resolveInfo != null) { result.add(resolveInfo); + Collections.sort(result, mResolvePrioritySorter); } return result; } @@ -4091,21 +4092,24 @@ public class PackageManagerService extends IPackageManager.Stub { for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) - && !PackageInstallerService.isStageFile(file); + && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { - // Ignore entries which are not apk's + // Ignore entries which are not packages continue; } try { - scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null); + scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, + scanMode, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); - // Don't mess around with apps in system partition. + // Delete invalid userdata apps if ((flags & PackageParser.PARSE_IS_SYSTEM) == 0 && e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { - // Delete the apk - Slog.w(TAG, "Cleaning up failed install of " + file); + Slog.w(TAG, "Deleting invalid package at " + file); + if (file.isDirectory()) { + FileUtils.deleteContents(file); + } file.delete(); } } @@ -5008,7 +5012,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static String deriveAbiOverride(String abiOverride, PackageSetting settings) { String cpuAbiOverride = null; - if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) { + if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) { cpuAbiOverride = null; } else if (abiOverride != null) { cpuAbiOverride = abiOverride; @@ -5512,7 +5516,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Warn if we've set an abiOverride for multi-lib packages.. // By definition, we need to copy both 32 and 64 bit libraries for // such packages. - if (pkg.cpuAbiOverride != null && !CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) { + if (pkg.cpuAbiOverride != null + && !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) { Slog.w(TAG, "Ignoring abiOverride for multi arch application."); } @@ -5522,7 +5527,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); } else { - abi32 = copyNativeLibrariesForInternalApp(handle, + abi32 = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, useIsaSpecificSubdirs); } } @@ -5534,7 +5539,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); } else { - abi64 = copyNativeLibrariesForInternalApp(handle, + abi64 = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, useIsaSpecificSubdirs); } } @@ -5573,8 +5578,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (isAsec) { copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList); } else { - copyRet = copyNativeLibrariesForInternalApp(handle, nativeLibraryRoot, abiList, - useIsaSpecificSubdirs); + copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, + nativeLibraryRoot, abiList, useIsaSpecificSubdirs); } if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { @@ -6457,58 +6462,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private static void createNativeLibrarySubdir(File path) throws IOException { - if (!path.isDirectory()) { - path.delete(); - - if (!path.mkdir()) { - throw new IOException("Cannot create " + path.getPath()); - } - - try { - Os.chmod(path.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); - } catch (ErrnoException e) { - throw new IOException("Cannot chmod native library directory " - + path.getPath(), e); - } - } else if (!SELinux.restorecon(path)) { - throw new IOException("Cannot set SELinux context for " + path.getPath()); - } - } - - private static int copyNativeLibrariesForInternalApp(NativeLibraryHelper.Handle handle, - final File nativeLibraryRoot, String[] abiList, boolean useIsaSubdir) throws IOException { - createNativeLibrarySubdir(nativeLibraryRoot); - - /* - * If this is an internal application or our nativeLibraryPath points to - * the app-lib directory, unpack the libraries if necessary. - */ - int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); - if (abi >= 0) { - /* - * If we have a matching instruction set, construct a subdir under the native - * library root that corresponds to this instruction set. - */ - final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]); - final File subDir; - if (useIsaSubdir) { - final File isaSubdir = new File(nativeLibraryRoot, instructionSet); - createNativeLibrarySubdir(isaSubdir); - subDir = isaSubdir; - } else { - subDir = nativeLibraryRoot; - } - - int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]); - if (copyRet != PackageManager.INSTALL_SUCCEEDED) { - return copyRet; - } - } - - return abi; - } - private void killApplication(String pkgName, int appId, String reason) { // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older @@ -7829,19 +7782,19 @@ public class PackageManagerService extends IPackageManager.Stub { verificationParams.setInstallerUid(uid); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(originFile, false, observer, filteredFlags, + msg.obj = new InstallParams(originFile, null, false, observer, filteredFlags, installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); } - void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer, - InstallSessionParams params, String installerPackageName, int installerUid, - UserHandle user) { + void installStage(String packageName, File stagedDir, String stagedCid, + IPackageInstallObserver2 observer, PackageInstaller.SessionParams params, + String installerPackageName, int installerUid, UserHandle user) { final VerificationParams verifParams = new VerificationParams(null, params.originatingUri, params.referrerUri, installerUid, null); final Message msg = mHandler.obtainMessage(INIT_COPY); - msg.obj = new InstallParams(stageDir, true, observer, params.installFlags, + msg.obj = new InstallParams(stagedDir, stagedCid, true, observer, params.installFlags, installerPackageName, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } @@ -8541,10 +8494,12 @@ public class PackageManagerService extends IPackageManager.Stub { * file, or a cluster directory. This location may be untrusted. */ final File originFile; + final String originCid; /** - * Flag indicating that {@link #originFile} has already been staged, - * meaning downstream users don't need to defensively copy the contents. + * Flag indicating that {@link #originFile} or {@link #originCid} has + * already been staged, meaning downstream users don't need to + * defensively copy the contents. */ boolean originStaged; @@ -8557,11 +8512,12 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageAbiOverride; boolean multiArch; - InstallParams(File originFile, boolean originStaged, IPackageInstallObserver2 observer, - int flags, String installerPackageName, VerificationParams verificationParams, - UserHandle user, String packageAbiOverride) { + InstallParams(File originFile, String originCid, boolean originStaged, + IPackageInstallObserver2 observer, int flags, String installerPackageName, + VerificationParams verificationParams, UserHandle user, String packageAbiOverride) { super(user); - this.originFile = Preconditions.checkNotNull(originFile); + this.originFile = originFile; + this.originCid = originCid; this.originStaged = originStaged; this.observer = observer; this.flags = flags; @@ -8572,9 +8528,8 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public String toString() { - return "InstallParams{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + originFile + "}"; + return "InstallParams{" + Integer.toHexString(System.identityHashCode(this)) + + " file=" + originFile + " cid=" + originCid + "}"; } public ManifestDigest getManifestDigest() { @@ -8643,15 +8598,6 @@ public class PackageManagerService extends IPackageManager.Stub { return pkgLite.recommendedInstallLocation; } - private long getMemoryLowThreshold() { - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - return 0L; - } - return dsm.getMemoryLowThreshold(); - } - /* * Invoke remote method to get package information and install * location values. Override install location based on default @@ -8660,6 +8606,20 @@ public class PackageManagerService extends IPackageManager.Stub { */ public void handleStartCopy() throws RemoteException { int ret = PackageManager.INSTALL_SUCCEEDED; + + // If we're already staged, we've firmly committed to an install location + if (originStaged) { + if (originFile != null) { + flags |= PackageManager.INSTALL_INTERNAL; + flags &= ~PackageManager.INSTALL_EXTERNAL; + } else if (originCid != null) { + flags |= PackageManager.INSTALL_EXTERNAL; + flags &= ~PackageManager.INSTALL_INTERNAL; + } else { + throw new IllegalStateException("Invalid stage location"); + } + } + final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0; PackageInfoLite pkgLite = null; @@ -8669,14 +8629,9 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Conflicting flags specified for installing on both internal and external"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { - final long lowThreshold = getMemoryLowThreshold(); - if (lowThreshold == 0L) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - } - // Remote call to find out default install location final String originPath = originFile.getAbsolutePath(); - pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, lowThreshold, + pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, packageAbiOverride); // Keep track of whether this package is a multiArch package until // we perform a full scan of it. We need to do this because we might @@ -8688,14 +8643,21 @@ public class PackageManagerService extends IPackageManager.Stub { * If we have too little free space, try to free cache * before giving up. */ - if (pkgLite.recommendedInstallLocation + if (!originStaged && pkgLite.recommendedInstallLocation == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { - final long size = mContainerService.calculateInstalledSize( + // TODO: focus freeing disk space on the target device + final StorageManager storage = StorageManager.from(mContext); + final long lowThreshold = storage.getStorageLowBytes( + Environment.getDataDirectory()); + + final long sizeBytes = mContainerService.calculateInstalledSize( originPath, isForwardLocked(), packageAbiOverride); - if (mInstaller.freeCache(size + lowThreshold) >= 0) { + + if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) { pkgLite = mContainerService.getMinimalPackageInfo(originPath, flags, - lowThreshold, packageAbiOverride); + packageAbiOverride); } + /* * The cache free must have deleted the file we * downloaded to install. @@ -9225,24 +9187,11 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - final long lowThreshold; + final long sizeBytes = imcs.calculateInstalledSize(originFile.getAbsolutePath(), + isFwdLocked(), abiOverride); - final DeviceStorageMonitorInternal - dsm = LocalServices.getService(DeviceStorageMonitorInternal.class); - if (dsm == null) { - Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); - lowThreshold = 0L; - } else { - if (dsm.isMemoryLow()) { - Log.w(TAG, "Memory is reported as being too low; aborting package install"); - return false; - } - - lowThreshold = dsm.getMemoryLowThreshold(); - } - - return imcs.checkInternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), - lowThreshold); + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(Environment.getDataDirectory())); } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { @@ -9254,7 +9203,7 @@ public class PackageManagerService extends IPackageManager.Stub { resourceFile = originFile; } else { try { - final File tempDir = mInstallerService.allocateSessionDir(); + final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); codeFile = tempDir; resourceFile = tempDir; } catch (IOException e) { @@ -9291,49 +9240,11 @@ public class PackageManagerService extends IPackageManager.Stub { NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); - if (multiArch) { - // Warn if we've set an abiOverride for multi-lib packages.. - // By definition, we need to copy both 32 and 64 bit libraries for - // such packages. - if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) { - Slog.w(TAG, "Ignoring abiOverride for multi arch application."); - } - - int copyRet = PackageManager.NO_NATIVE_LIBRARIES; - if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, - Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */); - maybeThrowExceptionForMultiArchCopy("Failure copying 32 bit native libraries", copyRet); - } - - if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, - Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */); - maybeThrowExceptionForMultiArchCopy("Failure copying 64 bit native libraries", copyRet); - } - } else { - final String cpuAbiOverride = deriveAbiOverride(this.abiOverride, null /* package setting */); - String[] abiList = (cpuAbiOverride != null) ? - new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; - - if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null && - NativeLibraryHelper.hasRenderscriptBitcode(handle)) { - abiList = Build.SUPPORTED_32_BIT_ABIS; - } - - int copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot, abiList, - true /* use isa specific subdirs */); - if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { - Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]"); - return copyRet; - } - } + ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libraryRoot, + abiOverride, multiArch); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; - } catch (PackageManagerException pme) { - Slog.e(TAG, "Copying native libraries failed", pme); - ret = pme.error; } finally { IoUtils.closeQuietly(handle); } @@ -9508,8 +9419,6 @@ public class PackageManagerService extends IPackageManager.Stub { * renaming logic. */ class AsecInstallArgs extends InstallArgs { - // TODO: teach about handling cluster directories - static final String RES_FILE_NAME = "pkg.apk"; static final String PUBLIC_RES_FILE_NAME = "res.zip"; @@ -9532,12 +9441,17 @@ public class PackageManagerService extends IPackageManager.Stub { super(null, false, null, (isExternal ? INSTALL_EXTERNAL : 0) | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, instructionSets, null, isMultiArch); + // Hackily pretend we're still looking at a full code path + if (!fullCodePath.endsWith(RES_FILE_NAME)) { + fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath(); + } + // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); int sidx = subStr1.lastIndexOf("/"); cid = subStr1.substring(sidx+1, eidx); - setCachePath(subStr1); + setMountPath(subStr1); } AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked, @@ -9546,7 +9460,7 @@ public class PackageManagerService extends IPackageManager.Stub { | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, instructionSets, null, isMultiArch); this.cid = cid; - setCachePath(PackageHelper.getSdDir(cid)); + setMountPath(PackageHelper.getSdDir(cid)); } /** New install from existing */ @@ -9559,12 +9473,22 @@ public class PackageManagerService extends IPackageManager.Stub { } void createCopyFile() { - cid = getTempContainerId(); + cid = mInstallerService.allocateExternalStageCidLegacy(); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { - return imcs.checkExternalFreeStorage(originFile.getAbsolutePath(), isFwdLocked(), + final long sizeBytes = imcs.calculateInstalledSize(packagePath, isFwdLocked(), abiOverride); + + final File target; + if (isExternal()) { + target = new UserEnvironment(UserHandle.USER_OWNER).getExternalStorageDirectory(); + } else { + target = Environment.getDataDirectory(); + } + + final StorageManager storage = StorageManager.from(mContext); + return (sizeBytes <= storage.getStorageBytesUntilLow(target)); } private final boolean isExternal() { @@ -9572,6 +9496,7 @@ public class PackageManagerService extends IPackageManager.Stub { } int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { + // TODO: if already staged, we only need to extract native code if (temp) { createCopyFile(); } else { @@ -9582,12 +9507,12 @@ public class PackageManagerService extends IPackageManager.Stub { PackageHelper.destroySdDir(cid); } - final String newCachePath = imcs.copyPackageToContainer( + final String newMountPath = imcs.copyPackageToContainer( originFile.getAbsolutePath(), cid, getEncryptKey(), isExternal(), isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */)); - if (newCachePath != null) { - setCachePath(newCachePath); + if (newMountPath != null) { + setMountPath(newMountPath); return PackageManager.INSTALL_SUCCEEDED; } else { return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; @@ -9616,10 +9541,10 @@ public class PackageManagerService extends IPackageManager.Stub { } else { boolean mounted = PackageHelper.isContainerMounted(cid); if (!mounted) { - String newCachePath = PackageHelper.mountSdDir(cid, getEncryptKey(), + String newMountPath = PackageHelper.mountSdDir(cid, getEncryptKey(), Process.SYSTEM_UID); - if (newCachePath != null) { - setCachePath(newCachePath); + if (newMountPath != null) { + setMountPath(newMountPath); } else { return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; } @@ -9630,7 +9555,7 @@ public class PackageManagerService extends IPackageManager.Stub { boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) { String newCacheId = getNextCodePath(oldCodePath, pkg.packageName, "/" + RES_FILE_NAME); - String newCachePath = null; + String newMountPath = null; if (PackageHelper.isContainerMounted(cid)) { // Unmount the container if (!PackageHelper.unMountSdDir(cid)) { @@ -9655,46 +9580,59 @@ public class PackageManagerService extends IPackageManager.Stub { } if (!PackageHelper.isContainerMounted(newCacheId)) { Slog.w(TAG, "Mounting container " + newCacheId); - newCachePath = PackageHelper.mountSdDir(newCacheId, + newMountPath = PackageHelper.mountSdDir(newCacheId, getEncryptKey(), Process.SYSTEM_UID); } else { - newCachePath = PackageHelper.getSdDir(newCacheId); + newMountPath = PackageHelper.getSdDir(newCacheId); } - if (newCachePath == null) { + if (newMountPath == null) { Slog.w(TAG, "Failed to get cache path for " + newCacheId); return false; } Log.i(TAG, "Succesfully renamed " + cid + " to " + newCacheId + - " at new path: " + newCachePath); + " at new path: " + newMountPath); cid = newCacheId; - setCachePath(newCachePath); - // TODO: extend to support split APKs - pkg.codePath = getCodePath(); - pkg.baseCodePath = getCodePath(); - pkg.splitCodePaths = null; - - pkg.applicationInfo.setCodePath(getCodePath()); - pkg.applicationInfo.setBaseCodePath(getCodePath()); - pkg.applicationInfo.setSplitCodePaths(null); - pkg.applicationInfo.setResourcePath(getResourcePath()); - pkg.applicationInfo.setBaseResourcePath(getResourcePath()); - pkg.applicationInfo.setSplitResourcePaths(null); + final File beforeCodeFile = new File(packagePath); + setMountPath(newMountPath); + final File afterCodeFile = new File(packagePath); + + // Reflect the rename in scanned details + pkg.codePath = afterCodeFile.getAbsolutePath(); + pkg.baseCodePath = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, + pkg.baseCodePath); + pkg.splitCodePaths = FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, + pkg.splitCodePaths); + + // Reflect the rename in app info + pkg.applicationInfo.setCodePath(pkg.codePath); + pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); + pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); + pkg.applicationInfo.setResourcePath(pkg.codePath); + pkg.applicationInfo.setBaseResourcePath(pkg.baseCodePath); + pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths); return true; } - private void setCachePath(String newCachePath) { - File cachePath = new File(newCachePath); - legacyNativeLibraryDir = new File(cachePath, LIB_DIR_NAME).getPath(); - packagePath = new File(cachePath, RES_FILE_NAME).getPath(); + private void setMountPath(String mountPath) { + final File mountFile = new File(mountPath); - if (isFwdLocked()) { - resourcePath = new File(cachePath, PUBLIC_RES_FILE_NAME).getPath(); + final File monolithicFile = new File(mountFile, RES_FILE_NAME); + if (monolithicFile.exists()) { + packagePath = monolithicFile.getAbsolutePath(); + if (isFwdLocked()) { + resourcePath = new File(mountFile, PUBLIC_RES_FILE_NAME).getAbsolutePath(); + } else { + resourcePath = packagePath; + } } else { + packagePath = mountFile.getAbsolutePath(); resourcePath = packagePath; } + + legacyNativeLibraryDir = new File(mountFile, LIB_DIR_NAME).getAbsolutePath(); } int doPostInstall(int status, int uid) { @@ -9733,23 +9671,43 @@ public class PackageManagerService extends IPackageManager.Stub { PackageHelper.destroySdDir(cid); } - void cleanUpResourcesLI() { - String sourceFile = getCodePath(); - // Remove dex file - if (instructionSets == null) { - throw new IllegalStateException("instructionSet == null"); - } - String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); - for (String dexCodeInstructionSet : dexCodeInstructionSets) { - int retCode = mInstaller.rmdex(sourceFile, dexCodeInstructionSet); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove dex file for package: " - + " at location " - + sourceFile.toString() + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion + private List<String> getAllCodePaths() { + final File codeFile = new File(getCodePath()); + if (codeFile != null && codeFile.exists()) { + try { + final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0); + return pkg.getAllCodePaths(); + } catch (PackageParserException e) { + // Ignored; we tried our best } } + return Collections.EMPTY_LIST; + } + + void cleanUpResourcesLI() { + // Enumerate all code paths before deleting + cleanUpResourcesLI(getAllCodePaths()); + } + + private void cleanUpResourcesLI(List<String> allCodePaths) { cleanUp(); + + if (!allCodePaths.isEmpty()) { + if (instructionSets == null) { + throw new IllegalStateException("instructionSet == null"); + } + String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); + for (String codePath : allCodePaths) { + for (String dexCodeInstructionSet : dexCodeInstructionSets) { + int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet); + if (retCode < 0) { + Slog.w(TAG, "Couldn't remove dex file for package: " + + " at location " + codePath + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } + } + } + } } boolean matchContainer(String app) { @@ -9764,16 +9722,19 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean doPostDeleteLI(boolean delete) { - boolean ret = false; + if (DEBUG_SD_INSTALL) Slog.i(TAG, "doPostDeleteLI() del=" + delete); + final List<String> allCodePaths = getAllCodePaths(); boolean mounted = PackageHelper.isContainerMounted(cid); if (mounted) { // Unmount first - ret = PackageHelper.unMountSdDir(cid); + if (PackageHelper.unMountSdDir(cid)) { + mounted = false; + } } - if (ret && delete) { - cleanUpResourcesLI(); + if (!mounted && delete) { + cleanUpResourcesLI(allCodePaths); } - return ret; + return !mounted; } @Override @@ -10960,6 +10921,7 @@ public class PackageManagerService extends IPackageManager.Stub { outInfo.args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString, getAppDexInstructionSets(ps), isMultiArch(ps)); + if (DEBUG_SD_INSTALL) Slog.i(TAG, "args=" + outInfo.args); } return true; } @@ -11462,11 +11424,13 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, int userId) { - addPreferredActivityInternal(filter, match, set, activity, true, userId); + addPreferredActivityInternal(filter, match, set, activity, true, userId, + "Adding preferred"); } private void addPreferredActivityInternal(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, boolean always, int userId) { + ComponentName[] set, ComponentName activity, boolean always, int userId, + String opname) { // writer int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, "add preferred activity"); @@ -11488,10 +11452,11 @@ public class PackageManagerService extends IPackageManager.Stub { android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); } - Slog.i(TAG, "Adding preferred activity " + activity + " for user " + userId + " :"); + PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId); + Slog.i(TAG, opname + " activity " + activity.flattenToShortString() + " for user " + + userId + ":"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); - mSettings.editPreferredActivitiesLPw(userId).addFilter( - new PreferredActivity(filter, match, set, activity, always)); + pir.addFilter(new PreferredActivity(filter, match, set, activity, always)); mSettings.writePackageRestrictionsLPr(userId); } } @@ -11514,7 +11479,6 @@ public class PackageManagerService extends IPackageManager.Stub { final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, "replace preferred activity"); - final int callingUserId = UserHandle.getUserId(callingUid); synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) @@ -11529,30 +11493,63 @@ public class PackageManagerService extends IPackageManager.Stub { android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); } - PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId); + PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId); if (pir != null) { - Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0)); - if (filter.countDataSchemes() == 1) { - Uri.Builder builder = new Uri.Builder(); - builder.scheme(filter.getDataScheme(0)); - intent.setData(builder.build()); - } - List<PreferredActivity> matches = pir.queryIntent( - intent, null, true, callingUserId); - if (DEBUG_PREFERRED) { - Slog.i(TAG, matches.size() + " preferred matches for " + intent); - } - for (int i = 0; i < matches.size(); i++) { - PreferredActivity pa = matches.get(i); + // Get all of the existing entries that exactly match this filter. + ArrayList<PreferredActivity> existing = pir.findFilters(filter); + if (existing != null && existing.size() == 1) { + PreferredActivity cur = existing.get(0); if (DEBUG_PREFERRED) { - Slog.i(TAG, "Removing preferred activity " - + pa.mPref.mComponent + ":"); + Slog.i(TAG, "Checking replace of preferred:"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); + if (!cur.mPref.mAlways) { + Slog.i(TAG, " -- CUR; not mAlways!"); + } else { + Slog.i(TAG, " -- CUR: mMatch=" + cur.mPref.mMatch); + Slog.i(TAG, " -- CUR: mSet=" + + Arrays.toString(cur.mPref.mSetComponents)); + Slog.i(TAG, " -- CUR: mComponent=" + cur.mPref.mShortComponent); + Slog.i(TAG, " -- NEW: mMatch=" + + (match&IntentFilter.MATCH_CATEGORY_MASK)); + Slog.i(TAG, " -- CUR: mSet=" + Arrays.toString(set)); + Slog.i(TAG, " -- CUR: mComponent=" + activity.flattenToShortString()); + } + } + if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity) + && cur.mPref.mMatch == (match&IntentFilter.MATCH_CATEGORY_MASK) + && cur.mPref.sameSet(set)) { + if (DEBUG_PREFERRED) { + Slog.i(TAG, "Replacing with same preferred activity " + + cur.mPref.mShortComponent + " for user " + + userId + ":"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + } else { + Slog.i(TAG, "Replacing with same preferred activity " + + cur.mPref.mShortComponent + " for user " + + userId); + } + return; + } + } + + if (existing != null) { + if (DEBUG_PREFERRED) { + Slog.i(TAG, existing.size() + " existing preferred matches for:"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + } + for (int i = 0; i < existing.size(); i++) { + PreferredActivity pa = existing.get(i); + if (DEBUG_PREFERRED) { + Slog.i(TAG, "Removing existing preferred activity " + + pa.mPref.mComponent + ":"); + pa.dump(new LogPrinter(Log.INFO, TAG), " "); + } + pir.removeFilter(pa); } - pir.removeFilter(pa); } } - addPreferredActivityInternal(filter, match, set, activity, true, callingUserId); + addPreferredActivityInternal(filter, match, set, activity, true, userId, + "Replacing preferred"); } } @@ -12610,7 +12607,7 @@ public class PackageManagerService extends IPackageManager.Stub { private boolean mMediaMounted = false; - private String getEncryptKey() { + static String getEncryptKey() { try { String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( SD_ENCRYPTION_KEYSTORE_NAME); @@ -12630,30 +12627,6 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe); return null; } - - } - - /* package */static String getTempContainerId() { - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - for (final String name : list) { - // Ignore null and non-temporary container entries - if (name == null || !name.startsWith(mTempContainerPrefix)) { - continue; - } - - String subStr = name.substring(mTempContainerPrefix.length()); - try { - int cid = Integer.parseInt(subStr); - if (cid >= tmpIdx) { - tmpIdx = cid + 1; - } - } catch (NumberFormatException e) { - } - } - } - return mTempContainerPrefix + tmpIdx; } /* @@ -12711,31 +12684,27 @@ public class PackageManagerService extends IPackageManager.Stub { */ private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus, boolean externalStorage) { - // Collection of uids - int uidArr[] = null; - // Collection of stale containers - HashSet<String> removeCids = new HashSet<String>(); - // Collection of packages on external media with valid containers. - HashMap<AsecInstallArgs, String> processCids = new HashMap<AsecInstallArgs, String>(); - // Get list of secure containers. - final String list[] = PackageHelper.getSecureContainerList(); - if (list == null || list.length == 0) { - Log.i(TAG, "No secure containers on sdcard"); + ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>(); + int[] uidArr = EmptyArray.INT; + + final String[] list = PackageHelper.getSecureContainerList(); + if (ArrayUtils.isEmpty(list)) { + Log.i(TAG, "No secure containers found"); } else { // Process list of secure containers and categorize them // as active or stale based on their package internal state. - int uidList[] = new int[list.length]; - int num = 0; + // reader synchronized (mPackages) { for (String cid : list) { + // Leave stages untouched for now; installer service owns them + if (PackageInstallerService.isStageName(cid)) continue; + if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); String pkgName = getAsecPackageName(cid); if (pkgName == null) { - if (DEBUG_SD_INSTALL) - Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no package name"); continue; } if (DEBUG_SD_INSTALL) @@ -12743,8 +12712,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps == null) { - Log.i(TAG, "Deleting container with no matching settings " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + " with no matching settings"); continue; } @@ -12760,7 +12728,8 @@ public class PackageManagerService extends IPackageManager.Stub { getAppDexInstructionSets(ps), isForwardLocked(ps), isMultiArch(ps)); // The package status is changed only if the code path // matches between settings and the container id. - if (ps.codePathString != null && ps.codePathString.equals(args.getCodePath())) { + if (ps.codePathString != null + && ps.codePathString.startsWith(args.getCodePath())) { if (DEBUG_SD_INSTALL) { Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName + " at code path: " + ps.codePathString); @@ -12770,35 +12739,25 @@ public class PackageManagerService extends IPackageManager.Stub { processCids.put(args, ps.codePathString); final int uid = ps.appId; if (uid != -1) { - uidList[num++] = uid; + uidArr = ArrayUtils.appendInt(uidArr, uid); } } else { - Log.i(TAG, "Deleting stale container for " + cid); - removeCids.add(cid); + Slog.i(TAG, "Found stale container " + cid + ": expected codePath=" + + ps.codePathString); } } } - if (num > 0) { - // Sort uid list - Arrays.sort(uidList, 0, num); - // Throw away duplicates - uidArr = new int[num]; - uidArr[0] = uidList[0]; - int di = 0; - for (int i = 1; i < num; i++) { - if (uidList[i - 1] != uidList[i]) { - uidArr[di++] = uidList[i]; - } - } - } + Arrays.sort(uidArr); } + // Process packages with valid entries. if (isMounted) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr, removeCids); + loadMediaPackages(processCids, uidArr); startCleaningPackages(); + mInstallerService.onSecureContainersAvailable(); } else { if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); @@ -12806,8 +12765,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, - ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { int size = pkgList.size(); if (size > 0) { // Send broadcasts here @@ -12832,11 +12791,10 @@ public class PackageManagerService extends IPackageManager.Stub { * the cid is added to list of removeCids. We currently don't delete stale * containers. */ - private void loadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], - HashSet<String> removeCids) { + private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr) { ArrayList<String> pkgList = new ArrayList<String>(); Set<AsecInstallArgs> keys = processCids.keySet(); - boolean doGc = false; + for (AsecInstallArgs args : keys) { String codePath = processCids.get(args); if (DEBUG_SD_INSTALL) @@ -12850,7 +12808,7 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } // Check code path here. - if (codePath == null || !codePath.equals(args.getCodePath())) { + if (codePath == null || !codePath.startsWith(args.getCodePath())) { Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath() + " does not match one in settings " + codePath); continue; @@ -12864,7 +12822,6 @@ public class PackageManagerService extends IPackageManager.Stub { parseFlags |= PackageParser.PARSE_FORWARD_LOCK; } - doGc = true; synchronized (mInstallLock) { PackageParser.Package pkg = null; try { @@ -12894,9 +12851,7 @@ public class PackageManagerService extends IPackageManager.Stub { } finally { if (retCode != PackageManager.INSTALL_SUCCEEDED) { - // Don't destroy container here. Wait till gc clears things - // up. - removeCids.add(args.cid); + Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode); } } } @@ -12931,21 +12886,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgList.size() > 0) { sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null); } - // Force gc to avoid any stale parser references that we might have. - if (doGc) { - Runtime.getRuntime().gc(); - } - // List stale containers and destroy stale temporary containers. - if (removeCids != null) { - for (String cid : removeCids) { - if (cid.startsWith(mTempContainerPrefix)) { - Log.i(TAG, "Destroying stale temporary container " + cid); - PackageHelper.destroySdDir(cid); - } else { - Log.w(TAG, "Container " + cid + " is stale"); - } - } - } } /* @@ -12969,7 +12909,7 @@ public class PackageManagerService extends IPackageManager.Stub { * that we always have to post this message if status has been requested no * matter what. */ - private void unloadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], + private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[], final boolean reportStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java index f437372..69c1909 100644 --- a/services/core/java/com/android/server/pm/PreferredComponent.java +++ b/services/core/java/com/android/server/pm/PreferredComponent.java @@ -44,10 +44,10 @@ public class PreferredComponent { // Whether this is to be the one that's always chosen. If false, it's the most recently chosen. public boolean mAlways; - private final String[] mSetPackages; - private final String[] mSetClasses; - private final String[] mSetComponents; - private final String mShortComponent; + final String[] mSetPackages; + final String[] mSetClasses; + final String[] mSetComponents; + final String mShortComponent; private String mParseError; private final Callbacks mCallbacks; @@ -193,7 +193,12 @@ public class PreferredComponent { } public boolean sameSet(List<ResolveInfo> query, int priority) { - if (mSetPackages == null) return false; + if (mSetPackages == null) { + return query == null; + } + if (query == null) { + return false; + } final int NQ = query.size(); final int NS = mSetPackages.length; int numMatch = 0; @@ -215,6 +220,27 @@ public class PreferredComponent { return numMatch == NS; } + public boolean sameSet(ComponentName[] comps) { + if (mSetPackages == null) return false; + final int NQ = comps.length; + final int NS = mSetPackages.length; + int numMatch = 0; + for (int i=0; i<NQ; i++) { + ComponentName cn = comps[i]; + boolean good = false; + for (int j=0; j<NS; j++) { + if (mSetPackages[j].equals(cn.getPackageName()) + && mSetClasses[j].equals(cn.getClassName())) { + numMatch++; + good = true; + break; + } + } + if (!good) return false; + } + return numMatch == NS; + } + public void dump(PrintWriter out, String prefix, Object ident) { out.print(prefix); out.print( Integer.toHexString(System.identityHashCode(ident))); diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 2b4a24a..46497242 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -406,7 +406,6 @@ final class Notifier { private void sendNextBroadcast() { final int powerState; - final int goToSleepReason; synchronized (mLock) { if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) { // Broadcasted power state is unknown. Send wake up. diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 39c6e0e..9734bd4 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -56,10 +56,9 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.SystemService; +import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; -import android.os.Parcel; -import android.os.ServiceManager; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.util.EventLog; @@ -709,7 +708,6 @@ public final class PowerManagerService extends com.android.server.SystemService if (mLowPowerModeEnabled != lowPowerModeEnabled) { mLowPowerModeEnabled = lowPowerModeEnabled; powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0); - setSurfaceFlingerLowPowerMode(lowPowerModeEnabled ? 1 : 0); mLowPowerModeEnabled = lowPowerModeEnabled; BackgroundThread.getHandler().post(new Runnable() { @Override @@ -820,17 +818,12 @@ public final class PowerManagerService extends com.android.server.SystemService + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags)); } - mWakeLocks.remove(index); - notifyWakeLockReleasedLocked(wakeLock); - wakeLock.mLock.unlinkToDeath(wakeLock, 0); - if ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0) { mRequestWaitForNegativeProximity = true; } - applyWakeLockFlagsOnReleaseLocked(wakeLock); - mDirty |= DIRTY_WAKE_LOCKS; - updatePowerStateLocked(); + wakeLock.mLock.unlinkToDeath(wakeLock, 0); + removeWakeLockLocked(wakeLock, index); } } @@ -846,15 +839,19 @@ public final class PowerManagerService extends com.android.server.SystemService return; } - mWakeLocks.remove(index); - notifyWakeLockReleasedLocked(wakeLock); - - applyWakeLockFlagsOnReleaseLocked(wakeLock); - mDirty |= DIRTY_WAKE_LOCKS; - updatePowerStateLocked(); + removeWakeLockLocked(wakeLock, index); } } + private void removeWakeLockLocked(WakeLock wakeLock, int index) { + mWakeLocks.remove(index); + notifyWakeLockReleasedLocked(wakeLock); + + applyWakeLockFlagsOnReleaseLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) { if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0 && isScreenLock(wakeLock)) { @@ -865,7 +862,8 @@ public final class PowerManagerService extends com.android.server.SystemService } } - private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws, String historyTag) { + private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws, String historyTag, + int callingUid) { synchronized (mLock) { int index = findWakeLockIndexLocked(lock); if (index < 0) { @@ -873,7 +871,8 @@ public final class PowerManagerService extends com.android.server.SystemService Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock) + " [not found], ws=" + ws); } - throw new IllegalArgumentException("Wake lock not active"); + throw new IllegalArgumentException("Wake lock not active: " + lock + + " from uid " + callingUid); } WakeLock wakeLock = mWakeLocks.get(index); @@ -975,21 +974,26 @@ public final class PowerManagerService extends com.android.server.SystemService return false; } - mNotifier.onUserActivity(event, uid); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "userActivity"); + try { + mNotifier.onUserActivity(event, uid); - if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { - if (eventTime > mLastUserActivityTimeNoChangeLights - && eventTime > mLastUserActivityTime) { - mLastUserActivityTimeNoChangeLights = eventTime; - mDirty |= DIRTY_USER_ACTIVITY; - return true; - } - } else { - if (eventTime > mLastUserActivityTime) { - mLastUserActivityTime = eventTime; - mDirty |= DIRTY_USER_ACTIVITY; - return true; + if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { + if (eventTime > mLastUserActivityTimeNoChangeLights + && eventTime > mLastUserActivityTime) { + mLastUserActivityTimeNoChangeLights = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } + } else { + if (eventTime > mLastUserActivityTime) { + mLastUserActivityTime = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); } return false; } @@ -1012,25 +1016,30 @@ public final class PowerManagerService extends com.android.server.SystemService return false; } - switch (mWakefulness) { - case WAKEFULNESS_ASLEEP: - Slog.i(TAG, "Waking up from sleep (uid " + uid +")..."); - break; - case WAKEFULNESS_DREAMING: - Slog.i(TAG, "Waking up from dream (uid " + uid +")..."); - break; - case WAKEFULNESS_DOZING: - Slog.i(TAG, "Waking up from dozing (uid " + uid +")..."); - break; - } + Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp"); + try { + switch (mWakefulness) { + case WAKEFULNESS_ASLEEP: + Slog.i(TAG, "Waking up from sleep (uid " + uid +")..."); + break; + case WAKEFULNESS_DREAMING: + Slog.i(TAG, "Waking up from dream (uid " + uid +")..."); + break; + case WAKEFULNESS_DOZING: + Slog.i(TAG, "Waking up from dozing (uid " + uid +")..."); + break; + } - mLastWakeTime = eventTime; - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_AWAKE; - setInteractiveStateLocked(true, 0); + mLastWakeTime = eventTime; + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_AWAKE; + setInteractiveStateLocked(true, 0); - userActivityNoUpdateLocked( - eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); + userActivityNoUpdateLocked( + eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } return true; } @@ -1058,53 +1067,58 @@ public final class PowerManagerService extends com.android.server.SystemService return false; } - switch (reason) { - case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: - Slog.i(TAG, "Going to sleep due to device administration policy " - + "(uid " + uid +")..."); - break; - case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: - Slog.i(TAG, "Going to sleep due to screen timeout (uid " + uid +")..."); - break; - case PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH: - Slog.i(TAG, "Going to sleep due to lid switch (uid " + uid +")..."); - break; - case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON: - Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")..."); - break; - case PowerManager.GO_TO_SLEEP_REASON_HDMI: - Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")..."); - break; - default: - Slog.i(TAG, "Going to sleep by application request (uid " + uid +")..."); - reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION; - break; - } - - mLastSleepTime = eventTime; - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_DOZING; - mSandmanSummoned = true; - setInteractiveStateLocked(false, reason); - - // Report the number of wake locks that will be cleared by going to sleep. - int numWakeLocksCleared = 0; - final int numWakeLocks = mWakeLocks.size(); - for (int i = 0; i < numWakeLocks; i++) { - final WakeLock wakeLock = mWakeLocks.get(i); - switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { - case PowerManager.FULL_WAKE_LOCK: - case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: - case PowerManager.SCREEN_DIM_WAKE_LOCK: - numWakeLocksCleared += 1; + Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep"); + try { + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + Slog.i(TAG, "Going to sleep due to device administration policy " + + "(uid " + uid +")..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + Slog.i(TAG, "Going to sleep due to screen timeout (uid " + uid +")..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH: + Slog.i(TAG, "Going to sleep due to lid switch (uid " + uid +")..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON: + Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_HDMI: + Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")..."); + break; + default: + Slog.i(TAG, "Going to sleep by application request (uid " + uid +")..."); + reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION; break; } - } - EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared); - // Skip dozing if requested. - if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { - reallyGoToSleepNoUpdateLocked(eventTime, uid); + mLastSleepTime = eventTime; + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_DOZING; + mSandmanSummoned = true; + setInteractiveStateLocked(false, reason); + + // Report the number of wake locks that will be cleared by going to sleep. + int numWakeLocksCleared = 0; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + numWakeLocksCleared += 1; + break; + } + } + EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared); + + // Skip dozing if requested. + if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { + reallyGoToSleepNoUpdateLocked(eventTime, uid); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } @@ -1127,12 +1141,17 @@ public final class PowerManagerService extends com.android.server.SystemService return false; } - Slog.i(TAG, "Nap time (uid " + uid +")..."); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "nap"); + try { + Slog.i(TAG, "Nap time (uid " + uid +")..."); - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_DREAMING; - mSandmanSummoned = true; - setInteractiveStateLocked(true, 0); + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_DREAMING; + mSandmanSummoned = true; + setInteractiveStateLocked(true, 0); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } return true; } @@ -1148,11 +1167,16 @@ public final class PowerManagerService extends com.android.server.SystemService return false; } - Slog.i(TAG, "Sleeping (uid " + uid +")..."); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallyGoToSleep"); + try { + Slog.i(TAG, "Sleeping (uid " + uid +")..."); - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_ASLEEP; - setInteractiveStateLocked(false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_ASLEEP; + setInteractiveStateLocked(false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } return true; } @@ -1189,40 +1213,45 @@ public final class PowerManagerService extends com.android.server.SystemService Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked"); } - // Phase 0: Basic state updates. - updateIsPoweredLocked(mDirty); - updateStayOnLocked(mDirty); - - // Phase 1: Update wakefulness. - // Loop because the wake lock and user activity computations are influenced - // by changes in wakefulness. - final long now = SystemClock.uptimeMillis(); - int dirtyPhase2 = 0; - for (;;) { - int dirtyPhase1 = mDirty; - dirtyPhase2 |= dirtyPhase1; - mDirty = 0; + Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState"); + try { + // Phase 0: Basic state updates. + updateIsPoweredLocked(mDirty); + updateStayOnLocked(mDirty); - updateWakeLockSummaryLocked(dirtyPhase1); - updateUserActivitySummaryLocked(now, dirtyPhase1); - if (!updateWakefulnessLocked(dirtyPhase1)) { - break; + // Phase 1: Update wakefulness. + // Loop because the wake lock and user activity computations are influenced + // by changes in wakefulness. + final long now = SystemClock.uptimeMillis(); + int dirtyPhase2 = 0; + for (;;) { + int dirtyPhase1 = mDirty; + dirtyPhase2 |= dirtyPhase1; + mDirty = 0; + + updateWakeLockSummaryLocked(dirtyPhase1); + updateUserActivitySummaryLocked(now, dirtyPhase1); + if (!updateWakefulnessLocked(dirtyPhase1)) { + break; + } } - } - // Phase 2: Update dreams and display power state. - updateDreamLocked(dirtyPhase2); - updateDisplayPowerStateLocked(dirtyPhase2); + // Phase 2: Update dreams and display power state. + updateDreamLocked(dirtyPhase2); + updateDisplayPowerStateLocked(dirtyPhase2); - // Phase 3: Send notifications, if needed. - if (mDisplayReady) { - finishInteractiveStateChangeLocked(); - } + // Phase 3: Send notifications, if needed. + if (mDisplayReady) { + finishInteractiveStateChangeLocked(); + } - // Phase 4: Update suspend blocker. - // Because we might release the last suspend blocker here, we need to make sure - // we finished everything else first! - updateSuspendBlockerLocked(); + // Phase 4: Update suspend blocker. + // Because we might release the last suspend blocker here, we need to make sure + // we finished everything else first! + updateSuspendBlockerLocked(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } /** @@ -1993,7 +2022,12 @@ public final class PowerManagerService extends com.android.server.SystemService Slog.d(TAG, "Setting HAL auto-suspend mode to " + enable); } mHalAutoSuspendModeEnabled = enable; - nativeSetAutoSuspend(enable); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setHalAutoSuspend(" + enable + ")"); + try { + nativeSetAutoSuspend(enable); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } } @@ -2003,7 +2037,12 @@ public final class PowerManagerService extends com.android.server.SystemService Slog.d(TAG, "Setting HAL interactive mode to " + enable); } mHalInteractiveModeEnabled = enable; - nativeSetInteractive(enable); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setHalInteractive(" + enable + ")"); + try { + nativeSetInteractive(enable); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } } @@ -2198,21 +2237,6 @@ public final class PowerManagerService extends com.android.server.SystemService nativeSendPowerHint(hintId, data); } - private static void setSurfaceFlingerLowPowerMode(int enabled) { - try { - final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); - if (flinger != null) { - final Parcel data = Parcel.obtain(); - data.writeInterfaceToken("android.ui.ISurfaceComposer"); - data.writeInt(enabled); - flinger.transact(1016, data, null, 0); - data.recycle(); - } - } catch (RemoteException ex) { - Slog.e(TAG, "Failed to reduce refresh rate", ex); - } - } - /** * Low-level function turn the device off immediately, without trying * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. @@ -2605,20 +2629,23 @@ public final class PowerManagerService extends com.android.server.SystemService private final class SuspendBlockerImpl implements SuspendBlocker { private final String mName; + private final String mTraceName; private int mReferenceCount; public SuspendBlockerImpl(String name) { mName = name; + mTraceName = "SuspendBlocker (" + name + ")"; } @Override protected void finalize() throws Throwable { try { if (mReferenceCount != 0) { - Log.wtf(TAG, "Suspend blocker \"" + mName + Slog.wtf(TAG, "Suspend blocker \"" + mName + "\" was finalized without being released!"); mReferenceCount = 0; nativeReleaseSuspendBlocker(mName); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0); } } finally { super.finalize(); @@ -2633,6 +2660,7 @@ public final class PowerManagerService extends com.android.server.SystemService if (DEBUG_SPEW) { Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\"."); } + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0); nativeAcquireSuspendBlocker(mName); } } @@ -2647,8 +2675,9 @@ public final class PowerManagerService extends com.android.server.SystemService Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\"."); } nativeReleaseSuspendBlocker(mName); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0); } else if (mReferenceCount < 0) { - Log.wtf(TAG, "Suspend blocker \"" + mName + Slog.wtf(TAG, "Suspend blocker \"" + mName + "\" was released without being acquired!", new Throwable()); mReferenceCount = 0; } @@ -2664,6 +2693,8 @@ public final class PowerManagerService extends com.android.server.SystemService } private final class ScreenOnBlockerImpl implements ScreenOnBlocker { + private static final String TRACE_NAME = "ScreenOnBlocker"; + private int mNestCount; public boolean isHeld() { @@ -2676,9 +2707,12 @@ public final class PowerManagerService extends com.android.server.SystemService public void acquire() { synchronized (this) { mNestCount += 1; - if (DEBUG) { + if (DEBUG || true) { Slog.d(TAG, "Screen on blocked: mNestCount=" + mNestCount); } + if (mNestCount == 1) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_NAME, 0); + } } } @@ -2686,16 +2720,16 @@ public final class PowerManagerService extends com.android.server.SystemService public void release() { synchronized (this) { mNestCount -= 1; - if (mNestCount < 0) { - Log.wtf(TAG, "Screen on blocker was released without being acquired!", - new Throwable()); - mNestCount = 0; - } if (mNestCount == 0) { + if (DEBUG || true) { + Slog.d(TAG, "Screen on unblocked: mNestCount=" + mNestCount); + } mHandler.sendEmptyMessage(MSG_SCREEN_ON_BLOCKER_RELEASED); - } - if (DEBUG) { - Slog.d(TAG, "Screen on unblocked: mNestCount=" + mNestCount); + Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_NAME, 0); + } else if (mNestCount < 0) { + Slog.wtf(TAG, "Screen on blocker was released without being acquired!", + new Throwable()); + mNestCount = 0; } } } @@ -2802,9 +2836,10 @@ public final class PowerManagerService extends com.android.server.SystemService ws = null; } + final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - updateWakeLockWorkSourceInternal(lock, ws, historyTag); + updateWakeLockWorkSourceInternal(lock, ws, historyTag, callingUid); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 21905f0..f85e2d9 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -22,7 +22,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -39,18 +38,14 @@ import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * A note on locking: We rely on the fact that calls onto mBar are oneway or * if they are local, that they just enqueue messages to not deadlock. */ -public class StatusBarManagerService extends IStatusBarService.Stub - implements WindowManagerService.OnHardKeyboardStatusChangeListener -{ +public class StatusBarManagerService extends IStatusBarService.Stub { private static final String TAG = "StatusBarManagerService"; private static final boolean SPEW = false; @@ -95,7 +90,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub public StatusBarManagerService(Context context, WindowManagerService windowManager) { mContext = context; mWindowManager = windowManager; - mWindowManager.setOnHardKeyboardStatusChangeListener(this); final Resources res = context.getResources(); mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); @@ -190,10 +184,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub @Override public void disable(int what, IBinder token, String pkg) { - if (!mNotificationDelegate.allowDisable(what, token, pkg)) { - if (SPEW) Slog.d(TAG, "Blocking disable request from " + pkg); - return; - } disableInternal(mCurrentUserId, what, token, pkg); } @@ -394,29 +384,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub } @Override - public void setHardKeyboardEnabled(final boolean enabled) { - mHandler.post(new Runnable() { - public void run() { - mWindowManager.setHardKeyboardEnabled(enabled); - } - }); - } - - @Override - public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) { - mHandler.post(new Runnable() { - public void run() { - if (mBar != null) { - try { - mBar.setHardKeyboardStatus(available, enabled); - } catch (RemoteException ex) { - } - } - } - }); - } - - @Override public void toggleRecentApps() { if (mBar != null) { try { @@ -510,11 +477,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub switches[2] = mMenuVisible ? 1 : 0; switches[3] = mImeWindowVis; switches[4] = mImeBackDisposition; - switches[7] = mShowImeSwitcher ? 1 : 0; + switches[5] = mShowImeSwitcher ? 1 : 0; binders.add(mImeToken); } - switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0; - switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0; } /** diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index eb38f4a..468a344 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -453,6 +453,8 @@ public class DeviceStorageMonitorService extends SystemService { notification.icon = com.android.internal.R.drawable.stat_notify_disk_full; notification.tickerText = title; notification.flags |= Notification.FLAG_NO_CLEAR; + notification.color = context.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(context, title, details, intent); notification.visibility = Notification.VISIBILITY_PUBLIC; notification.category = Notification.CATEGORY_SYSTEM; diff --git a/services/core/java/com/android/server/tv/PersistentDataStore.java b/services/core/java/com/android/server/tv/PersistentDataStore.java index 4af8f2c..fcfaaea 100644 --- a/services/core/java/com/android/server/tv/PersistentDataStore.java +++ b/services/core/java/com/android/server/tv/PersistentDataStore.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.media.tv.TvContentRating; import android.media.tv.TvInputManager; +import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; @@ -44,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -68,7 +70,8 @@ final class PersistentDataStore { // The atomic file used to safely read or write the file. private final AtomicFile mAtomicFile; - private final List<TvContentRating> mBlockedRatings = new ArrayList<TvContentRating>(); + private final List<TvContentRating> mBlockedRatings = + Collections.synchronizedList(new ArrayList<TvContentRating>()); private boolean mBlockedRatingsChanged; @@ -81,8 +84,13 @@ final class PersistentDataStore { public PersistentDataStore(Context context, int userId) { mContext = context; - mAtomicFile = new AtomicFile(new File("/data/system/tv/" + userId - + "/tv-input-manager-state.xml")); + File userDir = Environment.getUserSystemDirectory(userId); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + throw new IllegalStateException("User dir cannot be created: " + userDir); + } + } + mAtomicFile = new AtomicFile(new File(userDir, "tv-input-manager-state.xml")); } public boolean isParentalControlsEnabled() { @@ -101,9 +109,11 @@ final class PersistentDataStore { public boolean isRatingBlocked(TvContentRating rating) { loadIfNeeded(); - for (TvContentRating blcokedRating : mBlockedRatings) { - if (rating.contains(blcokedRating)) { - return true; + synchronized (mBlockedRatings) { + for (TvContentRating blcokedRating : mBlockedRatings) { + if (rating.contains(blcokedRating)) { + return true; + } } } return false; @@ -265,10 +275,12 @@ final class PersistentDataStore { serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, TAG_TV_INPUT_MANAGER_STATE); serializer.startTag(null, TAG_BLOCKED_RATINGS); - for (TvContentRating rating : mBlockedRatings) { - serializer.startTag(null, TAG_RATING); - serializer.attribute(null, ATTR_STRING, rating.flattenToString()); - serializer.endTag(null, TAG_RATING); + synchronized (mBlockedRatings) { + for (TvContentRating rating : mBlockedRatings) { + serializer.startTag(null, TAG_RATING); + serializer.attribute(null, ATTR_STRING, rating.flattenToString()); + serializer.endTag(null, TAG_RATING); + } } serializer.endTag(null, TAG_BLOCKED_RATINGS); serializer.startTag(null, TAG_PARENTAL_CONTROLS); diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java index e7dd82d..c7d95aa 100644 --- a/services/core/java/com/android/server/tv/TvInputHal.java +++ b/services/core/java/com/android/server/tv/TvInputHal.java @@ -19,12 +19,11 @@ package com.android.server.tv; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvStreamConfig; import android.os.Handler; -import android.os.HandlerThread; import android.os.Message; -import android.view.Surface; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.view.Surface; import java.util.LinkedList; import java.util.Queue; @@ -64,12 +63,12 @@ final class TvInputHal implements Handler.Callback { int generation); private static native void nativeClose(long ptr); - private Object mLock = new Object(); + private final Object mLock = new Object(); private long mPtr = 0; private final Callback mCallback; private final Handler mHandler; - private SparseIntArray mStreamConfigGenerations = new SparseIntArray(); - private SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>();; + private final SparseIntArray mStreamConfigGenerations = new SparseIntArray(); + private final SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>(); public TvInputHal(Callback callback) { mCallback = callback; @@ -153,7 +152,7 @@ final class TvInputHal implements Handler.Callback { // Handler.Callback implementation - private Queue<Message> mPendingMessageQueue = new LinkedList<Message>(); + private final Queue<Message> mPendingMessageQueue = new LinkedList<Message>(); @Override public boolean handleMessage(Message msg) { diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 78ea6c9..fa991c2 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -20,14 +20,12 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; import android.content.Context; -import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; import android.hardware.hdmi.IHdmiControlService; import android.hardware.hdmi.IHdmiDeviceEventListener; import android.hardware.hdmi.IHdmiHotplugEventListener; -import android.hardware.hdmi.IHdmiInputChangeListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.media.AudioDevicePort; import android.media.AudioFormat; @@ -39,7 +37,6 @@ import android.media.AudioPort; import android.media.AudioPortConfig; import android.media.tv.ITvInputHardware; import android.media.tv.ITvInputHardwareCallback; -import android.media.tv.TvContract; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvStreamConfig; @@ -77,7 +74,6 @@ import java.util.Map; class TvInputHardwareManager implements TvInputHal.Callback { private static final String TAG = TvInputHardwareManager.class.getSimpleName(); - private final Context mContext; private final Listener mListener; private final TvInputHal mHal = new TvInputHal(this); private final SparseArray<Connection> mConnections = new SparseArray<>(); @@ -107,7 +103,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { private final Object mLock = new Object(); public TvInputHardwareManager(Context context, Listener listener) { - mContext = context; mListener = listener; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mHal.init(); @@ -285,7 +280,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { return -1; } - public void addHdmiTvInput(int logicalAddress, TvInputInfo info) { + public void addHdmiTvInput(int id, TvInputInfo info) { if (info.getType() != TvInputInfo.TYPE_HDMI) { throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); } @@ -295,13 +290,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (parentIndex < 0) { throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); } - String oldInputId = mHdmiInputIdMap.get(logicalAddress); + String oldInputId = mHdmiInputIdMap.get(id); if (oldInputId != null) { Slog.w(TAG, "Trying to override previous registration: old = " - + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = " - + info + ":" + logicalAddress); + + mInputMap.get(oldInputId) + ":" + id + ", new = " + + info + ":" + id); } - mHdmiInputIdMap.put(logicalAddress, info.getId()); + mHdmiInputIdMap.put(id, info.getId()); mInputMap.put(info.getId(), info); } } @@ -963,7 +958,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { Object obj = null; switch (status) { case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { - if (!mHdmiDeviceList.contains(deviceInfo)) { + if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { mHdmiDeviceList.add(deviceInfo); } else { Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); @@ -974,7 +969,8 @@ class TvInputHardwareManager implements TvInputHal.Callback { break; } case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { - if (!mHdmiDeviceList.remove(deviceInfo)) { + HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); + if (!mHdmiDeviceList.remove(originalDeviceInfo)) { Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); return; } @@ -983,13 +979,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { break; } case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { - if (!mHdmiDeviceList.remove(deviceInfo)) { + HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); + if (!mHdmiDeviceList.remove(originalDeviceInfo)) { Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); return; } mHdmiDeviceList.add(deviceInfo); messageType = ListenerHandler.HDMI_DEVICE_UPDATED; - String inputId = mHdmiInputIdMap.get(deviceInfo.getLogicalAddress()); + String inputId = mHdmiInputIdMap.get(deviceInfo.getId()); SomeArgs args = SomeArgs.obtain(); args.arg1 = inputId; args.arg2 = deviceInfo; @@ -1006,6 +1003,15 @@ class TvInputHardwareManager implements TvInputHal.Callback { } } } + + private HdmiDeviceInfo findHdmiDeviceInfo(int id) { + for (HdmiDeviceInfo info : mHdmiDeviceList) { + if (info.getId() == id) { + return info; + } + } + return null; + } } private final class HdmiSystemAudioModeChangeListener extends diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 3b5e79d..6481944 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -283,7 +283,6 @@ public final class TvInputManagerService extends SystemService { userState.inputMap = inputMap; userState.ratingSystemXmlUriSet.clear(); - userState.ratingSystemXmlUriSet.add(TvContentRating.SYSTEM_CONTENT_RATING_SYSTEM_XML); for (TvInputState state : userState.inputMap.values()) { Uri ratingSystemXmlUri = state.mInfo.getRatingSystemXmlUri(); if (ratingSystemXmlUri != null) { @@ -691,6 +690,10 @@ public final class TvInputManagerService extends SystemService { private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) { SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); if (sessionState.mSession != null) { + UserState userState = getUserStateLocked(userId); + if (sessionToken == userState.mainSessionToken) { + setMainLocked(sessionToken, false, callingUid, userId); + } try { sessionState.mSession.release(); } catch (RemoteException e) { @@ -704,6 +707,9 @@ public final class TvInputManagerService extends SystemService { private void removeSessionStateLocked(IBinder sessionToken, int userId) { UserState userState = getUserStateLocked(userId); if (sessionToken == userState.mainSessionToken) { + if (DEBUG) { + Slog.d(TAG, "mainSessionToken=null"); + } userState.mainSessionToken = null; } @@ -740,6 +746,25 @@ public final class TvInputManagerService extends SystemService { mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget(); } + private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId); + if (sessionState.mHardwareSessionToken != null) { + sessionState = getSessionStateLocked(sessionState.mHardwareSessionToken, + Process.SYSTEM_UID, userId); + } + ServiceState serviceState = getServiceStateLocked(sessionState.mInfo.getComponent(), + userId); + if (!serviceState.mIsHardware) { + return; + } + ITvInputSession session = getSessionLocked(sessionState); + try { + session.setMain(isMain); + } catch (RemoteException e) { + Slog.e(TAG, "error in setMain", e); + } + } + private void notifyInputAddedLocked(UserState userState, String inputId) { if (DEBUG) { Slog.d(TAG, "notifyInputAdded: inputId = " + inputId); @@ -1050,6 +1075,9 @@ public final class TvInputManagerService extends SystemService { @Override public void releaseSession(IBinder sessionToken, int userId) { + if (DEBUG) { + Slog.d(TAG, "releaseSession(): " + sessionToken); + } final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "releaseSession"); @@ -1065,6 +1093,9 @@ public final class TvInputManagerService extends SystemService { @Override public void setMainSession(IBinder sessionToken, int userId) { + if (DEBUG) { + Slog.d(TAG, "setMainSession(): " + sessionToken); + } final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "setMainSession"); @@ -1075,50 +1106,19 @@ public final class TvInputManagerService extends SystemService { if (userState.mainSessionToken == sessionToken) { return; } - - SessionState newMainSessionState = getSessionStateLocked( - sessionToken, callingUid, resolvedUserId); - if (newMainSessionState.mHardwareSessionToken != null) { - newMainSessionState = getSessionStateLocked( - newMainSessionState.mHardwareSessionToken, - Process.SYSTEM_UID, resolvedUserId); - } - ServiceState newMainServiceState = getServiceStateLocked( - newMainSessionState.mInfo.getComponent(), resolvedUserId); - ITvInputSession newMainSession = getSessionLocked(newMainSessionState); - - ServiceState oldMainServiceState = null; - ITvInputSession oldMainSession = null; - if (userState.mainSessionToken != null) { - SessionState oldMainSessionState = getSessionStateLocked( - userState.mainSessionToken, Process.SYSTEM_UID, resolvedUserId); - if (oldMainSessionState.mHardwareSessionToken != null) { - oldMainSessionState = getSessionStateLocked( - oldMainSessionState.mHardwareSessionToken, - Process.SYSTEM_UID, resolvedUserId); - } - oldMainServiceState = getServiceStateLocked( - oldMainSessionState.mInfo.getComponent(), resolvedUserId); - oldMainSession = getSessionLocked(oldMainSessionState); + if (DEBUG) { + Slog.d(TAG, "mainSessionToken=" + sessionToken); } - + IBinder oldMainSessionToken = userState.mainSessionToken; userState.mainSessionToken = sessionToken; // Inform the new main session first. - // See {@link TvInputService#onSetMainSession}. - if (newMainServiceState.mIsHardware) { - try { - newMainSession.setMainSession(true); - } catch (RemoteException e) { - Slog.e(TAG, "error in setMainSession", e); - } + // See {@link TvInputService.Session#onSetMain}. + if (sessionToken != null) { + setMainLocked(sessionToken, true, callingUid, userId); } - if (oldMainSession != null && oldMainServiceState.mIsHardware) { - try { - oldMainSession.setMainSession(false); - } catch (RemoteException e) { - Slog.e(TAG, "error in setMainSession", e); - } + if (oldMainSessionToken != null) { + setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId); } } } finally { @@ -1223,7 +1223,7 @@ public final class TvInputManagerService extends SystemService { try { getSessionLocked(sessionToken, callingUid, resolvedUserId).tune( channelUri, params); - if (TvContract.isChannelUriForPassthroughTvInput(channelUri)) { + if (TvContract.isChannelUriForPassthroughInput(channelUri)) { // Do not log the watch history for passthrough inputs. return; } @@ -1800,11 +1800,8 @@ public final class TvInputManagerService extends SystemService { UserState userState = getUserStateLocked(mUserId); for (SessionState sessionState : userState.sessionStateMap.values()) { if (mSessionToken == sessionState.mHardwareSessionToken) { - try { - sessionState.mSession.release(); - } catch (RemoteException e) { - Slog.e(TAG, "error in release", e); - } + releaseSessionLocked(sessionState.mSessionToken, Process.SYSTEM_UID, + mUserId); try { sessionState.mClient.onSessionReleased(sessionState.mSeq); } catch (RemoteException e) { @@ -1962,11 +1959,11 @@ public final class TvInputManagerService extends SystemService { } @Override - public void addHdmiTvInput(int logicalAddress, TvInputInfo inputInfo) { + public void addHdmiTvInput(int id, TvInputInfo inputInfo) { ensureHardwarePermission(); ensureValidInput(inputInfo); synchronized (mLock) { - mTvInputHardwareManager.addHdmiTvInput(logicalAddress, inputInfo); + mTvInputHardwareManager.addHdmiTvInput(id, inputInfo); addTvInputLocked(inputInfo); } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 3fcd067..b2575e6 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -252,6 +252,15 @@ class AppWindowToken extends WindowToken { return false; } + void removeAllWindows() { + for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; --winNdx) { + WindowState win = allAppWindows.get(winNdx); + if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG, + "removeAllWindows: removing win=" + win); + win.mService.removeWindowLocked(win.mSession, win); + } + } + @Override void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index dfb1200..238c77e 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -155,7 +155,7 @@ public class TaskStack { final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator; - if (winAnimator.isAnimating() && !winAnimator.isDummyAnimation()) { + if (winAnimator.isAnimating() || winAnimator.mWin.mExiting) { return true; } } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 32e8afe..4edd5cf 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -88,6 +88,8 @@ public class WindowAnimator { boolean mInitialized = false; boolean mKeyguardGoingAway; + boolean mKeyguardGoingAwayToNotificationShade; + boolean mKeyguardGoingAwayDisableWindowAnimations; // forceHiding states. static final int KEYGUARD_NOT_SHOWN = 0; @@ -389,7 +391,11 @@ public class WindowAnimator { if (unForceHiding != null) { boolean startKeyguardExit = true; for (int i=unForceHiding.size()-1; i>=0; i--) { - Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding); + Animation a = null; + if (!mKeyguardGoingAwayDisableWindowAnimations) { + a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding, + mKeyguardGoingAwayToNotificationShade); + } if (a != null) { final WindowStateAnimator winAnimator = unForceHiding.get(i); winAnimator.setAnimation(a); @@ -405,9 +411,14 @@ public class WindowAnimator { } // Wallpaper is going away in un-force-hide motion, animate it as well. - if (!wallpaperInUnForceHiding && wallpaper != null) { - WindowStateAnimator animator = wallpaper.mWinAnimator; - animator.setAnimation(mPolicy.createForceHideWallpaperExitAnimation()); + if (!wallpaperInUnForceHiding && wallpaper != null + && !mKeyguardGoingAwayDisableWindowAnimations) { + Animation a = mPolicy.createForceHideWallpaperExitAnimation( + mKeyguardGoingAwayToNotificationShade); + if (a != null) { + WindowStateAnimator animator = wallpaper.mWinAnimator; + animator.setAnimation(a); + } } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd9f050..b57a090 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -51,6 +51,7 @@ import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; import android.animation.ValueAnimator; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -58,6 +59,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; @@ -428,7 +430,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Stores for each user whether screencapture is disabled * This array is essentially a cache for all userId for - * {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled(null, userId)} + * {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled} */ SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<Boolean>(); @@ -537,8 +539,23 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>(); boolean mHardKeyboardAvailable; - boolean mHardKeyboardEnabled; + boolean mShowImeWithHardKeyboard; OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; + SettingsObserver mSettingsObserver; + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver() { + super(new Handler()); + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this); + } + + @Override + public void onChange(boolean selfChange) { + updateShowImeWithHardKeyboard(); + } + } final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); @@ -838,6 +855,9 @@ public class WindowManagerService extends IWindowManager.Stub filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, filter); + mSettingsObserver = new SettingsObserver(); + updateShowImeWithHardKeyboard(); + mHoldingScreenWakeLock = mPowerManager.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG); mHoldingScreenWakeLock.setReferenceCounted(false); @@ -2315,9 +2335,12 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (token.appWindowToken != null) { - Slog.i(TAG, "Non-null appWindowToken for system window of type=" + type); - // app token should be null for any other window types. - token.appWindowToken = null; + Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type); + // It is not valid to use an app token with other system types; we will + // instead make a new token for it (as if null had been passed in for the token). + attrs.token = null; + token = new WindowToken(this, null, -1, false); + addToken = true; } win = new WindowState(this, session, client, token, @@ -3592,7 +3615,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } final Task oldTask = mTaskIdToTask.get(atoken.groupId); - removeAppFromTaskLocked(atoken); + oldTask.removeAppToken(atoken); atoken.groupId = groupId; Task newTask = mTaskIdToTask.get(groupId); @@ -4634,6 +4657,8 @@ public class WindowManagerService extends IWindowManager.Stub } void removeAppFromTaskLocked(AppWindowToken wtoken) { + wtoken.removeAllWindows(); + final Task task = mTaskIdToTask.get(wtoken.groupId); if (task != null) { if (!task.removeAppToken(wtoken)) { @@ -5243,13 +5268,16 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void keyguardGoingAway() { + public void keyguardGoingAway(boolean disableWindowAnimations, + boolean keyguardGoingToNotificationShade) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } synchronized (mWindowMap) { mAnimator.mKeyguardGoingAway = true; + mAnimator.mKeyguardGoingAwayToNotificationShade = keyguardGoingToNotificationShade; + mAnimator.mKeyguardGoingAwayDisableWindowAnimations = disableWindowAnimations; requestTraversalLocked(); } } @@ -5264,6 +5292,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { if (mKeyguardWaitingForActivityDrawn) { mPolicy.notifyActivityDrawnForKeyguardLw(); + mKeyguardWaitingForActivityDrawn = false; } } } @@ -7009,11 +7038,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; if (hardKeyboardAvailable != mHardKeyboardAvailable) { mHardKeyboardAvailable = hardKeyboardAvailable; - mHardKeyboardEnabled = hardKeyboardAvailable; mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); } - if (!mHardKeyboardEnabled) { + if (mShowImeWithHardKeyboard) { config.keyboard = Configuration.KEYBOARD_NOKEYS; } @@ -7033,18 +7061,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - public boolean isHardKeyboardEnabled() { + public void updateShowImeWithHardKeyboard() { + boolean showImeWithHardKeyboard = Settings.Secure.getIntForUser( + mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, + mCurrentUserId) == 1; synchronized (mWindowMap) { - return mHardKeyboardEnabled; - } - } - - public void setHardKeyboardEnabled(boolean enabled) { - synchronized (mWindowMap) { - if (mHardKeyboardEnabled != enabled) { - mHardKeyboardEnabled = enabled; - mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); - } + mShowImeWithHardKeyboard = showImeWithHardKeyboard; } } @@ -7056,15 +7078,14 @@ public class WindowManagerService extends IWindowManager.Stub } void notifyHardKeyboardStatusChange() { - final boolean available, enabled; + final boolean available; final OnHardKeyboardStatusChangeListener listener; synchronized (mWindowMap) { listener = mHardKeyboardStatusChangeListener; available = mHardKeyboardAvailable; - enabled = mHardKeyboardEnabled; } if (listener != null) { - listener.onHardKeyboardStatusChange(available, enabled); + listener.onHardKeyboardStatusChange(available); } } @@ -11047,7 +11068,7 @@ public class WindowManagerService extends IWindowManager.Stub } public interface OnHardKeyboardStatusChangeListener { - public void onHardKeyboardStatusChange(boolean available, boolean enabled); + public void onHardKeyboardStatusChange(boolean available); } void debugLayoutRepeats(final String msg, int pendingLayoutChanges) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 61c50d6..7bf090a 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1211,6 +1211,12 @@ class WindowStateAnimator { || w.mDecorFrame.isEmpty()) { // The universe background isn't cropped, nor windows without policy decor. w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); + } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) { + // If we're animating, the wallpaper crop should only be updated at the end of the + // animation. + mTmpClipRect.set(w.mSystemDecorRect); + applyDecorRect(w.mDecorFrame); + w.mSystemDecorRect.union(mTmpClipRect); } else { // Crop to the system decor specified by policy. applyDecorRect(w.mDecorFrame); diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index ce2ca9b..39b70a8 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,6 +41,8 @@ int register_android_server_hdmi_HdmiCecController(JNIEnv* env); int register_android_server_hdmi_HdmiMhlController(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); +int register_android_server_fingerprint_FingerprintService(JNIEnv* env); +int register_android_server_Watchdog(JNIEnv* env); }; using namespace android; @@ -77,6 +79,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_hdmi_HdmiMhlController(env); register_android_server_tv_TvInputHal(env); register_android_server_PersistentDataBlockService(env); + register_android_server_fingerprint_FingerprintService(env); + register_android_server_Watchdog(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/Android.mk b/services/devicepolicy/Android.mk index a55d138..7020f17 100644 --- a/services/devicepolicy/Android.mk +++ b/services/devicepolicy/Android.mk @@ -7,6 +7,6 @@ LOCAL_MODULE := services.devicepolicy LOCAL_SRC_FILES += \ $(call all-java-files-under,java) -LOCAL_JAVA_LIBRARIES := conscrypt +LOCAL_JAVA_LIBRARIES := conscrypt services.core include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fc96991..21ba31e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -18,17 +18,6 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; -import android.app.admin.DevicePolicyManagerInternal; -import com.android.internal.R; -import com.android.internal.os.storage.ExternalStorageFormatter; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.JournaledFile; -import com.android.internal.util.XmlUtils; -import com.android.internal.widget.LockPatternUtils; -import com.android.org.conscrypt.TrustedCertificateStore; -import com.android.server.LocalServices; -import com.android.server.SystemService; - import android.app.Activity; import android.app.ActivityManagerNative; import android.app.AlarmManager; @@ -40,6 +29,7 @@ import android.app.PendingIntent; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -58,6 +48,7 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; +import android.hardware.usb.UsbManager; import android.net.ProxyInfo; import android.os.Binder; import android.os.Bundle; @@ -88,6 +79,16 @@ import android.util.SparseArray; import android.util.Xml; import android.view.IWindowManager; +import com.android.internal.R; +import com.android.internal.os.storage.ExternalStorageFormatter; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.JournaledFile; +import com.android.internal.util.XmlUtils; +import com.android.internal.widget.LockPatternUtils; +import com.android.org.conscrypt.TrustedCertificateStore; +import com.android.server.LocalServices; +import com.android.server.SystemService; + import org.xmlpull.v1.XmlPullParser; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -145,10 +146,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; + private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS; + static { + DEVICE_OWNER_USER_RESTRICTIONS = new HashSet(); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_USB_FILE_TRANSFER); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_TETHERING); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FACTORY_RESET); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADD_USER); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_UNMUTE_MICROPHONE); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADJUST_VOLUME); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS); + } + final Context mContext; final UserManager mUserManager; final PowerManager.WakeLock mWakeLock; + final LocalService mLocalService; + IPowerManager mIPowerManager; IWindowManager mIWindowManager; NotificationManager mNotificationManager; @@ -774,6 +792,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { PackageManager.FEATURE_DEVICE_ADMIN); mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); + mLocalService = new LocalService(); if (!mHasFeature) { // Skip the rest of the initialization return; @@ -794,7 +813,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { filter.addDataScheme("package"); context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); - LocalServices.addService(DevicePolicyManagerInternal.class, new LocalService()); + LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService); } /** @@ -1515,6 +1534,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .setContentIntent(notifyIntent) .setPriority(Notification.PRIORITY_HIGH) .setShowWhen(false) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)) .build(); // If this is a boot intent, this will fire for each user. But if this is a storage changed @@ -1881,44 +1902,71 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) { - ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (activeAdmin.crossProfileWidgetProviders == null) { - activeAdmin.crossProfileWidgetProviders = new ArrayList<>(); + final int userId = UserHandle.getCallingUserId(); + List<String> changedProviders = null; + + synchronized (this) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null) { + activeAdmin.crossProfileWidgetProviders = new ArrayList<>(); + } + List<String> providers = activeAdmin.crossProfileWidgetProviders; + if (!providers.contains(packageName)) { + providers.add(packageName); + changedProviders = new ArrayList<>(providers); + saveSettingsLocked(userId); + } } - if (activeAdmin.crossProfileWidgetProviders.add(packageName)) { - saveSettingsLocked(UserHandle.getCallingUserId()); + + if (changedProviders != null) { + mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders); return true; } + return false; } @Override public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) { - ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (activeAdmin.crossProfileWidgetProviders == null) { - return false; + final int userId = UserHandle.getCallingUserId(); + List<String> changedProviders = null; + + synchronized (this) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null) { + return false; + } + List<String> providers = activeAdmin.crossProfileWidgetProviders; + if (providers.remove(packageName)) { + changedProviders = new ArrayList<>(providers); + saveSettingsLocked(userId); + } } - if (activeAdmin.crossProfileWidgetProviders.remove(packageName)) { - saveSettingsLocked(UserHandle.getCallingUserId()); + + if (changedProviders != null) { + mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders); return true; } + return false; } @Override public List<String> getCrossProfileWidgetProviders(ComponentName admin) { - ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (activeAdmin.crossProfileWidgetProviders == null - || activeAdmin.crossProfileWidgetProviders.isEmpty()) { - return null; - } - if (Binder.getCallingUid() == Process.myUid()) { - return new ArrayList<>(activeAdmin.crossProfileWidgetProviders); - } else { - return activeAdmin.crossProfileWidgetProviders; + synchronized (this) { + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (activeAdmin.crossProfileWidgetProviders == null + || activeAdmin.crossProfileWidgetProviders.isEmpty()) { + return null; + } + if (Binder.getCallingUid() == Process.myUid()) { + return new ArrayList<>(activeAdmin.crossProfileWidgetProviders); + } else { + return activeAdmin.crossProfileWidgetProviders; + } } } @@ -3417,8 +3465,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - mUserManager.setUserRestrictions(new Bundle(), - new UserHandle(UserHandle.USER_OWNER)); + clearUserRestrictions(new UserHandle(UserHandle.USER_OWNER)); if (mDeviceOwner != null) { mDeviceOwner.clearDeviceOwner(); mDeviceOwner.writeOwnerFile(); @@ -3481,7 +3528,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - mUserManager.setUserRestrictions(new Bundle(), callingUser); + clearUserRestrictions(callingUser); if (mDeviceOwner != null) { mDeviceOwner.removeProfileOwner(callingUser.getIdentifier()); mDeviceOwner.writeOwnerFile(); @@ -3492,6 +3539,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void clearUserRestrictions(UserHandle userHandle) { + AudioManager audioManager = + (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Bundle userRestrictions = mUserManager.getUserRestrictions(); + mUserManager.setUserRestrictions(new Bundle(), userHandle); + if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) { + audioManager.setMasterMute(false); + } + if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) { + audioManager.setMicrophoneMute(false); + } + } + @Override public boolean hasUserSetupCompleted() { if (!mHasFeature) { @@ -4030,11 +4090,66 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who == null) { throw new NullPointerException("ComponentName is null"); } - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + ActiveAdmin activeAdmin = + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + boolean isDeviceOwner = isDeviceOwner(activeAdmin.info.getPackageName()); + if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) { + throw new SecurityException("Profile owners cannot set user restriction " + key); + } long id = Binder.clearCallingIdentity(); try { + AudioManager audioManager = + (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + boolean alreadyRestricted = mUserManager.hasUserRestriction(key); + + if (enabled && !alreadyRestricted) { + if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_USB_FILE_TRANSFER.equals(key)) { + UsbManager manager = + (UsbManager) mContext.getSystemService(Context.USB_SERVICE); + manager.setCurrentFunction("none", false); + } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, + userHandle.getIdentifier()); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) { + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.ADB_ENABLED, "0", userHandle.getIdentifier()); + } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) { + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", + userHandle.getIdentifier()); + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.INSTALL_NON_MARKET_APPS, 0, + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + audioManager.setMicrophoneMute(true); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + audioManager.setMasterMute(true); + } + } + mUserManager.setUserRestriction(key, enabled, userHandle); + + if (!enabled && alreadyRestricted) { + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + audioManager.setMicrophoneMute(false); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + audioManager.setMasterMute(false); + } + } + } finally { restoreCallingIdentity(id); } @@ -4378,44 +4493,39 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * This function can only be called by the device owner. * @param components The list of components allowed to enter lock task mode. */ - public void setLockTaskPackages(String[] packages) throws SecurityException { - // Get the package names of the caller. - int uid = Binder.getCallingUid(); - String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); - + public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { synchronized (this) { - // Check whether any of the package name is the device owner. - for (int i=0; i<packageNames.length; i++) { - String packageName = packageNames[i]; - int userHandle = UserHandle.getUserId(uid); - if (isDeviceOwner(packageName)) { - - // If a package name is the device owner, - // we update the component list. - DevicePolicyData policy = getUserData(userHandle); - policy.mLockTaskPackages.clear(); - if (packages != null) { - for (int j = 0; j < packages.length; j++) { - String pkg = packages[j]; - policy.mLockTaskPackages.add(pkg); - } - } + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - // Store the settings persistently. - saveSettingsLocked(userHandle); - return; + int userHandle = Binder.getCallingUserHandle().getIdentifier(); + DevicePolicyData policy = getUserData(userHandle); + policy.mLockTaskPackages.clear(); + if (packages != null) { + for (int j = 0; j < packages.length; j++) { + String pkg = packages[j]; + policy.mLockTaskPackages.add(pkg); } } + + // Store the settings persistently. + saveSettingsLocked(userHandle); } - throw new SecurityException(); } /** * This function returns the list of components allowed to start the task lock mode. */ - public String[] getLockTaskPackages() { + public String[] getLockTaskPackages(ComponentName who) { synchronized (this) { - int userHandle = UserHandle.USER_OWNER; + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + int userHandle = Binder.getCallingUserHandle().getIdentifier(); DevicePolicyData policy = getUserData(userHandle); return policy.mLockTaskPackages.toArray(new String[0]); } @@ -4453,15 +4563,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { final DevicePolicyData policy = getUserData(userHandle); Bundle adminExtras = new Bundle(); - adminExtras.putBoolean(DeviceAdminReceiver.EXTRA_LOCK_TASK_ENTERING, isEnabled); adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg); for (ActiveAdmin admin : policy.mAdminList) { boolean ownsDevice = isDeviceOwner(admin.info.getPackageName()); boolean ownsProfile = (getProfileOwner(userHandle) != null && getProfileOwner(userHandle).equals(admin.info.getPackageName())); if (ownsDevice || ownsProfile) { - sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_CHANGED, - adminExtras, null); + if (isEnabled) { + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING, + adminExtras, null); + } else { + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING); + } } } } @@ -4591,26 +4704,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private final class LocalService extends DevicePolicyManagerInternal { + private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners; + @Override public List<String> getCrossProfileWidgetProviders(int profileId) { - ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId); - if (ownerComponent == null) { - return Collections.emptyList(); - } + synchronized (DevicePolicyManagerService.this) { + ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId); + if (ownerComponent == null) { + return Collections.emptyList(); + } - DevicePolicyData policy = getUserData(profileId); - ActiveAdmin admin = policy.mAdminMap.get(ownerComponent); + DevicePolicyData policy = getUserData(profileId); + ActiveAdmin admin = policy.mAdminMap.get(ownerComponent); - if (admin == null) { - return Collections.emptyList(); + if (admin == null || admin.crossProfileWidgetProviders == null + || admin.crossProfileWidgetProviders.isEmpty()) { + return Collections.emptyList(); + } + + return admin.crossProfileWidgetProviders; } + } - if (admin.crossProfileWidgetProviders == null - || admin.crossProfileWidgetProviders.isEmpty()) { - return Collections.emptyList(); + @Override + public void addOnCrossProfileWidgetProvidersChangeListener( + OnCrossProfileWidgetProvidersChangeListener listener) { + synchronized (DevicePolicyManagerService.this) { + if (mWidgetProviderListeners == null) { + mWidgetProviderListeners = new ArrayList<>(); + } + if (!mWidgetProviderListeners.contains(listener)) { + mWidgetProviderListeners.add(listener); + } } + } - return admin.crossProfileWidgetProviders; + private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) { + final List<OnCrossProfileWidgetProvidersChangeListener> listeners; + synchronized (DevicePolicyManagerService.this) { + listeners = new ArrayList<>(mWidgetProviderListeners); + } + final int listenerCount = listeners.size(); + for (int i = 0; i < listenerCount; i++) { + OnCrossProfileWidgetProvidersChangeListener listener = listeners.get(i); + listener.onCrossProfileWidgetProvidersChanged(userId, packages); + } } } } diff --git a/services/print/Android.mk b/services/print/Android.mk index 33604b7..00eb2e4 100644 --- a/services/print/Android.mk +++ b/services/print/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.print LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 7400dde..6a56de0 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -783,7 +783,9 @@ public final class PrintManagerService extends SystemService { .setContentIntent(pendingIntent) .setWhen(System.currentTimeMillis()) .setAutoCancel(true) - .setShowWhen(true); + .setShowWhen(true) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk index fcf8626..57d1c46 100644 --- a/services/restrictions/Android.mk +++ b/services/restrictions/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.restrictions LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/usage/Android.mk b/services/usage/Android.mk index d4b7fa8..f1cbe98 100644 --- a/services/usage/Android.mk +++ b/services/usage/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.usage LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index c0923ca..c078cb2 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -732,6 +732,8 @@ public class UsbDeviceManager { "com.android.settings.UsbSettings")); PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + notification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, message, pi); notification.visibility = Notification.VISIBILITY_PUBLIC; mNotificationManager.notifyAsUser(null, id, notification, @@ -768,6 +770,8 @@ public class UsbDeviceManager { "com.android.settings.DevelopmentSettings")); PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + notification.color = mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, message, pi); notification.visibility = Notification.VISIBILITY_PUBLIC; mAdbNotificationShown = true; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index bcbae60..5860fc7 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -314,14 +314,22 @@ public class UsbHostManager { mConnectedUsbCard = cardsParser.getNumCardRecords() - 1; mConnectedUsbDeviceNum = 0; - if (!waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) { - return; - } - mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard); mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard); mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard); + // Playback device file needed/present? + if (mConnectedHasPlayback && + !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) { + return; + } + + // Capture device file needed/present? + if (mConnectedHasCapture && + !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, true)) { + return; + } + if (DEBUG_AUDIO) { Slog.d(TAG, "usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index cd3684b..a173a5c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -132,14 +132,33 @@ public class VoiceInteractionManagerService extends SystemService { public void initForUser(int userHandle) { if (DEBUG) Slog.i(TAG, "initForUser user=" + userHandle); - ComponentName curInteractor = getCurInteractor(userHandle); + String curInteractorStr = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); + VoiceInteractionServiceInfo curInteractorInfo = null; + if (curInteractorStr == null && curRecognizer != null) { + // If there is no interactor setting, that means we are upgrading + // from an older platform version. If the current recognizer is not + // set or matches the preferred recognizer, then we want to upgrade + // the user to have the default voice interaction service enabled. + curInteractorInfo = findAvailInteractor(userHandle, curRecognizer); + if (curInteractorInfo != null) { + // Looks good! We'll apply this one. To make it happen, we clear the + // recognizer so that we don't think we have anything set and will + // re-apply the settings. + curRecognizer = null; + } + } + if (curRecognizer != null) { // If we already have at least a recognizer, then we probably want to // leave things as they are... unless something has disappeared. IPackageManager pm = AppGlobals.getPackageManager(); ServiceInfo interactorInfo = null; ServiceInfo recognizerInfo = null; + ComponentName curInteractor = !TextUtils.isEmpty(curInteractorStr) + ? ComponentName.unflattenFromString(curInteractorStr) : null; try { recognizerInfo = pm.getServiceInfo(curRecognizer, 0, userHandle); if (curInteractor != null) { @@ -154,32 +173,27 @@ public class VoiceInteractionManagerService extends SystemService { } // Initializing settings, look for an interactor first. - curInteractor = findAvailInteractor(userHandle); - if (curInteractor != null) { - try { - VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( - mContext.getPackageManager(), curInteractor, userHandle); - if (info.getParseError() == null) { - setCurInteractor(curInteractor, userHandle); - if (info.getRecognitionService() != null) { - // Eventually it will be an error to not specify this. - curRecognizer = new ComponentName(info.getServiceInfo().packageName, - info.getRecognitionService()); - setCurRecognizer(curRecognizer, userHandle); - return; - } - } else { - Slog.w(TAG, "Bad interaction service " + curInteractor + ": " - + info.getParseError()); - } - } catch (PackageManager.NameNotFoundException e) { - } catch (RemoteException e) { + if (curInteractorInfo == null) { + curInteractorInfo = findAvailInteractor(userHandle, null); + } + if (curInteractorInfo != null) { + // Eventually it will be an error to not specify this. + setCurInteractor(new ComponentName(curInteractorInfo.getServiceInfo().packageName, + curInteractorInfo.getServiceInfo().name), userHandle); + if (curInteractorInfo.getRecognitionService() != null) { + setCurRecognizer( + new ComponentName(curInteractorInfo.getServiceInfo().packageName, + curInteractorInfo.getRecognitionService()), userHandle); + return; } } // No voice interactor, we'll just set up a simple recognizer. curRecognizer = findAvailRecognizer(null, userHandle); if (curRecognizer != null) { + if (curInteractorInfo == null) { + setCurInteractor(null, userHandle); + } setCurRecognizer(curRecognizer, userHandle); } } @@ -234,7 +248,7 @@ public class VoiceInteractionManagerService extends SystemService { } } - ComponentName findAvailInteractor(int userHandle) { + VoiceInteractionServiceInfo findAvailInteractor(int userHandle, ComponentName recognizer) { List<ResolveInfo> available = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(VoiceInteractionService.SERVICE_INTERFACE), 0, userHandle); @@ -246,23 +260,41 @@ public class VoiceInteractionManagerService extends SystemService { } else { // Find first system package. We never want to allow third party services to // be automatically selected, because those require approval of the user. - ServiceInfo serviceInfo = null; + VoiceInteractionServiceInfo foundInfo = null; for (int i=0; i<numAvailable; i++) { ServiceInfo cur = available.get(i).serviceInfo; if ((cur.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { - if (serviceInfo == null) { - serviceInfo = cur; - } else { - Slog.w(TAG, "more than one voice interaction service, picking first " - + new ComponentName(serviceInfo.packageName, serviceInfo.name) - + " over " - + new ComponentName(cur.packageName, cur.name)); + ComponentName comp = new ComponentName(cur.packageName, cur.name); + try { + VoiceInteractionServiceInfo info = new VoiceInteractionServiceInfo( + mContext.getPackageManager(), comp, userHandle); + if (info.getParseError() == null) { + if (recognizer == null || info.getServiceInfo().packageName.equals( + recognizer.getPackageName())) { + if (foundInfo == null) { + foundInfo = info; + } else { + Slog.w(TAG, "More than one voice interaction service, " + + "picking first " + + new ComponentName( + foundInfo.getServiceInfo().packageName, + foundInfo.getServiceInfo().name) + + " over " + + new ComponentName(cur.packageName, cur.name)); + } + } + } else { + Slog.w(TAG, "Bad interaction service " + comp + ": " + + info.getParseError()); + } + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Failure looking up interaction service " + comp); + } catch (RemoteException e) { } } } - return serviceInfo != null ? - new ComponentName(serviceInfo.packageName, serviceInfo.name) : null; + return foundInfo; } } |