Merge "Check for null view root before checking for accessibility focus"
diff --git a/api/current.txt b/api/current.txt
index b218701..eb39a16 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4749,6 +4749,8 @@
}
public class DevicePolicyManager {
+ method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
+ method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public java.util.List<android.content.ComponentName> getActiveAdmins();
method public boolean getCameraDisabled(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 873db8e..7efb3f1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -358,10 +358,11 @@
ctx.mMainThread.getHandler());
}});
- registerService(CONNECTIVITY_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
+ registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
- return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
+ return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b),
+ ctx.getPackageName());
}});
registerService(COUNTRY_DETECTOR, new StaticServiceFetcher() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0cc878e..b4488cd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -22,6 +22,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.Context;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -1766,4 +1767,53 @@
}
return null;
}
+
+ /**
+ * Called by a profile owner or device owner to add a default intent handler activity for
+ * intents that match a certain intent filter. This activity will remain the default intent
+ * handler even if the set of potential event handlers for the intent filter changes and if
+ * the intent preferences are reset.
+ *
+ * <p>The default disambiguation mechanism takes over if the activity is not installed
+ * (anymore). When the activity is (re)installed, it is automatically reset as default
+ * intent handler for the filter.
+ *
+ * <p>The calling device admin must be a profile owner or device owner. If it is not, a
+ * security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param filter The IntentFilter for which a default handler is added.
+ * @param activity The Activity that is added as default intent handler.
+ */
+ public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter,
+ ComponentName activity) {
+ if (mService != null) {
+ try {
+ mService.addPersistentPreferredActivity(admin, filter, activity);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner or device owner to remove all persistent intent handler preferences
+ * associated with the given package that were set by {@link #addPersistentPreferredActivity}.
+ *
+ * <p>The calling device admin must be a profile owner. If it is not, a security
+ * exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The name of the package for which preferences are removed.
+ */
+ public void clearPackagePersistentPreferredActivities(ComponentName admin,
+ String packageName) {
+ if (mService != null) {
+ try {
+ mService.clearPackagePersistentPreferredActivities(admin, packageName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9d189db..8119585 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,7 @@
package android.app.admin;
import android.content.ComponentName;
+import android.content.IntentFilter;
import android.os.RemoteCallback;
/**
@@ -109,4 +110,7 @@
boolean installCaCert(in byte[] certBuffer);
void uninstallCaCert(in byte[] certBuffer);
+
+ void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
+ void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 20002ad..c9fb530 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -237,6 +237,10 @@
int getPreferredActivities(out List<IntentFilter> outFilters,
out List<ComponentName> outActivities, String packageName);
+ void addPersistentPreferredActivity(in IntentFilter filter, in ComponentName activity, int userId);
+
+ void clearPackagePersistentPreferredActivities(String packageName, int userId);
+
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
* is the current "always use this one" setting.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index cbd6ec3..c4b07cc 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1468,14 +1468,16 @@
/**
* <p>AE is off or recently reset. When a camera device is opened, it starts in
- * this state.</p>
+ * this state. This is a transient state, the camera device may skip reporting
+ * this state in capture result.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_INACTIVE = 0;
/**
* <p>AE doesn't yet have a good set of control values
- * for the current scene.</p>
+ * for the current scene. This is a transient state, the camera device may skip
+ * reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_SEARCHING = 1;
@@ -1506,7 +1508,8 @@
* (through the {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} START),
* and is currently executing it. Once PRECAPTURE
* completes, AE will transition to CONVERGED or
- * FLASH_REQUIRED as appropriate.</p>
+ * FLASH_REQUIRED as appropriate. This is a transient state, the
+ * camera device may skip reporting this state in capture result.</p>
*
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
* @see CaptureResult#CONTROL_AE_STATE
@@ -1520,7 +1523,8 @@
/**
* <p>AF off or has not yet tried to scan/been asked
* to scan. When a camera device is opened, it starts in
- * this state.</p>
+ * this state. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_INACTIVE = 0;
@@ -1528,7 +1532,8 @@
/**
* <p>if CONTINUOUS_* modes are supported. AF is
* currently doing an AF scan initiated by a continuous
- * autofocus mode</p>
+ * autofocus mode. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_SCAN = 1;
@@ -1536,15 +1541,17 @@
/**
* <p>if CONTINUOUS_* modes are supported. AF currently
* believes it is in focus, but may restart scanning at
- * any time.</p>
+ * any time. This is a transient state, the camera device may skip
+ * reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_FOCUSED = 2;
/**
* <p>if AUTO or MACRO modes are supported. AF is doing
- * an AF scan because it was triggered by AF
- * trigger</p>
+ * an AF scan because it was triggered by AF trigger. This is a
+ * transient state, the camera device may skip reporting
+ * this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_ACTIVE_SCAN = 3;
@@ -1552,7 +1559,7 @@
/**
* <p>if any AF mode besides OFF is supported. AF
* believes it is focused correctly and is
- * locked</p>
+ * locked.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_FOCUSED_LOCKED = 4;
@@ -1560,7 +1567,7 @@
/**
* <p>if any AF mode besides OFF is supported. AF has
* failed to focus successfully and is
- * locked</p>
+ * locked.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_NOT_FOCUSED_LOCKED = 5;
@@ -1568,7 +1575,8 @@
/**
* <p>if CONTINUOUS_* modes are supported. AF finished a
* passive scan without finding focus, and may restart
- * scanning at any time.</p>
+ * scanning at any time. This is a transient state, the camera
+ * device may skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_UNFOCUSED = 6;
@@ -1579,14 +1587,16 @@
/**
* <p>AWB is not in auto mode. When a camera device is opened, it
- * starts in this state.</p>
+ * starts in this state. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_INACTIVE = 0;
/**
* <p>AWB doesn't yet have a good set of control
- * values for the current scene.</p>
+ * values for the current scene. This is a transient state, the camera device
+ * may skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_SEARCHING = 1;
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index bd9ba2f..9981bf9 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -370,6 +370,54 @@
* </tr>
* </tbody>
* </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions
+ * listed in above table, it is also legal for the camera device to skip one or more
+ * transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td>
+ * </tr>
+ * </tbody>
+ * </table>
*
* @see CaptureRequest#CONTROL_AE_LOCK
* @see CaptureRequest#CONTROL_AE_MODE
@@ -431,7 +479,7 @@
new Key<int[]>("android.control.afRegions", int[].class);
/**
- * <p>Current state of AF algorithm</p>
+ * <p>Current state of AF algorithm.</p>
* <p>Switching between or enabling AF modes ({@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}) always
* resets the AF state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode},
* or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all
@@ -529,6 +577,48 @@
* </tr>
* </tbody>
* </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for these AF modes (AF_MODE_AUTO and AF_MODE_MACRO), in addition to the
+ * state transitions listed in above table, it is also legal for the camera device to skip
+ * one or more transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Focus failed after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is good after a scan, lens is not locked.</td>
+ * </tr>
+ * </tbody>
+ * </table>
* <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_CONTINUOUS_VIDEO:</p>
* <table>
* <thead>
@@ -735,6 +825,41 @@
* </tr>
* </tbody>
* </table>
+ * <p>When switch between AF_MODE_CONTINUOUS_* (CAF modes) and AF_MODE_AUTO/AF_MODE_MACRO
+ * (AUTO modes), the initial INACTIVE or PASSIVE_SCAN states may be skipped by the
+ * camera device. When a trigger is included in a mode switch request, the trigger
+ * will be evaluated in the context of the new mode in the request.
+ * See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">CAF-->AUTO mode switch</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td>
+ * </tr>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">CAF-->AUTO mode switch with AF_TRIGGER</td>
+ * <td align="center">trigger-reachable states from INACTIVE</td>
+ * <td align="center">Mode switch with trigger, INACTIVE is skipped</td>
+ * </tr>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">AUTO-->CAF mode switch</td>
+ * <td align="center">passively reachable states from INACTIVE</td>
+ * <td align="center">Mode switch without trigger, passive transient state is skipped</td>
+ * </tr>
+ * </tbody>
+ * </table>
*
* @see CaptureRequest#CONTROL_AF_MODE
* @see CaptureRequest#CONTROL_MODE
@@ -905,11 +1030,35 @@
* <td align="center">SEARCHING</td>
* <td align="center">Values not good after unlock</td>
* </tr>
+ * </tbody>
+ * </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for this AWB mode (AWB_MODE_AUTO), in addition to the state transitions
+ * listed in above table, it is also legal for the camera device to skip one or more
+ * transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device finished AWB scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * </tr>
* <tr>
* <td align="center">LOCKED</td>
* <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
* <td align="center">CONVERGED</td>
- * <td align="center">Values good after unlock</td>
+ * <td align="center">Values good after unlock, transient states are skipped by camera device.</td>
* </tr>
* </tbody>
* </table>
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 3a35cb9..e6b9d4c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -400,6 +400,8 @@
private final IConnectivityManager mService;
+ private final String mPackageName;
+
/**
* Tests if a given integer represents a valid network type.
* @param networkType the type to be tested
@@ -811,7 +813,7 @@
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
byte[] address = hostAddress.getAddress();
try {
- return mService.requestRouteToHostAddress(networkType, address);
+ return mService.requestRouteToHostAddress(networkType, address, mPackageName);
} catch (RemoteException e) {
return false;
}
@@ -907,8 +909,9 @@
/**
* {@hide}
*/
- public ConnectivityManager(IConnectivityManager service) {
+ public ConnectivityManager(IConnectivityManager service, String packageName) {
mService = checkNotNull(service, "missing IConnectivityManager");
+ mPackageName = checkNotNull(packageName, "missing package name");
}
/** {@hide} */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b3217eb..381a817 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -71,9 +71,9 @@
int stopUsingNetworkFeature(int networkType, in String feature);
- boolean requestRouteToHost(int networkType, int hostAddress);
+ boolean requestRouteToHost(int networkType, int hostAddress, String packageName);
- boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
+ boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName);
boolean getMobileDataEnabled();
void setMobileDataEnabled(boolean enabled);
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 5df5811..9f2bf33 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2007-2008 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
@@ -64,7 +64,7 @@
* The Service that implements this input method component.
*/
final ResolveInfo mService;
-
+
/**
* The unique string Id to identify the input method. This is generated
* from the input method component.
@@ -144,22 +144,22 @@
throw new XmlPullParserException("No "
+ InputMethod.SERVICE_META_DATA + " meta-data");
}
-
+
Resources res = pm.getResourcesForApplication(si.applicationInfo);
-
+
AttributeSet attrs = Xml.asAttributeSet(parser);
-
+
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
}
-
+
String nodeName = parser.getName();
if (!"input-method".equals(nodeName)) {
throw new XmlPullParserException(
"Meta-data does not start with input-method tag");
}
-
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.InputMethod);
settingsActivityComponent = sa.getString(
@@ -338,7 +338,7 @@
/**
* Load the user-displayed label for this input method.
- *
+ *
* @param pm Supply a PackageManager used to load the input method's
* resources.
*/
@@ -348,7 +348,7 @@
/**
* Load the user-displayed icon for this input method.
- *
+ *
* @param pm Supply a PackageManager used to load the input method's
* resources.
*/
@@ -362,7 +362,7 @@
* an {@link android.content.Intent} whose action is MAIN and with an
* explicit {@link android.content.ComponentName}
* composed of {@link #getPackageName} and the class name returned here.
- *
+ *
* <p>A null will be returned if there is no settings activity associated
* with the input method.
*/
@@ -419,7 +419,7 @@
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
}
-
+
@Override
public String toString() {
return "InputMethodInfo{" + mId
@@ -430,7 +430,7 @@
/**
* Used to test whether the given parameter object is an
* {@link InputMethodInfo} and its Id is the same to this one.
- *
+ *
* @return true if the given parameter object is an
* {@link InputMethodInfo} and its Id is the same to this one.
*/
@@ -467,7 +467,7 @@
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
diff --git a/media/java/android/media/IMediaHTTPConnection.aidl b/media/java/android/media/IMediaHTTPConnection.aidl
index 300211b..55ffc2e 100644
--- a/media/java/android/media/IMediaHTTPConnection.aidl
+++ b/media/java/android/media/IMediaHTTPConnection.aidl
@@ -29,5 +29,6 @@
int readAt(long offset, int size);
long getSize();
String getMIMEType();
+ String getUri();
}
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index 2732fbc..67680a8 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -16,7 +16,6 @@
package android.media;
-import android.net.Uri;
import android.os.IBinder;
import android.os.StrictMode;
import android.util.Log;
@@ -52,6 +51,7 @@
native_setup();
}
+ @Override
public IBinder connect(String uri, String headers) {
if (VERBOSE) {
Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
@@ -85,6 +85,7 @@
return map;
}
+ @Override
public void disconnect() {
teardownConnection();
mHeaders = null;
@@ -120,7 +121,11 @@
"Range", "bytes=" + offset + "-");
}
- if (mConnection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
+ int response = mConnection.getResponseCode();
+ // remember the current, possibly redirected URL
+ mURL = mConnection.getURL();
+
+ if (response == HttpURLConnection.HTTP_PARTIAL) {
// Partial content, we cannot just use getContentLength
// because what we want is not just the length of the range
// returned but the size of the full content if available.
@@ -145,16 +150,13 @@
}
}
}
- } else if (mConnection.getResponseCode()
- != HttpURLConnection.HTTP_OK) {
+ } else if (response != HttpURLConnection.HTTP_OK) {
throw new IOException();
} else {
mTotalSize = mConnection.getContentLength();
}
- if (offset > 0
- && mConnection.getResponseCode()
- != HttpURLConnection.HTTP_PARTIAL) {
+ if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
// Some servers simply ignore "Range" requests and serve
// data from the start of the content.
throw new IOException();
@@ -174,6 +176,7 @@
}
}
+ @Override
public int readAt(long offset, int size) {
return native_readAt(offset, size);
}
@@ -218,6 +221,7 @@
}
}
+ @Override
public long getSize() {
if (mConnection == null) {
try {
@@ -230,6 +234,7 @@
return mTotalSize;
}
+ @Override
public String getMIMEType() {
if (mConnection == null) {
try {
@@ -243,6 +248,11 @@
}
@Override
+ public String getUri() {
+ return mURL.toString();
+ }
+
+ @Override
protected void finalize() {
native_finalize();
}
@@ -260,4 +270,5 @@
}
private int mNativeContext;
+
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f8a7bb6..21e2f4b 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -417,8 +417,8 @@
setParameter("time-lapse-enable=1");
double timeBetweenFrameCapture = 1 / fps;
- int timeBetweenFrameCaptureMs = (int) (1000 * timeBetweenFrameCapture);
- setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureMs);
+ long timeBetweenFrameCaptureUs = (long) (1000000 * timeBetweenFrameCapture);
+ setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureUs);
}
/**
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
index 599522b..d7069cac 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java
@@ -58,6 +58,7 @@
private MediaRecorder mRecorder;
private int MIN_VIDEO_FPS = 5;
+ private int HIGH_SPEED_FPS = 120;
private static final int CAMERA_ID = 0;
@@ -221,10 +222,12 @@
return success;
}
- private boolean recordVideoFromSurface(int frameRate, int width, int height,
+ private boolean recordVideoFromSurface(
+ int frameRate, int captureRate, int width, int height,
int videoFormat, int outFormat, String outFile, boolean videoOnly) {
Log.v(TAG,"recordVideoFromSurface");
MediaRecorder recorder = new MediaRecorder();
+ int sleepTime = 33; // normal capture at 33ms / frame
try {
if (!videoOnly) {
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -233,6 +236,10 @@
recorder.setOutputFormat(outFormat);
recorder.setOutputFile(outFile);
recorder.setVideoFrameRate(frameRate);
+ if (captureRate > 0) {
+ recorder.setCaptureRate(captureRate);
+ sleepTime = 1000 / captureRate;
+ }
recorder.setVideoSize(width, height);
recorder.setVideoEncoder(videoFormat);
if (!videoOnly) {
@@ -256,7 +263,7 @@
String text = "Frame #" + i;
canvas.drawText(text, 100, 100, paint);
surface.unlockCanvasAndPost(canvas);
- Thread.sleep(33);
+ Thread.sleep(sleepTime);
}
Log.v(TAG, "start");
@@ -270,7 +277,7 @@
String text = "Frame #" + i;
canvas.drawText(text, 100, 100, paint);
surface.unlockCanvasAndPost(canvas);
- Thread.sleep(33);
+ Thread.sleep(sleepTime);
}
Log.v(TAG, "stop");
@@ -517,7 +524,7 @@
String filename = "/sdcard/surface_" +
(k==0?"video_only":"with_audio") + ".3gp";
- success = recordVideoFromSurface(frameRate, 352, 288, codec,
+ success = recordVideoFromSurface(frameRate, 0, 352, 288, codec,
MediaRecorder.OutputFormat.THREE_GPP, filename,
k == 0 ? true : false /* videoOnly */);
if (success) {
@@ -532,4 +539,38 @@
}
assertTrue("testSurfaceRecording", noOfFailure == 0);
}
+
+ // Test recording from surface source with/without audio
+ public void testSurfaceRecordingTimeLapse() {
+ boolean success = false;
+ int noOfFailure = 0;
+ try {
+ int codec = MediaRecorder.VideoEncoder.H264;
+ int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec);
+ for (int k = 0; k < 2; k++) {
+ // k==0: time lapse test, set capture rate to MIN_VIDEO_FPS
+ // k==1: slow motion test, set capture rate to HIGH_SPEED_FPS
+ String filename = "/sdcard/surface_" +
+ (k==0 ? "time_lapse" : "slow_motion") + ".3gp";
+
+ // always set videoOnly=false, MediaRecorder should disable
+ // audio automatically with time lapse/slow motion
+ success = recordVideoFromSurface(frameRate,
+ k==0 ? MIN_VIDEO_FPS : HIGH_SPEED_FPS,
+ 352, 288, codec,
+ MediaRecorder.OutputFormat.THREE_GPP,
+ filename, false /* videoOnly */);
+ if (success) {
+ success = validateVideo(filename, 352, 288);
+ }
+ if (!success) {
+ noOfFailure++;
+ }
+ }
+ } catch (Exception e) {
+ Log.v(TAG, e.toString());
+ }
+ assertTrue("testSurfaceRecordingTimeLapse", noOfFailure == 0);
+ }
+
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 342336e..97b78e5 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -33,6 +33,7 @@
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -44,7 +45,9 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -414,6 +417,8 @@
private SettingsObserver mSettingsObserver;
+ private AppOpsManager mAppOpsManager;
+
NetworkConfig[] mNetConfigs;
int mNetworksDefined;
@@ -697,6 +702,8 @@
filter = new IntentFilter();
filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
mContext.registerReceiver(mProvisioningReceiver, filter);
+
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
/**
@@ -1531,6 +1538,40 @@
}
/**
+ * Check if the address falls into any of currently running VPN's route's.
+ */
+ private boolean isAddressUnderVpn(InetAddress address) {
+ synchronized (mVpns) {
+ synchronized (mRoutesLock) {
+ int uid = UserHandle.getCallingUserId();
+ Vpn vpn = mVpns.get(uid);
+ if (vpn == null) {
+ return false;
+ }
+
+ // Check if an exemption exists for this address.
+ for (LinkAddress destination : mExemptAddresses) {
+ if (!NetworkUtils.addressTypeMatches(address, destination.getAddress())) {
+ continue;
+ }
+
+ int prefix = destination.getNetworkPrefixLength();
+ InetAddress addrMasked = NetworkUtils.getNetworkPart(address, prefix);
+ InetAddress destMasked = NetworkUtils.getNetworkPart(destination.getAddress(),
+ prefix);
+
+ if (addrMasked.equals(destMasked)) {
+ return false;
+ }
+ }
+
+ // Finally check if the address is covered by the VPN.
+ return vpn.isAddressCovered(address);
+ }
+ }
+ }
+
+ /**
* @deprecated use requestRouteToHostAddress instead
*
* Ensure that a network route exists to deliver traffic to the specified
@@ -1541,14 +1582,14 @@
* desired
* @return {@code true} on success, {@code false} on failure
*/
- public boolean requestRouteToHost(int networkType, int hostAddress) {
+ public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) {
InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
if (inetAddress == null) {
return false;
}
- return requestRouteToHostAddress(networkType, inetAddress.getAddress());
+ return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName);
}
/**
@@ -1560,11 +1601,40 @@
* desired
* @return {@code true} on success, {@code false} on failure
*/
- public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
+ public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
+ String packageName) {
enforceChangePermission();
if (mProtectedNetworks.contains(networkType)) {
enforceConnectivityInternalPermission();
}
+ boolean exempt;
+ InetAddress addr;
+ try {
+ addr = InetAddress.getByAddress(hostAddress);
+ } catch (UnknownHostException e) {
+ if (DBG) log("requestRouteToHostAddress got " + e.toString());
+ return false;
+ }
+ // System apps may request routes bypassing the VPN to keep other networks working.
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ exempt = true;
+ } else {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName,
+ 0);
+ exempt = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Failed to find calling package details", e);
+ }
+ }
+
+ // Non-exempt routeToHost's can only be added if the host is not covered by the VPN.
+ // This can be either because the VPN's routes do not cover the destination or a
+ // system application added an exemption that covers this destination.
+ if (!exempt && isAddressUnderVpn(addr)) {
+ return false;
+ }
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
@@ -1591,18 +1661,13 @@
}
final long token = Binder.clearCallingIdentity();
try {
- InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
- boolean ok = addRouteToAddress(lp, addr, EXEMPT);
+ boolean ok = addRouteToAddress(lp, addr, exempt);
if (DBG) log("requestRouteToHostAddress ok=" + ok);
return ok;
- } catch (UnknownHostException e) {
- if (DBG) log("requestRouteToHostAddress got " + e.toString());
} finally {
Binder.restoreCallingIdentity(token);
}
- if (DBG) log("requestRouteToHostAddress X bottom return false");
- return false;
}
private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
@@ -4334,7 +4399,7 @@
// Make a route to host so we check the specific interface.
if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
- hostAddr.getAddress())) {
+ hostAddr.getAddress(), null)) {
// Wait a short time to be sure the route is established ??
log("isMobileOk:"
+ " wait to establish route to hostAddr=" + hostAddr);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d87387f..af08d57 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -44,6 +44,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.NetworkInfo;
+import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Binder;
@@ -74,6 +75,7 @@
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetAddress;
import java.net.Inet4Address;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -430,6 +432,18 @@
return tun;
}
+ /**
+ * Check if a given address is covered by the VPN's routing rules.
+ */
+ public boolean isAddressCovered(InetAddress address) {
+ synchronized (Vpn.this) {
+ if (!isRunningLocked()) {
+ return false;
+ }
+ return RouteInfo.selectBestRoute(mConfig.routes, address) != null;
+ }
+ }
+
private boolean isRunningLocked() {
return mVpnUsers != null;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 84f0f2e..07a74cd 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2777,6 +2777,63 @@
return null;
}
+ private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+ int flags, List<ResolveInfo> query, boolean debug, int userId) {
+ final int N = query.size();
+ PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities
+ .get(userId);
+ // Get the list of persistent preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
+ List<PersistentPreferredActivity> pprefs = ppir != null
+ ? ppir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId)
+ : null;
+ if (pprefs != null && pprefs.size() > 0) {
+ final int M = pprefs.size();
+ for (int i=0; i<M; i++) {
+ final PersistentPreferredActivity ppa = pprefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+ + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+ + "\n component=" + ppa.mComponent);
+ ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+ flags | PackageManager.GET_DISABLED_COMPONENTS, userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found persistent preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ if (ai == null) {
+ // This previously registered persistent preferred activity
+ // component is no longer known. Ignore it and do NOT remove it.
+ continue;
+ }
+ for (int j=0; j<N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+ // Found a persistent preference that can handle the intent.
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning persistent preferred activity: " +
+ ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ return ri;
+ }
+ }
+ }
+ return null;
+ }
+
ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags,
List<ResolveInfo> query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId) {
@@ -2787,6 +2844,16 @@
intent = intent.getSelector();
}
if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+
+ // Try to find a matching persistent preferred activity.
+ ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
+ debug, userId);
+
+ // If a persistent preferred activity matched, use it.
+ if (pri != null) {
+ return pri;
+ }
+
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
// Get the list of preferred activities that handle the intent
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
@@ -10341,6 +10408,71 @@
}
@Override
+ public void addPersistentPreferredActivity(IntentFilter filter, ComponentName activity,
+ int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "addPersistentPreferredActivity can only be run by the system");
+ }
+ if (filter.countActions() == 0) {
+ Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+ return;
+ }
+ synchronized (mPackages) {
+ Slog.i(TAG, "Adding persistent preferred activity " + activity + " for user " + userId +
+ " :");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
+ mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
+ new PersistentPreferredActivity(filter, activity));
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ }
+
+ @Override
+ public void clearPackagePersistentPreferredActivities(String packageName, int userId) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException(
+ "clearPackagePersistentPreferredActivities can only be run by the system");
+ }
+ ArrayList<PersistentPreferredActivity> removed = null;
+ boolean changed = false;
+ synchronized (mPackages) {
+ for (int i=0; i<mSettings.mPersistentPreferredActivities.size(); i++) {
+ final int thisUserId = mSettings.mPersistentPreferredActivities.keyAt(i);
+ PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities
+ .valueAt(i);
+ if (userId != thisUserId) {
+ continue;
+ }
+ Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
+ while (it.hasNext()) {
+ PersistentPreferredActivity ppa = it.next();
+ // Mark entry for removal only if it matches the package name.
+ if (ppa.mComponent.getPackageName().equals(packageName)) {
+ if (removed == null) {
+ removed = new ArrayList<PersistentPreferredActivity>();
+ }
+ removed.add(ppa);
+ }
+ }
+ if (removed != null) {
+ for (int j=0; j<removed.size(); j++) {
+ PersistentPreferredActivity ppa = removed.get(j);
+ ppir.removeFilter(ppa);
+ }
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ }
+ }
+
+ @Override
public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
new file mode 100644
index 0000000..4284a6d4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.IOException;
+
+class PersistentPreferredActivity extends IntentFilter {
+ private static final String ATTR_NAME = "name"; // component name
+ private static final String ATTR_FILTER = "filter"; // filter
+
+ private static final String TAG = "PersistentPreferredActivity";
+
+ private static final boolean DEBUG_FILTERS = false;
+
+ final ComponentName mComponent;
+
+ PersistentPreferredActivity(IntentFilter filter, ComponentName activity) {
+ super(filter);
+ mComponent = activity;
+ }
+
+ PersistentPreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException {
+ String shortComponent = parser.getAttributeValue(null, ATTR_NAME);
+ mComponent = ComponentName.unflattenFromString(shortComponent);
+ if (mComponent == null) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings <hard-preferred-activity>: " +
+ "Bad activity name " + shortComponent +
+ " at " + parser.getPositionDescription());
+ }
+ int outerDepth = parser.getDepth();
+ String tagName = parser.getName();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ tagName = parser.getName();
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ } else if (type == XmlPullParser.START_TAG) {
+ if (tagName.equals(ATTR_FILTER)) {
+ break;
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <hard-preferred-activity>: " + tagName +
+ " at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+ if (tagName.equals(ATTR_FILTER)) {
+ readFromXml(parser);
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Missing element under <hard-preferred-activity>: filter at " +
+ parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_NAME, mComponent.flattenToShortString());
+ serializer.startTag(null, ATTR_FILTER);
+ super.writeToXml(serializer);
+ serializer.endTag(null, ATTR_FILTER);
+ }
+
+ @Override
+ public String toString() {
+ return "PersistentPreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this))
+ + " " + mComponent.flattenToShortString() + "}";
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
new file mode 100644
index 0000000..9c8a9bd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.io.PrintWriter;
+
+import com.android.server.IntentResolver;
+
+public class PersistentPreferredIntentResolver
+ extends IntentResolver<PersistentPreferredActivity, PersistentPreferredActivity> {
+ @Override
+ protected PersistentPreferredActivity[] newArray(int size) {
+ return new PersistentPreferredActivity[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, PersistentPreferredActivity filter) {
+ return packageName.equals(filter.mComponent.getPackageName());
+ }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e7c6446..9236bde 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -141,6 +141,11 @@
final SparseArray<PreferredIntentResolver> mPreferredActivities =
new SparseArray<PreferredIntentResolver>();
+ // The persistent preferred activities of the user's profile/device owner
+ // associated with particular intent filters.
+ final SparseArray<PersistentPreferredIntentResolver> mPersistentPreferredActivities =
+ new SparseArray<PersistentPreferredIntentResolver>();
+
final HashMap<String, SharedUserSetting> mSharedUsers =
new HashMap<String, SharedUserSetting>();
private final ArrayList<Object> mUserIds = new ArrayList<Object>();
@@ -776,6 +781,15 @@
return pir;
}
+ PersistentPreferredIntentResolver editPersistentPreferredActivitiesLPw(int userId) {
+ PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
+ if (ppir == null) {
+ ppir = new PersistentPreferredIntentResolver();
+ mPersistentPreferredActivities.put(userId, ppir);
+ }
+ return ppir;
+ }
+
private File getUserPackagesStateFile(int userId) {
return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
}
@@ -835,6 +849,27 @@
}
}
+ private void readPersistentPreferredActivitiesLPw(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ITEM)) {
+ PersistentPreferredActivity ppa = new PersistentPreferredActivity(parser);
+ editPersistentPreferredActivitiesLPw(userId).addFilter(ppa);
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <hard-preferred-activities>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
void readPackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Reading package restrictions for user=" + userId);
@@ -961,6 +996,8 @@
enabledCaller, enabledComponents, disabledComponents);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
+ } else if (tagName.equals("hard-preferred-activities")) {
+ readPersistentPreferredActivitiesLPw(parser, userId);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+ parser.getName());
@@ -1024,6 +1061,20 @@
serializer.endTag(null, "preferred-activities");
}
+ void writePersistentPreferredActivitiesLPr(XmlSerializer serializer, int userId)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ serializer.startTag(null, "hard-preferred-activities");
+ PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
+ if (ppir != null) {
+ for (final PersistentPreferredActivity ppa : ppir.filterSet()) {
+ serializer.startTag(null, TAG_ITEM);
+ ppa.writeToXml(serializer);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ }
+ serializer.endTag(null, "hard-preferred-activities");
+ }
+
void writePackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Writing package restrictions for user=" + userId);
@@ -1120,6 +1171,8 @@
writePreferredActivitiesLPr(serializer, userId, true);
+ writePersistentPreferredActivitiesLPr(serializer, userId);
+
serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
serializer.endDocument();
@@ -1721,6 +1774,10 @@
// Upgrading from old single-user implementation;
// these are the preferred activities for user 0.
readPreferredActivitiesLPw(parser, 0);
+ } else if (tagName.equals("hard-preferred-activities")) {
+ // TODO: check whether this is okay! as it is very
+ // similar to how preferred-activities are treated
+ readPersistentPreferredActivitiesLPw(parser, 0);
} else if (tagName.equals("updated-package")) {
readDisabledSysPackageLPw(parser);
} else if (tagName.equals("cleaning-package")) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f9a5e5d..f186b2c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2965,4 +2965,66 @@
}
}
}
+
+ private boolean isProfileOwner(String packageName, int userId) {
+ String profileOwnerPackage = getProfileOwner(userId);
+ // TODO: make public and connect with isProfileOwnerApp in DPM
+ return profileOwnerPackage != null && profileOwnerPackage.equals(packageName);
+ }
+
+ public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter,
+ ComponentName activity) {
+ int callingUserId = UserHandle.getCallingUserId();
+ Slog.d(LOG_TAG,"called by user " + callingUserId);
+ synchronized (this) {
+ ActiveAdmin aa = getActiveAdminUncheckedLocked(admin, callingUserId);
+ if (aa == null) {
+ throw new SecurityException("No active admin " + admin);
+ } else {
+ if (isProfileOwner(admin.getPackageName(), callingUserId)
+ || isDeviceOwner(admin.getPackageName())) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ long id = Binder.clearCallingIdentity();
+ try {
+ pm.addPersistentPreferredActivity(filter, activity, callingUserId);
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ } else {
+ throw new SecurityException("Admin " + admin +
+ "is not device owner or profile owner" );
+ }
+ }
+ }
+ }
+
+ public void clearPackagePersistentPreferredActivities(ComponentName admin,
+ String packageName) {
+ int callingUserId = UserHandle.getCallingUserId();
+ Slog.d(LOG_TAG,"called by user " + callingUserId);
+ synchronized (this) {
+ ActiveAdmin aa = getActiveAdminUncheckedLocked(admin, callingUserId);
+ if (aa == null) {
+ throw new SecurityException("No active admin " + admin);
+ } else {
+ if (isProfileOwner(admin.getPackageName(), callingUserId)
+ || isDeviceOwner(admin.getPackageName())) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ long id = Binder.clearCallingIdentity();
+ try{
+ pm.clearPackagePersistentPreferredActivities(packageName, callingUserId);
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ } else {
+ throw new SecurityException("Admin " + admin +
+ "is not device owner or profile owner" );
+ }
+ }
+ }
+ }
}