Merge "WatchDog: dump hal pids when killing a process."
diff --git a/api/removed.txt b/api/removed.txt
index 8acf4ad..75da976 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a2fcbcd..3aa9398 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -374,16 +374,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 8acf4ad..75da976 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index ffc0f87..db17b28 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -53,15 +53,15 @@
String arg = nextArg();
if (arg.equals("backup")) {
- doFullBackup(OsConstants.STDOUT_FILENO);
+ doBackup(OsConstants.STDOUT_FILENO);
} else if (arg.equals("restore")) {
- doFullRestore(OsConstants.STDIN_FILENO);
+ doRestore(OsConstants.STDIN_FILENO);
} else {
Log.e(TAG, "Invalid operation '" + arg + "'");
}
}
- private void doFullBackup(int socketFd) {
+ private void doBackup(int socketFd) {
ArrayList<String> packages = new ArrayList<String>();
boolean saveApks = false;
boolean saveObbs = false;
@@ -70,6 +70,7 @@
boolean doWidgets = false;
boolean allIncludesSystem = true;
boolean doCompress = true;
+ boolean doKeyValue = false;
String arg;
while ((arg = nextArg()) != null) {
@@ -100,6 +101,8 @@
doCompress = true;
} else if ("-nocompress".equals(arg)) {
doCompress = false;
+ } else if ("-includekeyvalue".equals(arg)) {
+ doKeyValue = true;
} else {
Log.w(TAG, "Unknown backup flag " + arg);
continue;
@@ -123,8 +126,8 @@
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
String[] packArray = new String[packages.size()];
- mBackupManager.fullBackup(fd, saveApks, saveObbs, saveShared, doWidgets,
- doEverything, allIncludesSystem, doCompress, packages.toArray(packArray));
+ mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+ allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for backup");
} finally {
@@ -136,12 +139,12 @@
}
}
- private void doFullRestore(int socketFd) {
+ private void doRestore(int socketFd) {
// No arguments to restore
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
- mBackupManager.fullRestore(fd);
+ mBackupManager.adbRestore(fd);
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for restore");
} finally {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index db3772d..658d662 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -94,6 +94,8 @@
runGetFbeMode();
} else if ("fstrim".equals(op)) {
runFstrim();
+ } else if ("set-virtual-disk".equals(op)) {
+ runSetVirtualDisk();
} else {
throw new IllegalArgumentException();
}
@@ -225,6 +227,12 @@
mSm.fstrim(0);
}
+ public void runSetVirtualDisk() throws RemoteException {
+ final boolean virtualDisk = Boolean.parseBoolean(nextArg());
+ mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0,
+ StorageManager.DEBUG_VIRTUAL_DISK);
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -240,6 +248,7 @@
System.err.println(" sm has-adoptable");
System.err.println(" sm get-primary-storage-uuid");
System.err.println(" sm set-force-adoptable [true|false]");
+ System.err.println(" sm set-virtual-disk [true|false]");
System.err.println("");
System.err.println(" sm partition DISK [public|private|mixed] [ratio]");
System.err.println(" sm mount VOLUME");
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b9c888c..4c080c9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -367,6 +367,7 @@
// STOPSHIP: fix buggy apps
if (SystemProperties.getBoolean("fw.ignore_buggy", false)) return false;
if ("com.google.android.tts".equals(getApplicationInfo().packageName)) return true;
+ if ("com.breel.geswallpapers".equals(getApplicationInfo().packageName)) return true;
return false;
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 76828ee..a5dd5bd 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -56,6 +56,7 @@
public static final String APK_TREE_TOKEN = "a";
public static final String OBB_TREE_TOKEN = "obb";
+ public static final String KEY_VALUE_DATA_TOKEN = "k";
public static final String ROOT_TREE_TOKEN = "r";
public static final String FILES_TREE_TOKEN = "f";
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 59a941a..9c3b110 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -144,9 +144,10 @@
void backupNow();
/**
- * Write a full backup of the given package to the supplied file descriptor.
+ * Write a backup of the given package to the supplied file descriptor.
* The fd may be a socket or other non-seekable destination. If no package names
* are supplied, then every application on the device will be backed up to the output.
+ * Currently only used by the 'adb backup' command.
*
* <p>This method is <i>synchronous</i> -- it does not return until the backup has
* completed.
@@ -167,12 +168,14 @@
* as including packages pre-installed as part of the system. If {@code false},
* then setting {@code allApps} to {@code true} will mean only that all 3rd-party
* applications will be included in the dataset.
+ * @param doKeyValue If {@code true}, also packages supporting key-value backup will be backed
+ * up. If {@code false}, key-value packages will be skipped.
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
- void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
- boolean doCompress, in String[] packageNames);
+ boolean doCompress, boolean doKeyValue, in String[] packageNames);
/**
* Perform a full-dataset backup of the given applications via the currently active
@@ -184,11 +187,12 @@
/**
* Restore device content from the data stream passed through the given socket. The
- * data stream must be in the format emitted by fullBackup().
+ * data stream must be in the format emitted by adbBackup().
+ * Currently only used by the 'adb restore' command.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
*/
- void fullRestore(in ParcelFileDescriptor fd);
+ void adbRestore(in ParcelFileDescriptor fd);
/**
* Confirm that the requested full backup/restore operation can proceed. The system will
diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
index f82c9e2..ad20ce5 100644
--- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
+++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java
@@ -130,9 +130,11 @@
continue;
}
- mKeyphrasePackageMap.put(
- getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors),
- ai.packageName);
+ KeyphraseMetadata metadata =
+ getKeyphraseMetadataFromApplicationInfo(pm, ai, parseErrors);
+ if (metadata != null) {
+ mKeyphrasePackageMap.put(metadata, ai.packageName);
+ }
} catch (PackageManager.NameNotFoundException e) {
String error = "error parsing voice enrollment meta-data for "
+ ri.activityInfo.packageName;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2e35a51..76128e6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2039,14 +2039,20 @@
}
}
+ /** @deprecated use {@link android.system.Os#open(String, int, int)} */
+ @Deprecated
+ static native FileDescriptor openFileDescriptor(String file, int mode)
+ throws FileNotFoundException;
- /*package*/ static native FileDescriptor openFileDescriptor(String file,
- int mode) throws FileNotFoundException;
- /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
- throws IOException;
- /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
- throws IOException;
- /*package*/ static native void clearFileDescriptor(FileDescriptor desc);
+ /** @deprecated use {@link android.system.Os#dup(FileDescriptor)} */
+ @Deprecated
+ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) throws IOException;
+
+ /** @deprecated use {@link android.system.Os#close(FileDescriptor)} */
+ @Deprecated
+ static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
+
+ static native void clearFileDescriptor(FileDescriptor desc);
/**
* Read a byte value from the parcel at the current dataPosition().
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8882672..3212139 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -17,11 +17,21 @@
package android.os;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
import static android.system.OsConstants.SEEK_SET;
-import static android.system.OsConstants.SOCK_STREAM;
import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
@@ -33,6 +43,7 @@
import android.util.Log;
import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
import libcore.io.Memory;
@@ -279,8 +290,28 @@
"Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
}
+ int flags = 0;
+ switch (mode & MODE_READ_WRITE) {
+ case 0:
+ case MODE_READ_ONLY: flags = O_RDONLY; break;
+ case MODE_WRITE_ONLY: flags = O_WRONLY; break;
+ case MODE_READ_WRITE: flags = O_RDWR; break;
+ }
+
+ if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
+ if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
+ if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+
+ int realMode = S_IRWXU | S_IRWXG;
+ if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+ if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
final String path = file.getPath();
- return Parcel.openFileDescriptor(path, mode);
+ try {
+ return Os.open(path, flags, realMode);
+ } catch (ErrnoException e) {
+ throw new FileNotFoundException(e.getMessage());
+ }
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 53c9a23..7e1b5ab 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -113,6 +113,8 @@
public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
/** {@hide} */
public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+ /** {@hide} */
+ public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
@@ -140,6 +142,8 @@
public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 2;
/** {@hide} */
public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+ /** {@hide} */
+ public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
// NOTE: keep in sync with installd
/** {@hide} */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 58595c2..7005d44 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5511,6 +5511,16 @@
"accessibility_shortcut_target_service";
/**
+ * Setting specifying the accessibility service or feature to be toggled via the
+ * accessibility button in the navigation bar. This is either a flattened
+ * {@link ComponentName} or the class name of a system class implementing a supported
+ * accessibility feature.
+ * @hide
+ */
+ public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT =
+ "accessibility_button_target_component";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -6997,6 +7007,7 @@
TOUCH_EXPLORATION_ENABLED,
ACCESSIBILITY_ENABLED,
ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
ACCESSIBILITY_SHORTCUT_ENABLED,
ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
@@ -9747,6 +9758,15 @@
public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
/**
+ * Indicates the maximum time that an app is blocked for the network rules to get updated.
+ *
+ * Type: long
+ *
+ * @hide
+ */
+ public static final String NETWORK_ACCESS_TIMEOUT_MS = "network_access_timeout_ms";
+
+ /**
* The reason for the settings database being downgraded. This is only for
* troubleshooting purposes and its value should not be interpreted in any way.
*
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c9f9f31..35276cc 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -22,6 +22,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -97,6 +98,22 @@
/** @hide */
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+ "android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
static final Object sInstanceSync = new Object();
private static AccessibilityManager sInstance;
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46f7a81..dabbf31 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -70,26 +70,6 @@
public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
return LinksInfo.NO_OP;
}
-
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
};
/**
@@ -154,16 +134,4 @@
*/
LinksInfo getLinks(
@NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales);
-
- // TODO: Remove
- /** @removed */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
- // TODO: Remove
- /** @removed */
- TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex);
- // TODO: Remove
- /** @removed */
- LinksInfo getLinks(CharSequence text, int linkMask);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 06ac869..be12f57 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -120,10 +120,12 @@
SmartSelection.ClassificationResult[] results = getSmartSelection()
.classifyText(text.toString(), startIndex, endIndex);
if (results.length > 0) {
+ final TextClassificationResult classificationResult =
+ createClassificationResult(results, classified);
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format(
- "Classification type: %s", getHighestScoringType(results)));
- return createClassificationResult(results, classified);
+ "Classification type: %s", classificationResult));
+ return classificationResult;
}
}
} catch (Throwable t) {
@@ -149,26 +151,6 @@
return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
}
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
-
private SmartSelection getSmartSelection() throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
if (mSmartSelection == null) {
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
new file mode 100644
index 0000000..ee5d339
--- /dev/null
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.internal.app;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Activity used to display and persist a service or feature target for the Accessibility button.
+ */
+public class AccessibilityButtonChooserActivity extends Activity {
+
+ private static final String MAGNIFICATION_COMPONENT_ID =
+ "com.android.server.accessibility.MagnificationController";
+
+ private AccessibilityButtonTarget mMagnificationTarget = null;
+
+ private List<AccessibilityButtonTarget> mTargets = null;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.accessibility_button_chooser);
+
+ String component = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+ if (TextUtils.isEmpty(component)) {
+ TextView prompt = (TextView) findViewById(R.id.accessibility_button_prompt);
+ prompt.setVisibility(View.VISIBLE);
+ }
+
+ mMagnificationTarget = new AccessibilityButtonTarget(this, MAGNIFICATION_COMPONENT_ID,
+ R.string.accessibility_magnification_chooser_text,
+ R.drawable.resolver_icon_placeholder);
+
+ mTargets = getServiceAccessibilityButtonTargets(this);
+ if (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
+ mTargets.add(mMagnificationTarget);
+ }
+
+ if (mTargets.size() < 2) {
+ // Why are we here?
+ finish();
+ }
+
+ GridView gridview = (GridView) findViewById(R.id.accessibility_button_chooser_grid);
+ gridview.setAdapter(new TargetAdapter());
+ gridview.setOnItemClickListener((parent, view, position, id) -> {
+ onTargetSelected(mTargets.get(position));
+ });
+ }
+
+ private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets(
+ @NonNull Context context) {
+ AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ if (services == null) {
+ return Collections.emptyList();
+ }
+
+ ArrayList<AccessibilityButtonTarget> targets = new ArrayList<>(services.size());
+ for (AccessibilityServiceInfo info : services) {
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+ targets.add(new AccessibilityButtonTarget(context, info));
+ }
+ }
+
+ return targets;
+ }
+
+ private void onTargetSelected(AccessibilityButtonTarget target) {
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, target.getId());
+ finish();
+ }
+
+ private class TargetAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mTargets.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = AccessibilityButtonChooserActivity.this.getLayoutInflater();
+ View root = inflater.inflate(R.layout.accessibility_button_chooser_item, parent, false);
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ ImageView iconView = root.findViewById(R.id.accessibility_button_target_icon);
+ TextView labelView = root.findViewById(R.id.accessibility_button_target_label);
+ iconView.setImageDrawable(target.getDrawable());
+ labelView.setText(target.getLabel());
+ return root;
+ }
+ }
+
+ private static class AccessibilityButtonTarget {
+ public String mId;
+ public CharSequence mLabel;
+ public Drawable mDrawable;
+
+ public AccessibilityButtonTarget(@NonNull Context context,
+ @NonNull AccessibilityServiceInfo serviceInfo) {
+ this.mId = serviceInfo.getComponentName().flattenToString();
+ this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
+ this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
+ }
+
+ public AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
+ int iconRes) {
+ this.mId = id;
+ this.mLabel = context.getText(labelResId);
+ this.mDrawable = context.getDrawable(iconRes);
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ public Drawable getDrawable() {
+ return mDrawable;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8abd022..8721f34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3344,6 +3344,19 @@
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+ android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui">
+ <intent-filter>
+ <action android:name="android.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<activity android:name="com.android.internal.app.IntentForwarderActivity"
android:finishOnCloseSystemDialogs="true"
android:theme="@style/Theme.NoDisplay"
diff --git a/core/res/res/layout/accessibility_button_chooser.xml b/core/res/res/layout/accessibility_button_chooser.xml
new file mode 100644
index 0000000..0ef785f
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2017, 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.
+*/
+-->
+<com.android.internal.widget.ResolverDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxWidth="@dimen/resolver_max_width"
+ android:maxCollapsedHeight="256dp"
+ android:maxCollapsedHeightSmall="56dp"
+ android:id="@id/contentPanel">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="?attr/colorBackground"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="56dp"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:text="@string/accessibility_button_prompt_text"
+ android:gravity="start|center_vertical"
+ android:layout_alignParentStart="true"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"/>
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/accessibility_button_chooser_grid"
+ android:columnWidth="90dp"
+ android:numColumns="auto_fit"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:stretchMode="columnWidth"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:gravity="center"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/accessibility_button_prompt"
+ android:layout_alwaysShow="true"
+ android:textAppearance="?attr/textAppearanceMedium"
+ android:text="@string/accessibility_button_instructional_text"
+ android:gravity="start|center_vertical"
+ android:paddingStart="?attr/dialogPreferredPadding"
+ android:paddingEnd="?attr/dialogPreferredPadding"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:visibility="gone"/>
+ </LinearLayout>
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
new file mode 100644
index 0000000..76a9308
--- /dev/null
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="80dp"
+ android:gravity="center"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:background="?attr/selectableItemBackgroundBorderless">
+
+ <ImageView android:id="@+id/accessibility_button_target_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginLeft="3dp"
+ android:layout_marginRight="3dp"
+ android:layout_marginBottom="3dp"
+ android:scaleType="fitCenter"/>
+
+ <TextView android:id="@+id/accessibility_button_target_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginLeft="4dp"
+ android:layout_marginRight="4dp"
+ android:textAppearance="?attr/textAppearanceSmall"
+ android:textColor="?attr/textColorPrimary"
+ android:textSize="12sp"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="top|center_horizontal"
+ android:minLines="2"
+ android:maxLines="2"
+ android:ellipsize="marquee"/>
+</LinearLayout>
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 566ba02..868e256 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3958,6 +3958,15 @@
<string name="accessibility_shortcut_disabling_service">Accessibility Shortcut turned
<xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> off</string>
+ <!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_button_prompt_text">Choose a feature to use when you tap the Accessibility button:</string>
+
+ <!-- Text describing how to display UI allowing a user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_button_instructional_text">To change features, touch & hold the Accessibility button.</string>
+
+ <!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
+ <string name="accessibility_magnification_chooser_text">Magnification</string>
+
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f4d490a..b23c96c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2841,6 +2841,15 @@
<java-symbol type="string" name="leave_accessibility_shortcut_on" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+ <!-- Accessibility Button -->
+ <java-symbol type="layout" name="accessibility_button_chooser" />
+ <java-symbol type="layout" name="accessibility_button_chooser_item" />
+ <java-symbol type="id" name="accessibility_button_chooser_grid" />
+ <java-symbol type="id" name="accessibility_button_prompt" />
+ <java-symbol type="id" name="accessibility_button_target_icon" />
+ <java-symbol type="id" name="accessibility_button_target_label" />
+ <java-symbol type="string" name="accessibility_magnification_chooser_text" />
+
<!-- com.android.internal.widget.RecyclerView -->
<java-symbol type="id" name="item_touch_helper_previous_elevation"/>
<java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3e33dd8..782a50f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -328,6 +328,7 @@
Settings.Global.USE_GOOGLE_MAIL,
Settings.Global.VT_IMS_ENABLED,
Settings.Global.WAIT_FOR_DEBUGGER,
+ Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
Settings.Global.WARNING_TEMPERATURE,
Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
index 8f7d6ac..9e50490 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
@@ -25,6 +25,9 @@
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
@@ -805,6 +808,29 @@
mFailedToCompleteWithNoError = true;
String testResult;
+ final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ final MediaExtractor extractor = new MediaExtractor();
+ boolean hasSupportedVideo = false;
+
+ try {
+ extractor.setDataSource(filePath);
+
+ for (int index = 0; index < extractor.getTrackCount(); ++index) {
+ MediaFormat format = extractor.getTrackFormat(index);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ if (!mime.startsWith("video/")) {
+ continue;
+ }
+
+ if (list.findDecoderForFormat(format) != null) {
+ hasSupportedVideo = true;
+ break;
+ }
+ }
+ } finally {
+ extractor.release();
+ }
+
initializeMessageLooper();
synchronized (lock) {
try {
@@ -820,7 +846,12 @@
mMediaPlayer.setOnInfoListener(mInfoListener);
Log.v(TAG, "playMediaSamples: sample file name " + filePath);
mMediaPlayer.setDataSource(filePath);
- mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+ if (hasSupportedVideo) {
+ mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+ } else {
+ Log.i(TAG, "Set no display due to no (supported) video track.");
+ mMediaPlayer.setDisplay(null);
+ }
mMediaPlayer.prepare();
duration = mMediaPlayer.getDuration();
// start to play
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 4426495..12d31cf 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -21,7 +21,7 @@
<timezone id="America/St_Johns"></timezone>
<timezone id="America/Recife"></timezone>
<timezone id="America/Sao_Paulo"></timezone>
- <timezone id="America/Buenos_Aires"></timezone>
+ <timezone id="America/Argentina/Buenos_Aires"></timezone>
<timezone id="America/Godthab"></timezone>
<timezone id="America/Montevideo"></timezone>
<timezone id="Atlantic/South_Georgia"></timezone>
@@ -58,11 +58,11 @@
<timezone id="Asia/Karachi"></timezone>
<timezone id="Asia/Oral"></timezone>
<timezone id="Asia/Yekaterinburg"></timezone>
- <timezone id="Asia/Calcutta"></timezone>
+ <timezone id="Asia/Kolkata"></timezone>
<timezone id="Asia/Colombo"></timezone>
- <timezone id="Asia/Katmandu"></timezone>
+ <timezone id="Asia/Kathmandu"></timezone>
<timezone id="Asia/Almaty"></timezone>
- <timezone id="Asia/Rangoon"></timezone>
+ <timezone id="Asia/Yangon"></timezone>
<timezone id="Asia/Krasnoyarsk"></timezone>
<timezone id="Asia/Bangkok"></timezone>
<timezone id="Asia/Jakarta"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 55c886e..0280f26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -95,7 +95,7 @@
private WifiTrackerNetworkCallback mNetworkCallback;
- private boolean mSavedNetworksExist;
+ private int mNumSavedNetworks;
private boolean mRegistered;
/** Updated using main handler. Clone of this collection is returned
@@ -363,11 +363,11 @@
}
/**
- * @return true when there are saved networks on the device, regardless
- * of whether the WifiTracker is tracking saved networks.
+ * Returns the number of saved networks on the device, regardless of whether the WifiTracker
+ * is tracking saved networks.
*/
- public boolean doSavedNetworksExist() {
- return mSavedNetworksExist;
+ public int getNumSavedNetworks() {
+ return mNumSavedNetworks;
}
public boolean isConnected() {
@@ -461,11 +461,12 @@
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
- mSavedNetworksExist = configs.size() != 0;
+ mNumSavedNetworks = 0;
for (WifiConfiguration config : configs) {
if (config.selfAdded && config.numAssociation == 0) {
continue;
}
+ mNumSavedNetworks++;
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e100884..46726f2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -395,6 +395,26 @@
}
@Test
+ public void testGetNumSavedNetworks() throws InterruptedException {
+ WifiConfiguration validConfig = new WifiConfiguration();
+ validConfig.SSID = SSID_1;
+ validConfig.BSSID = BSSID_1;
+
+ WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
+ selfAddedNoAssociation.selfAdded = true;
+ selfAddedNoAssociation.numAssociation = 0;
+ selfAddedNoAssociation.SSID = SSID_2;
+ selfAddedNoAssociation.BSSID = BSSID_2;
+
+ when(mockWifiManager.getConfiguredNetworks())
+ .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
+
+ WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+
+ assertEquals(1, tracker.getNumSavedNetworks());
+ }
+
+ @Test
public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index a8629f8..14bb02d 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -51,7 +51,7 @@
<bool name="def_wifi_on">false</bool>
<!-- 0 == never, 1 == only when plugged in, 2 == always -->
<integer name="def_wifi_sleep_policy">2</integer>
- <bool name="def_wifi_wakeup_enabled">true</bool>
+ <bool name="def_wifi_wakeup_enabled">false</bool>
<bool name="def_networks_available_notification_on">true</bool>
<bool name="def_backup_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 37b155f..1a752f9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3343,24 +3343,6 @@
currentVersion = 142;
}
- if (currentVersion == 142) {
- // Version 142: Set a default value for Wi-Fi wakeup feature.
- if (userId == UserHandle.USER_SYSTEM) {
- final SettingsState globalSettings = getGlobalSettingsLocked();
- Setting currentSetting = globalSettings.getSettingLocked(
- Settings.Global.WIFI_WAKEUP_ENABLED);
- if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
- Settings.Global.WIFI_WAKEUP_ENABLED,
- getContext().getResources().getBoolean(
- R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
- null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
- }
-
- currentVersion = 143;
- }
-
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8580085..fbb075a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -95,6 +95,7 @@
*/
private final Class<?>[] SERVICES_PER_USER = new Class[] {
Dependency.class,
+ NotificationChannels.class,
Recents.class
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 5fb642f..1f03024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -557,9 +557,9 @@
}
private boolean onAccessibilityLongClick(View v) {
- // TODO(b/34720082): Target service selection via long click
- android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
- android.widget.Toast.LENGTH_LONG).show();
+ Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ v.getContext().startActivity(intent);
return true;
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index c091c41..783aae7 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -45,16 +45,16 @@
// The view or control was updated.
TYPE_UPDATE = 6;
- // Type for APP_TRANSITION event: The transition started a new activity for which it's process
- // wasn't running.
+ // Type for APP_TRANSITION event: The transition started a new
+ // activity for which it's process wasn't running.
TYPE_TRANSITION_COLD_LAUNCH = 7;
- // Type for APP_TRANSITION event: The transition started a new activity for which it's process
- // was already running.
+ // Type for APP_TRANSITION event: The transition started a new
+ // activity for which it's process was already running.
TYPE_TRANSITION_WARM_LAUNCH = 8;
- // Type for APP_TRANSITION event: The transition brought an already existing activity to the
- // front.
+ // Type for APP_TRANSITION event: The transition brought an
+ // already existing activity to the front.
TYPE_TRANSITION_HOT_LAUNCH = 9;
// The action was successful
@@ -64,6 +64,80 @@
TYPE_FAILURE = 11;
}
+ // Types of alerts, as bit field values
+ enum Alert {
+ // Vibrate the device.
+ ALERT_BUZZ = 1;
+
+ // Make sound through the speaker.
+ ALERT_BEEP = 2;
+
+ // Flash a notificaiton light.
+ ALERT_BLINK = 4;
+ }
+
+ // Reasons that a notification might be dismissed.
+ enum DismissReason {
+ // from android.service.notification.NotificationListenerService
+
+ // Notification was canceled by the status bar reporting a notification click
+ REASON_CLICK = 1;
+
+ // Notification was canceled by the status bar reporting a user dismissal.
+ REASON_CANCEL = 2;
+
+ // Notification was canceled by the status bar reporting a user dismiss all.
+ REASON_CANCEL_ALL = 3;
+
+ // Notification was canceled by the status bar reporting an inflation error.
+ REASON_ERROR = 4;
+
+ // Notification was canceled by the package manager modifying the package.
+ REASON_PACKAGE_CHANGED = 5;
+
+ // Notification was canceled by the owning user context being stopped.
+ REASON_USER_STOPPED = 6;
+
+ // Notification was canceled by the user banning the package.
+ REASON_PACKAGE_BANNED = 7;
+
+ // Notification was canceled by the app canceling this specific notification.
+ REASON_APP_CANCEL = 8;
+
+ //Notification was canceled by the app cancelling all its notifications.
+ REASON_APP_CANCEL_ALL = 9;
+
+ // Notification was canceled by a listener reporting a user dismissal.
+ REASON_LISTENER_CANCEL = 10;
+
+ //Notification was canceled by a listener reporting a user dismiss all.
+ REASON_LISTENER_CANCEL_ALL = 11;
+
+ // Notification was canceled because it was a member of a canceled group.
+ REASON_GROUP_SUMMARY_CANCELED = 12;
+
+ // Notification was canceled because it was an invisible member of a group.
+ REASON_GROUP_OPTIMIZATION = 13;
+
+ // Notification was canceled by the device administrator suspending the package.
+ REASON_PACKAGE_SUSPENDED = 14;
+
+ // Notification was canceled by the owning managed profile being turned off.
+ REASON_PROFILE_TURNED_OFF = 15;
+
+ // Autobundled summary notification was canceled because its group was unbundled.
+ REASON_UNAUTOBUNDLED = 16;
+
+ // Notification was canceled by the user banning the channel.
+ REASON_CHANNEL_BANNED = 17;
+
+ // Notification was snoozed.
+ REASON_SNOOZED = 18;
+
+ // Notification was canceled due to timeout.
+ REASON_TIMEOUT = 19;
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -3797,6 +3871,9 @@
// VALUE: The package name of the app
ACTION_SETTINGS_CLEAR_INSTANT_APP = 923;
+ // OPEN: Settings -> System -> Reset options
+ RESET_DASHBOARD = 924;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3666763..05c6592 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -70,6 +70,7 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.provider.SettingsStringUtil.ComponentNameSet;
import android.provider.SettingsStringUtil.SettingStringHelper;
import android.text.TextUtils;
@@ -100,7 +101,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
@@ -1154,17 +1154,55 @@
private void notifyAccessibilityButtonClickedLocked() {
final UserState state = getCurrentUserStateLocked();
- if (state.mIsNavBarMagnificationEnabled) {
- mMainHandler.obtainMessage(
- MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final Service service = state.mBoundServices.get(i);
- // TODO(b/34720082): Only notify a single user-defined service
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked();
+
+ int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ potentialTargets++;
+ }
+ }
+
+ if (potentialTargets == 0) {
+ return;
+ }
+ if (potentialTargets == 1) {
+ if (state.mIsNavBarMagnificationEnabled) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+ return;
+ } else {
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonClickedLocked();
+ return;
+ }
}
}
+ } else {
+ if (state.mServiceAssignedToAccessibilityButton == null
+ && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
+ } else if (state.mIsNavBarMagnificationEnabled
+ && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget();
+ return;
+ } else {
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
+ state.mServiceAssignedToAccessibilityButton))) {
+ service.notifyAccessibilityButtonClickedLocked();
+ return;
+ }
+ }
+ }
+ // The user may have turned off the assigned service or feature
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER).sendToTarget();
}
}
@@ -1534,6 +1572,12 @@
}
}
+ private void showAccessibilityButtonTargetSelection() {
+ Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ }
+
private void scheduleNotifyClientsOfServicesStateChange(UserState userState) {
mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS,
userState.mUserId).sendToTarget();
@@ -1681,6 +1725,7 @@
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
updateRelevantEventsLocked(userState);
+ updateAccessibilityButtonTargets(userState);
}
private void updateAccessibilityFocusBehaviorLocked(UserState userState) {
@@ -1794,6 +1839,7 @@
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
+ somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
return somethingChanged;
}
@@ -1928,6 +1974,37 @@
return true;
}
+ private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+ String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
+ if (TextUtils.isEmpty(componentId)) {
+ if ((userState.mServiceAssignedToAccessibilityButton == null)
+ && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = null;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ return true;
+ }
+
+ if (componentId.equals(MagnificationController.class.getName())) {
+ if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = null;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+ return true;
+ }
+
+ ComponentName componentName = ComponentName.unflattenFromString(componentId);
+ if (componentName.equals(userState.mServiceAssignedToAccessibilityButton)) {
+ return false;
+ }
+ userState.mServiceAssignedToAccessibilityButton = componentName;
+ userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ return true;
+ }
+
/**
* Check if the service that will be enabled by the shortcut is installed. If it isn't,
* clear the value and the associated setting so a sideloaded service can't spoof the
@@ -2138,6 +2215,22 @@
}
}
+ private void updateAccessibilityButtonTargets(UserState userState) {
+ final List<Service> services;
+ synchronized (mLock) {
+ services = userState.mBoundServices;
+ int numServices = services.size();
+ for (int i = 0; i < numServices; i++) {
+ final Service service = services.get(i);
+ if (service.mRequestAccessibilityButton) {
+ boolean available = service.mComponentName.equals(
+ userState.mServiceAssignedToAccessibilityButton);
+ service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+ }
+ }
+ }
+
@GuardedBy("mLock")
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
@@ -2212,7 +2305,7 @@
* Disables accessibility service specified by {@param componentName} for the {@param userId}.
*/
private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) {
- final SettingStringHelper setting =
+ final SettingsStringUtil.SettingStringHelper setting =
new SettingStringHelper(
mContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -2342,6 +2435,7 @@
public static final int MSG_UPDATE_FINGERPRINT = 11;
public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12;
public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13;
+ public static final int MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER = 14;
public MainHandler(Looper looper) {
super(looper);
@@ -2435,6 +2529,10 @@
mInputFilter.notifyAccessibilityButtonClicked();
}
}
+ } break;
+
+ case MSG_SHOW_ACCESSIBILITY_BUTTON_CHOOSER: {
+ showAccessibilityButtonTargetSelection();
}
}
}
@@ -4813,6 +4911,8 @@
public int mSoftKeyboardShowMode = 0;
public boolean mIsAccessibilityButtonAvailable;
+ public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+ public ComponentName mServiceAssignedToAccessibilityButton;
public boolean mIsTouchExplorationEnabled;
public boolean mIsTextHighContrastEnabled;
@@ -4888,6 +4988,8 @@
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
mIsNavBarMagnificationEnabled = false;
+ mServiceAssignedToAccessibilityButton = null;
+ mIsNavBarMagnificationAssignedToAccessibilityButton = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
@@ -4953,6 +5055,9 @@
private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+ private final Uri mAccessibilityButtonComponentIdUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4985,6 +5090,8 @@
mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -5042,6 +5149,10 @@
if (readAccessibilityShortcutSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
+ if (readAccessibilityButtonSettingsLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 30d06db..037804e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -222,12 +222,27 @@
// 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
// 3 : introduced "_meta" metadata file; no other format change per se
// 4 : added support for new device-encrypted storage locations
- static final int BACKUP_FILE_VERSION = 4;
+ // 5 : added support for key-value packages
+ static final int BACKUP_FILE_VERSION = 5;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
static final int BACKUP_PW_FILE_VERSION = 2;
static final String BACKUP_METADATA_FILENAME = "_meta";
static final int BACKUP_METADATA_VERSION = 1;
static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
+
+ static final int TAR_HEADER_LONG_RADIX = 8;
+ static final int TAR_HEADER_OFFSET_FILESIZE = 124;
+ static final int TAR_HEADER_LENGTH_FILESIZE = 12;
+ static final int TAR_HEADER_OFFSET_MODTIME = 136;
+ static final int TAR_HEADER_LENGTH_MODTIME = 12;
+ static final int TAR_HEADER_OFFSET_MODE = 100;
+ static final int TAR_HEADER_LENGTH_MODE = 8;
+ static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
+ static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
+ static final int TAR_HEADER_OFFSET_PATH = 0;
+ static final int TAR_HEADER_LENGTH_PATH = 100;
+ static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
+
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
static final String SETTINGS_PACKAGE = "com.android.providers.settings";
@@ -553,19 +568,20 @@
}
}
- class FullParams {
+ // Parameters used by adbBackup() and adbRestore()
+ class AdbParams {
public ParcelFileDescriptor fd;
public final AtomicBoolean latch;
public IFullBackupRestoreObserver observer;
public String curPassword; // filled in by the confirmation step
public String encryptPassword;
- FullParams() {
+ AdbParams() {
latch = new AtomicBoolean(false);
}
}
- class FullBackupParams extends FullParams {
+ class AdbBackupParams extends AdbParams {
public boolean includeApks;
public boolean includeObbs;
public boolean includeShared;
@@ -573,11 +589,12 @@
public boolean allApps;
public boolean includeSystem;
public boolean doCompress;
+ public boolean includeKeyValue;
public String[] packages;
- FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+ AdbBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
boolean saveShared, boolean alsoWidgets, boolean doAllApps, boolean doSystem,
- boolean compress, String[] pkgList) {
+ boolean compress, boolean doKeyValue, String[] pkgList) {
fd = output;
includeApks = saveApks;
includeObbs = saveObbs;
@@ -586,12 +603,13 @@
allApps = doAllApps;
includeSystem = doSystem;
doCompress = compress;
+ includeKeyValue = doKeyValue;
packages = pkgList;
}
}
- class FullRestoreParams extends FullParams {
- FullRestoreParams(ParcelFileDescriptor input) {
+ class AdbRestoreParams extends AdbParams {
+ AdbRestoreParams(ParcelFileDescriptor input) {
fd = input;
}
}
@@ -627,10 +645,10 @@
static final int OP_TIMEOUT = -1;
// Waiting for backup agent to respond during backup operation.
- private static final int OP_TYPE_BACKUP_WAIT = 0;
+ static final int OP_TYPE_BACKUP_WAIT = 0;
// Waiting for backup agent to respond during restore operation.
- private static final int OP_TYPE_RESTORE_WAIT = 1;
+ static final int OP_TYPE_RESTORE_WAIT = 1;
// An entire backup operation spanning multiple packages.
private static final int OP_TYPE_BACKUP = 2;
@@ -672,7 +690,7 @@
final Object mCurrentOpLock = new Object();
final Random mTokenGenerator = new Random();
- final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>();
+ final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<AdbParams>();
// Where we keep our journal files and other bookkeeping
File mBaseStateDir;
@@ -791,15 +809,9 @@
}
/* adb backup: is this app only capable of doing key/value? We say otherwise if
- * the app has a backup agent and does not say fullBackupOnly, *unless* it
- * is a package that we know _a priori_ explicitly supports both key/value and
- * full-data backup.
+ * the app has a backup agent and does not say fullBackupOnly,
*/
private static boolean appIsKeyValueOnly(PackageInfo pkg) {
- if ("com.android.providers.settings".equals(pkg.packageName)) {
- return false;
- }
-
return !appGetsFullBackup(pkg);
}
@@ -912,13 +924,12 @@
{
// TODO: refactor full backup to be a looper-based state machine
// similar to normal backup/restore.
- FullBackupParams params = (FullBackupParams)msg.obj;
+ AdbBackupParams params = (AdbBackupParams)msg.obj;
PerformAdbBackupTask task = new PerformAdbBackupTask(params.fd,
params.observer, params.includeApks, params.includeObbs,
- params.includeShared, params.doWidgets,
- params.curPassword, params.encryptPassword,
- params.allApps, params.includeSystem, params.doCompress,
- params.packages, params.latch);
+ params.includeShared, params.doWidgets, params.curPassword,
+ params.encryptPassword, params.allApps, params.includeSystem,
+ params.doCompress, params.includeKeyValue, params.packages, params.latch);
(new Thread(task, "adb-backup")).start();
break;
}
@@ -963,7 +974,7 @@
{
// TODO: refactor full restore to be a looper-based state machine
// similar to normal backup/restore.
- FullRestoreParams params = (FullRestoreParams)msg.obj;
+ AdbRestoreParams params = (AdbRestoreParams)msg.obj;
PerformAdbRestoreTask task = new PerformAdbRestoreTask(params.fd,
params.curPassword, params.encryptPassword,
params.observer, params.latch);
@@ -1071,16 +1082,16 @@
case MSG_FULL_CONFIRMATION_TIMEOUT:
{
- synchronized (mFullConfirmations) {
- FullParams params = mFullConfirmations.get(msg.arg1);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ AdbParams params = mAdbBackupRestoreConfirmations.get(msg.arg1);
if (params != null) {
Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation");
// Release the waiter; timeout == completion
- signalFullBackupRestoreCompletion(params);
+ signalAdbBackupRestoreCompletion(params);
// Remove the token from the set
- mFullConfirmations.delete(msg.arg1);
+ mAdbBackupRestoreConfirmations.delete(msg.arg1);
// Report a timeout to the observer, if any
if (params.observer != null) {
@@ -3719,7 +3730,7 @@
}
- private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+ static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
throws IOException {
// We do not take close() responsibility for the pipe FD
FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
@@ -3822,7 +3833,7 @@
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
- writeAppManifest(mPackage, mManifestFile, mSendApk, writeWidgetData);
+ writeAppManifest(mPackage, mPackageManager, mManifestFile, mSendApk, writeWidgetData);
FullBackup.backupToTar(mPackage.packageName, null, null,
mFilesDir.getAbsolutePath(),
mManifestFile.getAbsolutePath(),
@@ -4006,52 +4017,6 @@
}
}
- private void writeAppManifest(PackageInfo pkg, File manifestFile,
- boolean withApk, boolean withWidgets) throws IOException {
- // Manifest format. All data are strings ending in LF:
- // BACKUP_MANIFEST_VERSION, currently 1
- //
- // Version 1:
- // package name
- // package's versionCode
- // platform versionCode
- // getInstallerPackageName() for this package (maybe empty)
- // boolean: "1" if archive includes .apk; any other string means not
- // number of signatures == N
- // N*: signature byte array in ascii format per Signature.toCharsString()
- StringBuilder builder = new StringBuilder(4096);
- StringBuilderPrinter printer = new StringBuilderPrinter(builder);
-
- printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
- printer.println(pkg.packageName);
- printer.println(Integer.toString(pkg.versionCode));
- printer.println(Integer.toString(Build.VERSION.SDK_INT));
-
- String installerName = mPackageManager.getInstallerPackageName(pkg.packageName);
- printer.println((installerName != null) ? installerName : "");
-
- printer.println(withApk ? "1" : "0");
- if (pkg.signatures == null) {
- printer.println("0");
- } else {
- printer.println(Integer.toString(pkg.signatures.length));
- for (Signature sig : pkg.signatures) {
- printer.println(sig.toCharsString());
- }
- }
-
- FileOutputStream outstream = new FileOutputStream(manifestFile);
- outstream.write(builder.toString().getBytes());
- outstream.close();
-
- // We want the manifest block in the archive stream to be idempotent:
- // each time we generate a backup stream for the app, we want the manifest
- // block to be identical. The underlying tar mechanism sees it as a file,
- // though, and will propagate its mtime, causing the tar header to vary.
- // Avoid this problem by pinning the mtime to zero.
- manifestFile.setLastModified(0);
- }
-
// Widget metadata format. All header entries are strings ending in LF:
//
// Version 1 header:
@@ -4100,6 +4065,52 @@
}
}
+ static void writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile,
+ boolean withApk, boolean withWidgets) throws IOException {
+ // Manifest format. All data are strings ending in LF:
+ // BACKUP_MANIFEST_VERSION, currently 1
+ //
+ // Version 1:
+ // package name
+ // package's versionCode
+ // platform versionCode
+ // getInstallerPackageName() for this package (maybe empty)
+ // boolean: "1" if archive includes .apk; any other string means not
+ // number of signatures == N
+ // N*: signature byte array in ascii format per Signature.toCharsString()
+ StringBuilder builder = new StringBuilder(4096);
+ StringBuilderPrinter printer = new StringBuilderPrinter(builder);
+
+ printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
+ printer.println(pkg.packageName);
+ printer.println(Integer.toString(pkg.versionCode));
+ printer.println(Integer.toString(Build.VERSION.SDK_INT));
+
+ String installerName = packageManager.getInstallerPackageName(pkg.packageName);
+ printer.println((installerName != null) ? installerName : "");
+
+ printer.println(withApk ? "1" : "0");
+ if (pkg.signatures == null) {
+ printer.println("0");
+ } else {
+ printer.println(Integer.toString(pkg.signatures.length));
+ for (Signature sig : pkg.signatures) {
+ printer.println(sig.toCharsString());
+ }
+ }
+
+ FileOutputStream outstream = new FileOutputStream(manifestFile);
+ outstream.write(builder.toString().getBytes());
+ outstream.close();
+
+ // We want the manifest block in the archive stream to be idempotent:
+ // each time we generate a backup stream for the app, we want the manifest
+ // block to be identical. The underlying tar mechanism sees it as a file,
+ // though, and will propagate its mtime, causing the tar header to vary.
+ // Avoid this problem by pinning the mtime to zero.
+ manifestFile.setLastModified(0);
+ }
+
// Generic driver skeleton for full backup operations
abstract class FullBackupTask implements Runnable {
IFullBackupRestoreObserver mObserver;
@@ -4172,6 +4183,7 @@
boolean mAllApps;
boolean mIncludeSystem;
boolean mCompress;
+ boolean mKeyValue;
ArrayList<String> mPackages;
PackageInfo mCurrentTarget;
String mCurrentPassword;
@@ -4179,9 +4191,9 @@
private final int mCurrentOpToken;
PerformAdbBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
- boolean includeApks, boolean includeObbs, boolean includeShared,
- boolean doWidgets, String curPassword, String encryptPassword, boolean doAllApps,
- boolean doSystem, boolean doCompress, String[] packages, AtomicBoolean latch) {
+ boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+ String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
+ boolean doCompress, boolean doKeyValue, String[] packages, AtomicBoolean latch) {
super(observer);
mCurrentOpToken = generateToken();
mLatch = latch;
@@ -4210,6 +4222,7 @@
Slog.w(TAG, "Encrypting backup with passphrase=" + mEncryptPassword);
}
mCompress = doCompress;
+ mKeyValue = doKeyValue;
}
void addPackagesToSet(TreeMap<String, PackageInfo> set, List<String> pkgNames) {
@@ -4309,7 +4322,8 @@
@Override
public void run() {
- Slog.i(TAG, "--- Performing full-dataset adb backup ---");
+ String includeKeyValue = mKeyValue ? ", including key-value backups" : "";
+ Slog.i(TAG, "--- Performing adb backup" + includeKeyValue + " ---");
TreeMap<String, PackageInfo> packagesToBackup = new TreeMap<String, PackageInfo>();
FullBackupObbConnection obbConnection = new FullBackupObbConnection();
@@ -4361,14 +4375,26 @@
// Now we cull any inapplicable / inappropriate packages from the set. This
// includes the special shared-storage agent package; we handle that one
- // explicitly at the end of the backup pass.
+ // explicitly at the end of the backup pass. Packages supporting key-value backup are
+ // added to their own queue, and handled after packages supporting fullbackup.
+ ArrayList<PackageInfo> keyValueBackupQueue = new ArrayList<>();
Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
while (iter.hasNext()) {
PackageInfo pkg = iter.next().getValue();
if (!appIsEligibleForBackup(pkg.applicationInfo)
- || appIsStopped(pkg.applicationInfo)
- || appIsKeyValueOnly(pkg)) {
+ || appIsStopped(pkg.applicationInfo)) {
iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is not eligible for backup, removing.");
+ }
+ } else if (appIsKeyValueOnly(pkg)) {
+ iter.remove();
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkg.packageName
+ + " is key-value.");
+ }
+ keyValueBackupQueue.add(pkg);
}
}
@@ -4402,7 +4428,7 @@
// final '\n'.
//
// line 1: "ANDROID BACKUP"
- // line 2: backup file format version, currently "2"
+ // line 2: backup file format version, currently "5"
// line 3: compressed? "0" if not compressed, "1" if compressed.
// line 4: name of encryption algorithm [currently only "none" or "AES-256"]
//
@@ -4462,10 +4488,14 @@
}
}
- // Now actually run the constructed backup sequence
+ // Now actually run the constructed backup sequence for full backup
int N = backupQueue.size();
for (int i = 0; i < N; i++) {
pkg = backupQueue.get(i);
+ if (DEBUG) {
+ Slog.i(TAG,"--- Performing full backup for package " + pkg.packageName
+ + " ---");
+ }
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
@@ -4485,6 +4515,21 @@
}
}
}
+ // And for key-value backup if enabled
+ if (mKeyValue) {
+ for (PackageInfo keyValuePackage : keyValueBackupQueue) {
+ if (DEBUG) {
+ Slog.i(TAG, "--- Performing key-value backup for package "
+ + keyValuePackage.packageName + " ---");
+ }
+ KeyValueAdbBackupEngine kvBackupEngine =
+ new KeyValueAdbBackupEngine(out, keyValuePackage,
+ BackupManagerService.this,
+ mPackageManager, mBaseStateDir, mDataDir);
+ sendOnBackupPackage(keyValuePackage.packageName);
+ kvBackupEngine.backupOnePackage();
+ }
+ }
// Done!
finalizeBackup(out);
@@ -6693,19 +6738,24 @@
try {
// okay, presume we're okay, and extract the various metadata
info = new FileMetadata();
- info.size = extractRadix(block, 124, 12, 8);
- info.mtime = extractRadix(block, 136, 12, 8);
- info.mode = extractRadix(block, 100, 8, 8);
+ info.size = extractRadix(block, TAR_HEADER_OFFSET_FILESIZE,
+ TAR_HEADER_LENGTH_FILESIZE, TAR_HEADER_LONG_RADIX);
+ info.mtime = extractRadix(block, TAR_HEADER_OFFSET_MODTIME,
+ TAR_HEADER_LENGTH_MODTIME, TAR_HEADER_LONG_RADIX);
+ info.mode = extractRadix(block, TAR_HEADER_OFFSET_MODE,
+ TAR_HEADER_LENGTH_MODE, TAR_HEADER_LONG_RADIX);
- info.path = extractString(block, 345, 155); // prefix
- String path = extractString(block, 0, 100);
+ info.path = extractString(block, TAR_HEADER_OFFSET_PATH_PREFIX,
+ TAR_HEADER_LENGTH_PATH_PREFIX);
+ String path = extractString(block, TAR_HEADER_OFFSET_PATH,
+ TAR_HEADER_LENGTH_PATH);
if (path.length() > 0) {
if (info.path.length() > 0) info.path += '/';
info.path += path;
}
// tar link indicator field: 1 byte at offset 156 in the header.
- int typeChar = block[156];
+ int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
if (typeChar == 'x') {
// pax extended header, so we need to read that
gotHeader = readPaxExtendedHeader(instream, info);
@@ -6716,7 +6766,7 @@
}
if (!gotHeader) throw new IOException("Bad or missing pax header");
- typeChar = block[156];
+ typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
}
switch (typeChar) {
@@ -7037,6 +7087,7 @@
IFullBackupRestoreObserver mObserver;
AtomicBoolean mLatchObject;
IBackupAgent mAgent;
+ PackageManagerBackupAgent mPackageManagerBackupAgent;
String mAgentPackage;
ApplicationInfo mTargetApp;
FullBackupObbConnection mObbConnection = null;
@@ -7088,6 +7139,7 @@
mObserver = observer;
mLatchObject = latch;
mAgent = null;
+ mPackageManagerBackupAgent = new PackageManagerBackupAgent(mPackageManager);
mAgentPackage = null;
mTargetApp = null;
mObbConnection = new FullBackupObbConnection();
@@ -7505,14 +7557,21 @@
long toCopy = info.size;
final int token = generateToken();
try {
- prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null,
+ prepareOperationTimeout(token, TIMEOUT_RESTORE_INTERVAL, null,
OP_TYPE_RESTORE_WAIT);
- if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
+ if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ " : " + info.path);
mObbConnection.restoreObbFile(pkg, mPipes[0],
info.size, info.type, info.path, info.mode,
info.mtime, token, mBackupManagerBinder);
+ } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
+ if (DEBUG) Slog.d(TAG, "Restoring key-value file for " + pkg
+ + " : " + info.path);
+ KeyValueAdbRestoreEngine restoreEngine =
+ new KeyValueAdbRestoreEngine(BackupManagerService.this,
+ mDataDir, info, mPipes[0], mAgent, token);
+ new Thread(restoreEngine, "restore-key-value-runner").start();
} else {
if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ info.path);
@@ -8100,6 +8159,7 @@
Slog.i(TAG, b.toString());
}
}
+
// Consume a tar file header block [sequence] and accumulate the relevant metadata
FileMetadata readTarHeaders(InputStream instream) throws IOException {
byte[] block = new byte[512];
@@ -9920,16 +9980,16 @@
return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0);
}
- // Run a *full* backup pass for the given packages, writing the resulting data stream
+ // Run a backup pass for the given packages, writing the resulting data stream
// to the supplied file descriptor. This method is synchronous and does not return
// to the caller until the backup has been completed.
//
// This is the variant used by 'adb backup'; it requires on-screen confirmation
// by the user because it can be used to offload data over untrusted USB.
- public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
- boolean includeObbs, boolean includeShared, boolean doWidgets,
- boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) {
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
+ boolean compress, boolean doKeyValue, String[] pkgList) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
final int callingUserHandle = UserHandle.getCallingUserId();
// TODO: http://b/22388012
@@ -9954,27 +10014,28 @@
try {
// Doesn't make sense to do a full backup prior to setup
if (!deviceIsProvisioned()) {
- Slog.i(TAG, "Full backup not supported before setup");
+ Slog.i(TAG, "Backup not supported before setup");
return;
}
- if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
- + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
- + " system=" + includeSystem + " pkgs=" + pkgList);
- Slog.i(TAG, "Beginning full backup...");
+ if (DEBUG) Slog.v(TAG, "Requesting backup: apks=" + includeApks + " obb=" + includeObbs
+ + " shared=" + includeShared + " all=" + doAllApps + " system="
+ + includeSystem + " includekeyvalue=" + doKeyValue + " pkgs=" + pkgList);
+ Slog.i(TAG, "Beginning adb backup...");
- FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
- includeShared, doWidgets, doAllApps, includeSystem, compress, pkgList);
+ AdbBackupParams params = new AdbBackupParams(fd, includeApks, includeObbs,
+ includeShared, doWidgets, doAllApps, includeSystem, compress, doKeyValue,
+ pkgList);
final int token = generateToken();
- synchronized (mFullConfirmations) {
- mFullConfirmations.put(token, params);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
}
// start up the confirmation UI
if (DEBUG) Slog.d(TAG, "Starting backup confirmation UI, token=" + token);
if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch full backup confirmation");
- mFullConfirmations.delete(token);
+ Slog.e(TAG, "Unable to launch backup confirmation UI");
+ mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -9987,7 +10048,7 @@
startConfirmationTimeout(token, params);
// wait for the backup to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for full backup completion...");
+ if (DEBUG) Slog.d(TAG, "Waiting for backup completion...");
waitForCompletion(params);
} finally {
try {
@@ -9996,7 +10057,7 @@
// just eat it
}
Binder.restoreCallingIdentity(oldId);
- Slog.d(TAG, "Full backup processing complete.");
+ Slog.d(TAG, "Adb backup processing complete.");
}
}
@@ -10049,8 +10110,8 @@
}
}
- public void fullRestore(ParcelFileDescriptor fd) {
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore");
+ public void adbRestore(ParcelFileDescriptor fd) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore");
final int callingUserHandle = UserHandle.getCallingUserId();
// TODO: http://b/22388012
@@ -10068,19 +10129,19 @@
return;
}
- Slog.i(TAG, "Beginning full restore...");
+ Slog.i(TAG, "Beginning restore...");
- FullRestoreParams params = new FullRestoreParams(fd);
+ AdbRestoreParams params = new AdbRestoreParams(fd);
final int token = generateToken();
- synchronized (mFullConfirmations) {
- mFullConfirmations.put(token, params);
+ synchronized (mAdbBackupRestoreConfirmations) {
+ mAdbBackupRestoreConfirmations.put(token, params);
}
// start up the confirmation UI
if (DEBUG) Slog.d(TAG, "Starting restore confirmation UI, token=" + token);
if (!startConfirmationUi(token, FullBackup.FULL_RESTORE_INTENT_ACTION)) {
- Slog.e(TAG, "Unable to launch full restore confirmation");
- mFullConfirmations.delete(token);
+ Slog.e(TAG, "Unable to launch restore confirmation");
+ mAdbBackupRestoreConfirmations.delete(token);
return;
}
@@ -10093,16 +10154,16 @@
startConfirmationTimeout(token, params);
// wait for the restore to be performed
- if (DEBUG) Slog.d(TAG, "Waiting for full restore completion...");
+ if (DEBUG) Slog.d(TAG, "Waiting for restore completion...");
waitForCompletion(params);
} finally {
try {
fd.close();
} catch (IOException e) {
- Slog.w(TAG, "Error trying to close fd after full restore: " + e);
+ Slog.w(TAG, "Error trying to close fd after adb restore: " + e);
}
Binder.restoreCallingIdentity(oldId);
- Slog.i(TAG, "Full restore processing complete.");
+ Slog.i(TAG, "adb restore processing complete.");
}
}
@@ -10120,7 +10181,7 @@
return true;
}
- void startConfirmationTimeout(int token, FullParams params) {
+ void startConfirmationTimeout(int token, AdbParams params) {
if (MORE_DEBUG) Slog.d(TAG, "Posting conf timeout msg after "
+ TIMEOUT_FULL_CONFIRMATION + " millis");
Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT,
@@ -10128,7 +10189,7 @@
mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION);
}
- void waitForCompletion(FullParams params) {
+ void waitForCompletion(AdbParams params) {
synchronized (params.latch) {
while (params.latch.get() == false) {
try {
@@ -10138,7 +10199,7 @@
}
}
- void signalFullBackupRestoreCompletion(FullParams params) {
+ void signalAdbBackupRestoreCompletion(AdbParams params) {
synchronized (params.latch) {
params.latch.set(true);
params.latch.notifyAll();
@@ -10147,27 +10208,27 @@
// Confirm that the previously-requested full backup/restore operation can proceed. This
// is used to require a user-facing disclosure about the operation.
- public void acknowledgeFullBackupOrRestore(int token, boolean allow,
+ public void acknowledgeAdbBackupOrRestore(int token, boolean allow,
String curPassword, String encPpassword, IFullBackupRestoreObserver observer) {
- if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token
+ if (DEBUG) Slog.d(TAG, "acknowledgeAdbBackupOrRestore : token=" + token
+ " allow=" + allow);
// TODO: possibly require not just this signature-only permission, but even
// require that the specific designated confirmation-UI app uid is the caller?
- mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeFullBackupOrRestore");
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "acknowledgeAdbBackupOrRestore");
long oldId = Binder.clearCallingIdentity();
try {
- FullParams params;
- synchronized (mFullConfirmations) {
- params = mFullConfirmations.get(token);
+ AdbParams params;
+ synchronized (mAdbBackupRestoreConfirmations) {
+ params = mAdbBackupRestoreConfirmations.get(token);
if (params != null) {
mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params);
- mFullConfirmations.delete(token);
+ mAdbBackupRestoreConfirmations.delete(token);
if (allow) {
- final int verb = params instanceof FullBackupParams
+ final int verb = params instanceof AdbBackupParams
? MSG_RUN_ADB_BACKUP
: MSG_RUN_ADB_RESTORE;
@@ -10183,7 +10244,7 @@
} else {
Slog.w(TAG, "User rejected full backup/restore operation");
// indicate completion without having actually transferred any data
- signalFullBackupRestoreCompletion(params);
+ signalAdbBackupRestoreCompletion(params);
}
} else {
Slog.w(TAG, "Attempted to ack full backup/restore with invalid token");
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
new file mode 100644
index 0000000..cd13760
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -0,0 +1,281 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+
+import android.app.ApplicationThreadConstants;
+import android.app.IBackupAgent;
+import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Used by BackupManagerService to perform adb backup for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbBackupEngine {
+ private static final String TAG = "KeyValueAdbBackupEngine";
+ private static final boolean DEBUG = false;
+
+ private static final String BACKUP_KEY_VALUE_DIRECTORY_NAME = "key_value_dir";
+ private static final String BACKUP_KEY_VALUE_BLANK_STATE_FILENAME = "blank_state";
+ private static final String BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX = ".data";
+ private static final String BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX = ".new";
+
+ private BackupManagerService mBackupManagerService;
+ private final PackageManager mPackageManager;
+ private final OutputStream mOutput;
+ private final PackageInfo mCurrentPackage;
+ private final File mDataDir;
+ private final File mStateDir;
+ private final File mBlankStateName;
+ private final File mBackupDataName;
+ private final File mNewStateName;
+ private final File mManifestFile;
+ private ParcelFileDescriptor mSavedState;
+ private ParcelFileDescriptor mBackupData;
+ private ParcelFileDescriptor mNewState;
+
+ KeyValueAdbBackupEngine(OutputStream output, PackageInfo packageInfo,
+ BackupManagerService backupManagerService, PackageManager packageManager,
+ File baseStateDir, File dataDir) {
+ mOutput = output;
+ mCurrentPackage = packageInfo;
+ mBackupManagerService = backupManagerService;
+ mPackageManager = packageManager;
+
+ mDataDir = dataDir;
+ mStateDir = new File(baseStateDir, BACKUP_KEY_VALUE_DIRECTORY_NAME);
+ mStateDir.mkdirs();
+
+ String pkg = mCurrentPackage.packageName;
+
+ mBlankStateName = new File(mStateDir, BACKUP_KEY_VALUE_BLANK_STATE_FILENAME);
+ mBackupDataName = new File(mDataDir,
+ pkg + BACKUP_KEY_VALUE_BACKUP_DATA_FILENAME_SUFFIX);
+ mNewStateName = new File(mStateDir,
+ pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
+
+ mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+ }
+
+ void backupOnePackage() throws IOException {
+ ApplicationInfo targetApp = mCurrentPackage.applicationInfo;
+
+ try {
+ prepareBackupFiles(mCurrentPackage.packageName);
+
+ IBackupAgent agent = bindToAgent(targetApp);
+
+ if (agent == null) {
+ // We failed binding to the agent, so ignore this package
+ Slog.e(TAG, "Failed binding to BackupAgent for package "
+ + mCurrentPackage.packageName);
+ return;
+ }
+
+ // We are bound to agent, initiate backup.
+ if (!invokeAgentForAdbBackup(mCurrentPackage.packageName, agent)) {
+ // Backup failed, skip package.
+ Slog.e(TAG, "Backup Failed for package " + mCurrentPackage.packageName);
+ return;
+ }
+
+ // Backup finished successfully. Copy the backup data to the output stream.
+ writeBackupData();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Failed creating files for package " + mCurrentPackage.packageName
+ + " will ignore package. " + e);
+ } finally {
+ // We are either done, failed or have timed out, so do cleanup and kill the agent.
+ cleanup();
+ }
+ }
+
+ private void prepareBackupFiles(String packageName) throws FileNotFoundException {
+
+ // We pass a blank state to make sure we are getting the complete backup, not just an
+ // increment
+ mSavedState = ParcelFileDescriptor.open(mBlankStateName,
+ MODE_READ_ONLY | MODE_CREATE); // Make an empty file if necessary
+
+ mBackupData = ParcelFileDescriptor.open(mBackupDataName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+ if (!SELinux.restorecon(mBackupDataName)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBackupDataName);
+ }
+
+ mNewState = ParcelFileDescriptor.open(mNewStateName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+ }
+
+ private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
+ try {
+ return mBackupManagerService.bindToAgentSynchronous(targetApp,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "error in binding to agent for package " + targetApp.packageName
+ + ". " + e);
+ return null;
+ }
+ }
+
+ // Return true on backup success, false otherwise
+ private boolean invokeAgentForAdbBackup(String packageName, IBackupAgent agent) {
+ int token = mBackupManagerService.generateToken();
+ try {
+ mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+ OP_TYPE_BACKUP_WAIT);
+
+ // Start backup and wait for BackupManagerService to get callback for success or timeout
+ agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+ mBackupManagerService.mBackupManagerBinder);
+ if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Key-value backup failed on package " + packageName);
+ return false;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "Key-value backup success for package " + packageName);
+ }
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error invoking agent for backup on " + packageName + ". " + e);
+ return false;
+ }
+ }
+
+ class KeyValueAdbBackupDataCopier implements Runnable {
+ private final PackageInfo mPackage;
+ private final ParcelFileDescriptor mPipe;
+ private final int mToken;
+
+ KeyValueAdbBackupDataCopier(PackageInfo pack, ParcelFileDescriptor pipe,
+ int token)
+ throws IOException {
+ mPackage = pack;
+ mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
+ mToken = token;
+ }
+
+ @Override
+ public void run() {
+ try {
+ FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+ }
+ BackupManagerService.writeAppManifest(
+ mPackage, mPackageManager, mManifestFile, false, false);
+ FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+ mDataDir.getAbsolutePath(),
+ mManifestFile.getAbsolutePath(),
+ output);
+ mManifestFile.delete();
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing key-value package payload" + mPackage.packageName);
+ }
+ FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
+ mDataDir.getAbsolutePath(),
+ mBackupDataName.getAbsolutePath(),
+ output);
+
+ // Write EOD marker
+ try {
+ FileOutputStream out = new FileOutputStream(mPipe.getFileDescriptor());
+ byte[] buf = new byte[4];
+ out.write(buf);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to finalize backup stream!");
+ }
+
+ try {
+ mBackupManagerService.mBackupManagerBinder.opComplete(mToken, 0);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running full backup for " + mPackage.packageName + ". " + e);
+ } finally {
+ IoUtils.closeQuietly(mPipe);
+ }
+ }
+ }
+
+ private void writeBackupData() throws IOException {
+
+ int token = mBackupManagerService.generateToken();
+
+ ParcelFileDescriptor[] pipes = null;
+ try {
+ pipes = ParcelFileDescriptor.createPipe();
+
+ mBackupManagerService.prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, null,
+ OP_TYPE_BACKUP_WAIT);
+
+ // We will have to create a runnable that will read the manifest and backup data we
+ // created, such that we can pipe the data into mOutput. The reason we do this is that
+ // internally FullBackup.backupToTar is used, which will create the necessary file
+ // header, but will also chunk the data. The method routeSocketDataToOutput in
+ // BackupManagerService will dechunk the data, and append it to the TAR outputstream.
+ KeyValueAdbBackupDataCopier runner = new KeyValueAdbBackupDataCopier(mCurrentPackage, pipes[1],
+ token);
+ pipes[1].close(); // the runner has dup'd it
+ pipes[1] = null;
+ Thread t = new Thread(runner, "key-value-app-data-runner");
+ t.start();
+
+ // Now pull data from the app and stuff it into the output
+ BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+
+ if (!mBackupManagerService.waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Full package backup success: " + mCurrentPackage.packageName);
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Error backing up " + mCurrentPackage.packageName + ": " + e);
+ } finally {
+ // flush after every package
+ mOutput.flush();
+ if (pipes != null) {
+ IoUtils.closeQuietly(pipes[0]);
+ IoUtils.closeQuietly(pipes[1]);
+ }
+ }
+ }
+
+ private void cleanup() {
+ mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
+ mBlankStateName.delete();
+ mNewStateName.delete();
+ mBackupDataName.delete();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
new file mode 100644
index 0000000..6fb9355
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -0,0 +1,148 @@
+package com.android.server.backup;
+
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+
+import android.app.IBackupAgent;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackup;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.backup.BackupManagerService.FileMetadata;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this
+ * class resembles what is done in the standard key-value code paths in BackupManagerService, and
+ * should be unified later.
+ *
+ * TODO: We should create unified backup/restore engines that can be used for both transport and
+ * adb backup/restore, and for fullbackup and key-value backup.
+ */
+class KeyValueAdbRestoreEngine implements Runnable {
+ private static final String TAG = "KeyValueAdbRestoreEngine";
+ private static final boolean DEBUG = false;
+
+ private final BackupManagerService mBackupManagerService;
+ private final File mDataDir;
+
+ FileMetadata mInfo;
+ BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+ ParcelFileDescriptor mInFD;
+ IBackupAgent mAgent;
+ int mToken;
+
+ KeyValueAdbRestoreEngine(BackupManagerService backupManagerService, File dataDir,
+ FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent, int token) {
+ mBackupManagerService = backupManagerService;
+ mDataDir = dataDir;
+ mInfo = info;
+ mInFD = inFD;
+ mAgent = agent;
+ mToken = token;
+ }
+
+ @Override
+ public void run() {
+ try {
+ File restoreData = prepareRestoreData(mInfo, mInFD);
+
+ // TODO: version ?
+ invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException {
+ String pkg = info.packageName;
+ File restoreDataName = new File(mDataDir, pkg + ".restore");
+ File sortedDataName = new File(mDataDir, pkg + ".sorted");
+
+ FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName);
+
+ // Sort the keys, as the BackupAgent expect them to come in lexicographical order
+ sortKeyValueData(restoreDataName, sortedDataName);
+ return sortedDataName;
+ }
+
+ private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
+ int versionCode) throws IOException {
+ String pkg = info.packageName;
+ File newStateName = new File(mDataDir, pkg + ".new");
+ try {
+ ParcelFileDescriptor backupData =
+ ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY);
+ ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName,
+ MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
+ + versionCode);
+ }
+ agent.doRestore(backupData, versionCode, newState, mToken,
+ mBackupManagerService.mBackupManagerBinder);
+ } catch (IOException e) {
+ Slog.e(TAG, "Exception opening file. " + e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception calling doRestore on agent: " + e);
+ }
+ }
+
+ private void sortKeyValueData (File restoreData, File sortedData) throws IOException {
+ FileInputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ try {
+ inputStream = new FileInputStream(restoreData);
+ outputStream = new FileOutputStream(sortedData);
+ BackupDataInput reader = new BackupDataInput(inputStream.getFD());
+ BackupDataOutput writer = new BackupDataOutput(outputStream.getFD());
+ copyKeysInLexicalOrder(reader, writer);
+ } finally {
+ if (inputStream != null) {
+ IoUtils.closeQuietly(inputStream);
+ }
+ if (outputStream != null) {
+ IoUtils.closeQuietly(outputStream);
+ }
+ }
+ }
+
+ private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out)
+ throws IOException {
+ Map<String, byte[]> data = new HashMap<>();
+ while (in.readNextHeader()) {
+ String key = in.getKey();
+ int size = in.getDataSize();
+ if (size < 0) {
+ in.skipEntityData();
+ continue;
+ }
+ byte[] value = new byte[size];
+ in.readEntityData(value, 0, size);
+ data.put(key, value);
+ }
+ List<String> keys = new ArrayList<>(data.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ byte[] value = data.get(key);
+ out.writeEntityHeader(key, value.length);
+ out.writeEntityData(value, value.length);
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8855661..c40f2ca 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -227,14 +227,14 @@
}
@Override
- public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps,
- boolean allIncludesSystem, boolean doCompress, String[] packageNames)
+ boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
- allApps, allIncludesSystem, doCompress, packageNames);
+ svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+ allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
}
}
@@ -247,10 +247,10 @@
}
@Override
- public void fullRestore(ParcelFileDescriptor fd) throws RemoteException {
+ public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.fullRestore(fd);
+ svc.adbRestore(fd);
}
}
@@ -260,7 +260,7 @@
throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.acknowledgeFullBackupOrRestore(token, allow,
+ svc.acknowledgeAdbBackupOrRestore(token, allow,
curPassword, encryptionPassword, observer);
}
}
diff --git a/services/core/java/com/android/server/NetworkManagementInternal.java b/services/core/java/com/android/server/NetworkManagementInternal.java
new file mode 100644
index 0000000..f53c454
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkManagementInternal.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * NetworkManagement local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class NetworkManagementInternal {
+ /**
+ * Checks if network is restricted for {@param uid} as per the app idle state, device idle mode,
+ * battery and data saver modes.
+ */
+ public abstract boolean isNetworkRestrictedForUid(int uid);
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index adc5e33..74328c0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -27,7 +27,9 @@
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NONE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.FIREWALL_TYPE_BLACKLIST;
import static android.net.NetworkPolicyManager.FIREWALL_TYPE_WHITELIST;
import static android.net.NetworkStats.SET_DEFAULT;
@@ -90,6 +92,7 @@
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.HexDump;
@@ -222,7 +225,12 @@
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
+ /**
+ * If both locks need to be held, then they should be obtained in the order:
+ * first {@link #mQuotaLock} and then {@link #mRulesLock}.
+ */
private Object mQuotaLock = new Object();
+ private Object mRulesLock = new Object();
/** Set of interfaces with active quotas. */
@GuardedBy("mQuotaLock")
@@ -231,41 +239,41 @@
@GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
/** Set of UIDs blacklisted on metered networks. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
/** Set of UIDs whitelisted on metered networks. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
/** Set of UIDs with cleartext penalties. */
@GuardedBy("mQuotaLock")
private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
/** Set of UIDs that are to be blocked/allowed by firewall controller. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to application idles.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device idles.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
/**
* Set of UIDs that are to be blocked/allowed by firewall controller. This set of Ids matches
* to device on power-save mode.
*/
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
- @GuardedBy("mQuotaLock")
+ @GuardedBy("mRulesLock")
final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@GuardedBy("mQuotaLock")
- private boolean mDataSaverMode;
+ private volatile boolean mDataSaverMode;
private Object mIdleTimerLock = new Object();
/** Set of interfaces with active idle timers. */
@@ -321,6 +329,17 @@
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
+
+ LocalServices.addService(NetworkManagementInternal.class, new LocalService());
+ }
+
+ @VisibleForTesting
+ NetworkManagementService() {
+ mConnector = null;
+ mContext = null;
+ mDaemonHandler = null;
+ mFgHandler = null;
+ mThread = null;
}
static NetworkManagementService create(Context context, String socket)
@@ -502,21 +521,24 @@
}
// Sync the state of the given chain with the native daemon.
- private void syncFirewallChainLocked(int chain, SparseIntArray uidFirewallRules, String name) {
- int size = uidFirewallRules.size();
- if (size > 0) {
+ private void syncFirewallChainLocked(int chain, String name) {
+ SparseIntArray rules;
+ synchronized (mRulesLock) {
+ final SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
// Make a copy of the current rules, and then clear them. This is because
- // setFirewallUidRuleInternal only pushes down rules to the native daemon if they are
- // different from the current rules stored in the mUidFirewall*Rules array for the
- // specified chain. If we don't clear the rules, setFirewallUidRuleInternal will do
- // nothing.
- final SparseIntArray rules = uidFirewallRules.clone();
+ // setFirewallUidRuleInternal only pushes down rules to the native daemon if they
+ // are different from the current rules stored in the mUidFirewall*Rules array for
+ // the specified chain. If we don't clear the rules, setFirewallUidRuleInternal
+ // will do nothing.
+ rules = uidFirewallRules.clone();
uidFirewallRules.clear();
-
+ }
+ if (rules.size() > 0) {
// Now push the rules. setFirewallUidRuleInternal will push each of these down to the
// native daemon, and also add them to the mUidFirewall*Rules array for the specified
// chain.
- if (DBG) Slog.d(TAG, "Pushing " + size + " active firewall " + name + "UID rules");
+ if (DBG) Slog.d(TAG, "Pushing " + rules.size() + " active firewall "
+ + name + "UID rules");
for (int i = 0; i < rules.size(); i++) {
setFirewallUidRuleLocked(chain, rules.keyAt(i), rules.valueAt(i));
}
@@ -597,22 +619,30 @@
}
}
- size = mUidRejectOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
- final SparseBooleanArray uidRejectOnQuota = mUidRejectOnMetered;
- mUidRejectOnMetered = new SparseBooleanArray();
+ SparseBooleanArray uidRejectOnQuota = null;
+ SparseBooleanArray uidAcceptOnQuota = null;
+ synchronized (mRulesLock) {
+ size = mUidRejectOnMetered.size();
+ if (size > 0) {
+ if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
+ uidRejectOnQuota = mUidRejectOnMetered;
+ mUidRejectOnMetered = new SparseBooleanArray();
+ }
+
+ size = mUidAllowOnMetered.size();
+ if (size > 0) {
+ if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
+ uidAcceptOnQuota = mUidAllowOnMetered;
+ mUidAllowOnMetered = new SparseBooleanArray();
+ }
+ }
+ if (uidRejectOnQuota != null) {
for (int i = 0; i < uidRejectOnQuota.size(); i++) {
setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
uidRejectOnQuota.valueAt(i));
}
}
-
- size = mUidAllowOnMetered.size();
- if (size > 0) {
- if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
- final SparseBooleanArray uidAcceptOnQuota = mUidAllowOnMetered;
- mUidAllowOnMetered = new SparseBooleanArray();
+ if (uidAcceptOnQuota != null) {
for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
uidAcceptOnQuota.valueAt(i));
@@ -631,20 +661,17 @@
setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
- syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
- syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
- syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, mUidFirewallDozableRules, "dozable ");
- syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, mUidFirewallPowerSaveRules,
- "powersave ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_NONE, "");
+ syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
+ syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
- if (mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_STANDBY, true);
- }
- if (mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true);
- }
- if (mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE)) {
- setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE, true);
+ final int[] chains =
+ {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE};
+ for (int chain : chains) {
+ if (getFirewallChainState(chain)) {
+ setFirewallChainEnabled(chain, true);
+ }
}
}
}
@@ -1602,8 +1629,7 @@
}
}
- private void setUidOnMeteredNetworkList(SparseBooleanArray quotaList, int uid,
- boolean blacklist, boolean enable) {
+ private void setUidOnMeteredNetworkList(int uid, boolean blacklist, boolean enable) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
// silently discard when control disabled
@@ -1614,7 +1640,12 @@
final String suffix = enable ? "add" : "remove";
synchronized (mQuotaLock) {
- final boolean oldEnable = quotaList.get(uid, false);
+ boolean oldEnable;
+ SparseBooleanArray quotaList;
+ synchronized (mRulesLock) {
+ quotaList = blacklist ? mUidRejectOnMetered : mUidAllowOnMetered;
+ oldEnable = quotaList.get(uid, false);
+ }
if (oldEnable == enable) {
// TODO: eventually consider throwing
return;
@@ -1623,10 +1654,12 @@
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
try {
mConnector.execute("bandwidth", suffix + chain, uid);
- if (enable) {
- quotaList.put(uid, true);
- } else {
- quotaList.delete(uid);
+ synchronized (mRulesLock) {
+ if (enable) {
+ quotaList.put(uid, true);
+ } else {
+ quotaList.delete(uid);
+ }
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
@@ -1638,12 +1671,12 @@
@Override
public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
- setUidOnMeteredNetworkList(mUidRejectOnMetered, uid, true, enable);
+ setUidOnMeteredNetworkList(uid, true, enable);
}
@Override
public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
- setUidOnMeteredNetworkList(mUidAllowOnMetered, uid, false, enable);
+ setUidOnMeteredNetworkList(uid, false, enable);
}
@Override
@@ -1934,7 +1967,6 @@
// UID ranges whose sockets we won't touch.
int[] exemptUids;
- final SparseIntArray rules = getUidFirewallRules(chain);
int numUids = 0;
if (getFirewallType(chain) == FIREWALL_TYPE_WHITELIST) {
@@ -1945,11 +1977,14 @@
new UidRange(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE),
};
// ... except for the UIDs that have allow rules.
- exemptUids = new int[rules.size()];
- for (int i = 0; i < exemptUids.length; i++) {
- if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
- exemptUids[numUids] = rules.keyAt(i);
- numUids++;
+ synchronized (mRulesLock) {
+ final SparseIntArray rules = getUidFirewallRulesLR(chain);
+ exemptUids = new int[rules.size()];
+ for (int i = 0; i < exemptUids.length; i++) {
+ if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_ALLOW) {
+ exemptUids[numUids] = rules.keyAt(i);
+ numUids++;
+ }
}
}
// Normally, whitelist chains only contain deny rules, so numUids == exemptUids.length.
@@ -1964,12 +1999,15 @@
}
} else {
// Close sockets for every UID that has a deny rule...
- ranges = new UidRange[rules.size()];
- for (int i = 0; i < ranges.length; i++) {
- if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
- int uid = rules.keyAt(i);
- ranges[numUids] = new UidRange(uid, uid);
- numUids++;
+ synchronized (mRulesLock) {
+ final SparseIntArray rules = getUidFirewallRulesLR(chain);
+ ranges = new UidRange[rules.size()];
+ for (int i = 0; i < ranges.length; i++) {
+ if (rules.valueAt(i) == NetworkPolicyManager.FIREWALL_RULE_DENY) {
+ int uid = rules.keyAt(i);
+ ranges[numUids] = new UidRange(uid, uid);
+ numUids++;
+ }
}
}
// As above; usually numUids == ranges.length, but not always.
@@ -1991,12 +2029,14 @@
public void setFirewallChainEnabled(int chain, boolean enable) {
enforceSystemUid();
synchronized (mQuotaLock) {
- if (mFirewallChainStates.get(chain) == enable) {
- // All is the same, nothing to do. This relies on the fact that netd has child
- // chains default detached.
- return;
+ synchronized (mRulesLock) {
+ if (getFirewallChainState(chain) == enable) {
+ // All is the same, nothing to do. This relies on the fact that netd has child
+ // chains default detached.
+ return;
+ }
+ setFirewallChainState(chain, enable);
}
- mFirewallChainStates.put(chain, enable);
final String operation = enable ? "enable_chain" : "disable_chain";
final String chainName;
@@ -2048,27 +2088,29 @@
public void setFirewallUidRules(int chain, int[] uids, int[] rules) {
enforceSystemUid();
synchronized (mQuotaLock) {
- SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
- SparseIntArray newRules = new SparseIntArray();
- // apply new set of rules
- for (int index = uids.length - 1; index >= 0; --index) {
- int uid = uids[index];
- int rule = rules[index];
- updateFirewallUidRuleLocked(chain, uid, rule);
- newRules.put(uid, rule);
- }
- // collect the rules to remove.
- SparseIntArray rulesToRemove = new SparseIntArray();
- for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
- int uid = uidFirewallRules.keyAt(index);
- if (newRules.indexOfKey(uid) < 0) {
- rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+ synchronized (mRulesLock) {
+ SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
+ SparseIntArray newRules = new SparseIntArray();
+ // apply new set of rules
+ for (int index = uids.length - 1; index >= 0; --index) {
+ int uid = uids[index];
+ int rule = rules[index];
+ updateFirewallUidRuleLocked(chain, uid, rule);
+ newRules.put(uid, rule);
}
- }
- // remove dead rules
- for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
- int uid = rulesToRemove.keyAt(index);
- updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+ // collect the rules to remove.
+ SparseIntArray rulesToRemove = new SparseIntArray();
+ for (int index = uidFirewallRules.size() - 1; index >= 0; --index) {
+ int uid = uidFirewallRules.keyAt(index);
+ if (newRules.indexOfKey(uid) < 0) {
+ rulesToRemove.put(uid, FIREWALL_RULE_DEFAULT);
+ }
+ }
+ // remove dead rules
+ for (int index = rulesToRemove.size() - 1; index >= 0; --index) {
+ int uid = rulesToRemove.keyAt(index);
+ updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
+ }
}
try {
switch (chain) {
@@ -2112,28 +2154,30 @@
// TODO: now that netd supports batching, NMS should not keep these data structures anymore...
private boolean updateFirewallUidRuleLocked(int chain, int uid, int rule) {
- SparseIntArray uidFirewallRules = getUidFirewallRules(chain);
+ synchronized (mRulesLock) {
+ SparseIntArray uidFirewallRules = getUidFirewallRulesLR(chain);
- final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
- if (DBG) {
- Slog.d(TAG, "oldRule = " + oldUidFirewallRule
- + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
- }
- if (oldUidFirewallRule == rule) {
- if (DBG) Slog.d(TAG, "!!!!! Skipping change");
- // TODO: eventually consider throwing
- return false;
- }
+ final int oldUidFirewallRule = uidFirewallRules.get(uid, FIREWALL_RULE_DEFAULT);
+ if (DBG) {
+ Slog.d(TAG, "oldRule = " + oldUidFirewallRule
+ + ", newRule=" + rule + " for uid=" + uid + " on chain " + chain);
+ }
+ if (oldUidFirewallRule == rule) {
+ if (DBG) Slog.d(TAG, "!!!!! Skipping change");
+ // TODO: eventually consider throwing
+ return false;
+ }
- String ruleName = getFirewallRuleName(chain, rule);
- String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
+ String ruleName = getFirewallRuleName(chain, rule);
+ String oldRuleName = getFirewallRuleName(chain, oldUidFirewallRule);
- if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
- uidFirewallRules.delete(uid);
- } else {
- uidFirewallRules.put(uid, rule);
+ if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
+ uidFirewallRules.delete(uid);
+ } else {
+ uidFirewallRules.put(uid, rule);
+ }
+ return !ruleName.equals(oldRuleName);
}
- return !ruleName.equals(oldRuleName);
}
private @NonNull String getFirewallRuleName(int chain, int rule) {
@@ -2154,7 +2198,7 @@
return ruleName;
}
- private @NonNull SparseIntArray getUidFirewallRules(int chain) {
+ private @NonNull SparseIntArray getUidFirewallRulesLR(int chain) {
switch (chain) {
case FIREWALL_CHAIN_STANDBY:
return mUidFirewallStandbyRules;
@@ -2284,29 +2328,25 @@
pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
pw.print("Data saver mode: "); pw.println(mDataSaverMode);
- dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
- dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+ synchronized (mRulesLock) {
+ dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
+ dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
+ }
}
- synchronized (mUidFirewallRules) {
+ synchronized (mRulesLock) {
dumpUidFirewallRule(pw, "", mUidFirewallRules);
- }
- pw.print("UID firewall standby chain enabled: "); pw.println(
- mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY));
- synchronized (mUidFirewallStandbyRules) {
+ pw.print("UID firewall standby chain enabled: "); pw.println(
+ getFirewallChainState(FIREWALL_CHAIN_STANDBY));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules);
- }
- pw.print("UID firewall dozable chain enabled: "); pw.println(
- mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE));
- synchronized (mUidFirewallDozableRules) {
+ pw.print("UID firewall dozable chain enabled: "); pw.println(
+ getFirewallChainState(FIREWALL_CHAIN_DOZABLE));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules);
- }
- pw.println("UID firewall powersave chain enabled: " +
- mFirewallChainStates.get(FIREWALL_CHAIN_POWERSAVE));
- synchronized (mUidFirewallPowerSaveRules) {
+ pw.println("UID firewall powersave chain enabled: " +
+ getFirewallChainState(FIREWALL_CHAIN_POWERSAVE));
dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules);
}
@@ -2576,4 +2616,99 @@
return failures;
}
+
+ private void setFirewallChainState(int chain, boolean state) {
+ synchronized (mRulesLock) {
+ mFirewallChainStates.put(chain, state);
+ }
+ }
+
+ private boolean getFirewallChainState(int chain) {
+ synchronized (mRulesLock) {
+ return mFirewallChainStates.get(chain);
+ }
+ }
+
+ @VisibleForTesting
+ class LocalService extends NetworkManagementInternal {
+ @Override
+ public boolean isNetworkRestrictedForUid(int uid) {
+ synchronized (mRulesLock) {
+ if (getFirewallChainState(FIREWALL_CHAIN_STANDBY)
+ && mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode");
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_DOZABLE)
+ && mUidFirewallDozableRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of device idle mode");
+ return true;
+ }
+ if (getFirewallChainState(FIREWALL_CHAIN_POWERSAVE)
+ && mUidFirewallPowerSaveRules.get(uid) != FIREWALL_RULE_ALLOW) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of power saver mode");
+ return true;
+ }
+ if (mUidRejectOnMetered.get(uid)) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
+ + " in the background");
+ return true;
+ }
+ if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) {
+ if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode");
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ Injector getInjector() {
+ return new Injector();
+ }
+
+ @VisibleForTesting
+ class Injector {
+ void setDataSaverMode(boolean dataSaverMode) {
+ mDataSaverMode = dataSaverMode;
+ }
+
+ void setFirewallChainState(int chain, boolean state) {
+ NetworkManagementService.this.setFirewallChainState(chain, state);
+ }
+
+ void setFirewallRule(int chain, int uid, int rule) {
+ synchronized (mRulesLock) {
+ getUidFirewallRulesLR(chain).put(uid, rule);
+ }
+ }
+
+ void setUidOnMeteredNetworkList(boolean blacklist, int uid, boolean enable) {
+ synchronized (mRulesLock) {
+ if (blacklist) {
+ mUidRejectOnMetered.put(uid, enable);
+ } else {
+ mUidAllowOnMetered.put(uid, enable);
+ }
+ }
+ }
+
+ void reset() {
+ synchronized (mRulesLock) {
+ setDataSaverMode(false);
+ final int[] chains = {
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE
+ };
+ for (int chain : chains) {
+ setFirewallChainState(chain, false);
+ getUidFirewallRulesLR(chain).clear();
+ }
+ mUidAllowOnMetered.clear();
+ mUidRejectOnMetered.clear();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 891a13b..457c5f8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -715,7 +715,8 @@
final Intent intent = new Intent(action,
Uri.fromFile(userVol.getPathFile()));
intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mContext.sendBroadcastAsUser(intent, userVol.getOwner());
}
break;
@@ -2079,6 +2080,20 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+ final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef2cc81..272fbf8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -54,6 +54,7 @@
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.provider.Settings.System.FONT_SCALE;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
@@ -357,6 +358,7 @@
import com.android.server.IntentResolver;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.NetworkManagementInternal;
import com.android.server.RescueParty;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
@@ -572,9 +574,9 @@
static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
/**
- * Indicates the maximum time spent waiting for the network rules to get updated.
+ * Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
*/
- private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+ private static final long NETWORK_ACCESS_TIMEOUT_DEFAULT_MS = 0; // 0 sec
/**
* State indicating that there is no need for any blocking for network.
@@ -753,6 +755,12 @@
final AppErrors mAppErrors;
+ /**
+ * Indicates the maximum time spent waiting for the network rules to get updated.
+ */
+ @VisibleForTesting
+ long mWaitForNetworkTimeoutMs;
+
public boolean canShowErrorDialogs() {
return mShowDialogs && !mSleeping && !mShuttingDown
&& !mKeyguardController.isKeyguardShowing();
@@ -13807,6 +13815,8 @@
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
final boolean forceResizable = Settings.Global.getInt(
resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+ final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver,
+ NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS);
final boolean supportsLeanbackOnly =
mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY);
@@ -13862,6 +13872,7 @@
mFullscreenThumbnailScale = res.getFraction(
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
}
+ mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
}
}
@@ -22515,6 +22526,9 @@
@VisibleForTesting
@GuardedBy("this")
void incrementProcStateSeqAndNotifyAppsLocked() {
+ if (mWaitForNetworkTimeoutMs <= 0) {
+ return;
+ }
// Used for identifying which uids need to block for network.
ArrayList<Integer> blockingUids = null;
for (int i = mActiveUids.size() - 1; i >= 0; --i) {
@@ -23557,10 +23571,14 @@
}
final long startTime = SystemClock.uptimeMillis();
record.waitingForNetwork = true;
- record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+ record.lock.wait(mWaitForNetworkTimeoutMs);
record.waitingForNetwork = false;
final long totalTime = SystemClock.uptimeMillis() - startTime;
- if (DEBUG_NETWORK || totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+ if (totalTime >= mWaitForNetworkTimeoutMs) {
+ Slog.wtf(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ + procStateSeq);
+ } else if (DEBUG_NETWORK || totalTime >= mWaitForNetworkTimeoutMs / 2) {
Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ procStateSeq);
@@ -23855,6 +23873,8 @@
@VisibleForTesting
public static class Injector {
+ private NetworkManagementInternal mNmi;
+
public AppOpsService getAppOpsService(File file, Handler handler) {
return new AppOpsService(file, handler);
}
@@ -23864,8 +23884,17 @@
}
public boolean isNetworkRestrictedForUid(int uid) {
- // TODO: add implementation
+ if (ensureHasNetworkManagementInternal()) {
+ return mNmi.isNetworkRestrictedForUid(uid);
+ }
return false;
}
+
+ private boolean ensureHasNetworkManagementInternal() {
+ if (mNmi == null) {
+ mNmi = LocalServices.getService(NetworkManagementInternal.class);
+ }
+ return mNmi != null;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1b7b225..4bd06b7 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1790,21 +1790,7 @@
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- if (mLaunchBounds != null) {
- mInTask.updateOverrideConfiguration(mLaunchBounds);
- int stackId = mInTask.getLaunchStackId();
- if (stackId != mInTask.getStackId()) {
- mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
- DEFER_RESUME, "inTaskToFront");
- stackId = mInTask.getStackId();
- }
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
- }
- }
mTargetStack = mInTask.getStack();
- mTargetStack.moveTaskToFrontLocked(
- mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
// Check whether we should actually launch the new activity in to the task,
// or just reuse the current activity on top.
@@ -1813,6 +1799,8 @@
&& top.userId == mStartActivity.userId) {
if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
@@ -1826,12 +1814,31 @@
}
if (!mAddingToTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
// We don't actually want to have this activity added to the task, so just
// stop here but still tell the caller that we consumed the intent.
ActivityOptions.abort(mOptions);
return START_TASK_TO_FRONT;
}
+ if (mLaunchBounds != null) {
+ mInTask.updateOverrideConfiguration(mLaunchBounds);
+ int stackId = mInTask.getLaunchStackId();
+ if (stackId != mInTask.getStackId()) {
+ mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "inTaskToFront");
+ stackId = mInTask.getStackId();
+ mTargetStack = mInTask.getStack();
+ }
+ if (StackId.resizeStackWithLaunchBounds(stackId)) {
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+ }
+ }
+
+ mTargetStack.moveTaskToFrontLocked(
+ mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
+
addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+ " in explicit task " + mStartActivity.task);
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 59e698c..7b4fa87 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -16,8 +16,6 @@
package android.net.ip;
-import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
-
import com.android.internal.util.MessageUtils;
import com.android.internal.util.WakeupMessage;
@@ -44,7 +42,6 @@
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
-import android.system.OsConstants;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
@@ -1031,36 +1028,15 @@
return true;
}
- private void enableInterfaceIPv6PrivacyExtensions() {
+ private boolean startIPv6() {
+ // Set privacy extensions.
final String PREFER_TEMPADDRS = "2";
- NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS);
- });
- }
-
- private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) {
- // Setting RIO max plen is best effort. Catch and ignore most exceptions.
try {
NetdService.run((INetd netd) -> {
- netd.setProcSysNet(
- INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen",
- Integer.toString(plen));
- });
- } catch (ServiceSpecificException e) {
- // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen
- // in the /proc filesystem. If the kernel supports RIOs we should never see any other
- // type of error.
- if (e.errorCode != OsConstants.ENOENT) {
- logError("unexpected error setting accept_ra_rt_info_max_plen %s", e);
- }
- }
- }
-
- private boolean startIPv6() {
- try {
- enableInterfaceIPv6PrivacyExtensions();
- setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH);
+ netd.setProcSysNet(
+ INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
+ PREFER_TEMPADDRS);
+ });
mNwService.enableIpv6(mInterfaceName);
} catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
logError("Unable to change interface settings: %s", e);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
new file mode 100644
index 0000000..c9180a9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementInternalTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 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 static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.NetworkPolicyManager;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+
+/**
+ * Test class for {@link NetworkManagementInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.NetworkManagementInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.NetworkManagementInternalTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkManagementInternalTest {
+ private static final int TEST_UID = 111;
+
+ private NetworkManagementService.Injector mInjector;
+ private NetworkManagementInternal mNmi;
+
+ @Before
+ public void setUp() {
+ final NetworkManagementService service = new NetworkManagementService();
+ mInjector = service.getInjector();
+ mNmi = service.new LocalService();
+ }
+
+ @Test
+ public void testIsNetworkRestrictedForUid() {
+ // No firewall chains enabled
+ assertFalse(mNmi.isNetworkRestrictedForUid(TEST_UID));
+
+ // Restrict usage of mobile data in background
+ mInjector.setUidOnMeteredNetworkList(true, TEST_UID, true);
+ assertTrue("Should be true since mobile data usage is restricted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ // Data saver is on and uid is not whitelisted
+ mInjector.setDataSaverMode(true);
+ mInjector.setUidOnMeteredNetworkList(false, TEST_UID, false);
+ assertTrue("Should be true since data saver is on and the uid is not whitelisted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ // Data saver is on and uid is whitelisted
+ mInjector.setDataSaverMode(true);
+ mInjector.setUidOnMeteredNetworkList(false, TEST_UID, true);
+ assertFalse("Should be false since data saver is on and the uid is whitelisted",
+ mNmi.isNetworkRestrictedForUid(TEST_UID));
+ mInjector.reset();
+
+ final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+ // Dozable chain
+ final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+ isRestrictedForDozable.put(FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForDozable.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForDozable.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+ // Powersaver chain
+ final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+ isRestrictedForPowerSave.put(FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForPowerSave.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForPowerSave.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+ // Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+ isRestrictedForStandby.put(FIREWALL_RULE_DEFAULT, false);
+ isRestrictedForStandby.put(FIREWALL_RULE_ALLOW, false);
+ isRestrictedForStandby.put(FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+
+ final int[] chains = {
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_DOZABLE
+ };
+ final int[] states = {
+ FIREWALL_RULE_ALLOW,
+ FIREWALL_RULE_DENY,
+ FIREWALL_RULE_DEFAULT
+ };
+ BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+ return String.format("Unexpected value for chain: %s and state: %s",
+ valueToString(NetworkPolicyManager.class, "FIREWALL_CHAIN_", chain),
+ valueToString(NetworkPolicyManager.class, "FIREWALL_RULE_", state));
+ };
+ for (int chain : chains) {
+ final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+ mInjector.setFirewallChainState(chain, true);
+ for (int state : states) {
+ mInjector.setFirewallRule(chain, TEST_UID, state);
+ assertEquals(errorMsg.apply(chain, state),
+ expectedValues.get(state), mNmi.isNetworkRestrictedForUid(TEST_UID));
+ }
+ mInjector.reset();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index cc5764b..b12da34 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -132,6 +132,7 @@
mHandler = new TestHandler(mHandlerThread.getLooper());
mInjector = new TestInjector();
mAms = new ActivityManagerService(mInjector);
+ mAms.mWaitForNetworkTimeoutMs = 100;
}
@After
@@ -217,6 +218,17 @@
44, // exptectedCurProcStateSeq
-1, // expectedBlockState, -1 to verify there are no interactions with main thread.
false); // expectNotify
+
+ // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented.
+ mAms.mWaitForNetworkTimeoutMs = 0;
+ mInjector.setNetworkRestrictedForUid(true);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_TOP, // prevState
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+ false); // expectNotify
}
private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 43f4ebc..e4b2020 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -127,6 +127,9 @@
private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+ if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
+ return;
+ }
// The polygon of shadow (same as the original item)
float[] shadowPoly = new float[coordinates.length * 3];
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index b2baa98..736b287 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ