Merge "Fall-through for ActionModes with ToolbarActionBar" into lmp-dev
diff --git a/Android.mk b/Android.mk
index 273f5f6..52c2d16 100644
--- a/Android.mk
+++ b/Android.mk
@@ -630,7 +630,7 @@
-since $(SRC_API_DIR)/18.txt 18 \
-since $(SRC_API_DIR)/19.txt 19 \
-since $(SRC_API_DIR)/20.txt 20 \
- -werror -hide 113 \
+ -werror -hide 111 -hide 113 \
-overview $(LOCAL_PATH)/core/java/overview.html
framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \
diff --git a/api/current.txt b/api/current.txt
index 4fbebf8..b2a73fe 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5380,6 +5380,7 @@
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
+ method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
@@ -5396,6 +5397,7 @@
method public boolean getBlockUninstall(android.content.ComponentName, java.lang.String);
method public boolean getCameraDisabled(android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -5429,6 +5431,7 @@
method public boolean isProfileOwnerApp(java.lang.String);
method public void lockNow();
method public void removeActiveAdmin(android.content.ComponentName);
+ method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
method public boolean resetPassword(java.lang.String, int);
method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
@@ -5734,6 +5737,7 @@
method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo);
method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo);
method protected void onProvidersChanged();
+ method public final void startAppWidgetConfigureActivityForResult(android.app.Activity, android.content.Intent, int);
method public void startListening();
method public void stopListening();
}
@@ -5756,10 +5760,12 @@
public class AppWidgetManager {
method public boolean bindAppWidgetIdIfAllowed(int, android.content.ComponentName);
method public boolean bindAppWidgetIdIfAllowed(int, android.content.ComponentName, android.os.Bundle);
+ method public boolean bindAppWidgetIdIfAllowed(int, android.os.UserHandle, android.content.ComponentName, android.os.Bundle);
method public int[] getAppWidgetIds(android.content.ComponentName);
method public android.appwidget.AppWidgetProviderInfo getAppWidgetInfo(int);
method public android.os.Bundle getAppWidgetOptions(int);
method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProviders();
+ method public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfiles(android.os.UserHandle[]);
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method public void notifyAppWidgetViewDataChanged(int[], int);
method public void notifyAppWidgetViewDataChanged(int, int);
@@ -5784,6 +5790,7 @@
field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
+ field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
field public static final java.lang.String EXTRA_CUSTOM_INFO = "customInfo";
field public static final java.lang.String EXTRA_HOST_ID = "hostId";
@@ -5812,6 +5819,10 @@
ctor public AppWidgetProviderInfo(android.os.Parcel);
method public android.appwidget.AppWidgetProviderInfo clone();
method public int describeContents();
+ method public final android.os.UserHandle getProfile();
+ method public final android.graphics.drawable.Drawable loadIcon(android.content.Context, int);
+ method public final java.lang.String loadLabel(android.content.pm.PackageManager);
+ method public final android.graphics.drawable.Drawable loadPreviewImage(android.content.Context, int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int RESIZE_BOTH = 3; // 0x3
@@ -5823,15 +5834,15 @@
field public static final int WIDGET_CATEGORY_RECENTS = 4; // 0x4
field public int autoAdvanceViewId;
field public android.content.ComponentName configure;
- field public int icon;
+ field public deprecated int icon;
field public int initialKeyguardLayout;
field public int initialLayout;
- field public java.lang.String label;
+ field public deprecated java.lang.String label;
field public int minHeight;
field public int minResizeHeight;
field public int minResizeWidth;
field public int minWidth;
- field public int previewImage;
+ field public deprecated int previewImage;
field public android.content.ComponentName provider;
field public int resizeMode;
field public int updatePeriodMillis;
@@ -7277,6 +7288,7 @@
field public static final java.lang.String ACCOUNT_SERVICE = "account";
field public static final java.lang.String ACTIVITY_SERVICE = "activity";
field public static final java.lang.String ALARM_SERVICE = "alarm";
+ field public static final java.lang.String APPWIDGET_SERVICE = "appwidget";
field public static final java.lang.String APP_OPS_SERVICE = "appops";
field public static final java.lang.String AUDIO_SERVICE = "audio";
field public static final java.lang.String BATTERY_SERVICE = "batterymanager";
@@ -13235,6 +13247,7 @@
method public android.view.Display getDisplay();
method public android.view.Surface getSurface();
method public void release();
+ method public void resize(int, int, int);
method public void setSurface(android.view.Surface);
}
@@ -15118,6 +15131,7 @@
method public long getLong(java.lang.String);
method public android.media.Rating getRating(java.lang.String);
method public java.lang.String getString(java.lang.String);
+ method public java.lang.CharSequence getText(java.lang.String);
method public java.util.Set<java.lang.String> keySet();
method public int size();
method public void writeToParcel(android.os.Parcel, int);
@@ -15134,6 +15148,11 @@
field public static final java.lang.String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
field public static final java.lang.String METADATA_KEY_DATE = "android.media.metadata.DATE";
field public static final java.lang.String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
field public static final java.lang.String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
field public static final java.lang.String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
field public static final java.lang.String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
@@ -15153,6 +15172,7 @@
method public android.media.MediaMetadata.Builder putLong(java.lang.String, long);
method public android.media.MediaMetadata.Builder putRating(java.lang.String, android.media.Rating);
method public android.media.MediaMetadata.Builder putString(java.lang.String, java.lang.String);
+ method public android.media.MediaMetadata.Builder putText(java.lang.String, java.lang.CharSequence);
}
public abstract deprecated class MediaMetadataEditor {
@@ -16849,9 +16869,8 @@
package android.media.tv {
public final class TvContentRating {
- method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String...);
+ method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...);
method public java.lang.String flattenToString();
- method public java.lang.String getCountry();
method public java.lang.String getDomain();
method public java.lang.String getMainRating();
method public java.lang.String getRatingSystem();
@@ -17038,22 +17057,6 @@
method public void onInputStateChanged(java.lang.String, int);
}
- public abstract class TvInputPassthroughWrapperService extends android.media.tv.TvInputService {
- ctor public TvInputPassthroughWrapperService();
- method public abstract java.lang.String getPassthroughInputId(java.lang.String);
- method public abstract android.media.tv.TvInputPassthroughWrapperService.PassthroughWrapperSession onCreatePassthroughWrapperSession();
- method public final android.media.tv.TvInputService.Session onCreateSession(java.lang.String);
- }
-
- public abstract class TvInputPassthroughWrapperService.PassthroughWrapperSession extends android.media.tv.TvInputService.Session {
- ctor public TvInputPassthroughWrapperService.PassthroughWrapperSession();
- method public abstract void onPassthroughSessionCreationFailed();
- method public abstract void onPassthroughSessionReleased();
- method public abstract void onPassthroughVideoAvailable();
- method public abstract void onPassthroughVideoUnavailable(int);
- method public final boolean onSetSurface(android.view.Surface);
- }
-
public abstract class TvInputService extends android.app.Service {
ctor public TvInputService();
method public final android.os.IBinder onBind(android.content.Intent);
@@ -17062,13 +17065,21 @@
field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input";
}
+ public abstract class TvInputService.HardwareSession extends android.media.tv.TvInputService.Session {
+ ctor public TvInputService.HardwareSession();
+ method public abstract java.lang.String getHardwareInputId();
+ method public void onHardwareVideoAvailable();
+ method public void onHardwareVideoUnavailable(int);
+ method public final boolean onSetSurface(android.view.Surface);
+ }
+
public abstract class TvInputService.Session implements android.view.KeyEvent.Callback {
ctor public TvInputService.Session();
method public void notifyChannelRetuned(android.net.Uri);
method public void notifyContentAllowed();
method public void notifyContentBlocked(android.media.tv.TvContentRating);
- method public void notifyTrackInfoChanged(java.util.List<android.media.tv.TvTrackInfo>);
- method public void notifyTrackSelectionChanged(java.util.List<android.media.tv.TvTrackInfo>);
+ method public void notifyTrackSelected(int, java.lang.String);
+ method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>);
method public void notifyVideoAvailable();
method public void notifyVideoUnavailable(int);
method public android.view.View onCreateOverlayView();
@@ -17078,7 +17089,7 @@
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
method public boolean onKeyUp(int, android.view.KeyEvent);
method public abstract void onRelease();
- method public boolean onSelectTrack(android.media.tv.TvTrackInfo);
+ method public boolean onSelectTrack(int, java.lang.String);
method public abstract void onSetCaptionEnabled(boolean);
method public abstract void onSetStreamVolume(float);
method public abstract boolean onSetSurface(android.view.Surface);
@@ -17087,7 +17098,6 @@
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public void onUnblockContent(android.media.tv.TvContentRating);
- method public boolean onUnselectTrack(android.media.tv.TvTrackInfo);
method public void setOverlayViewEnabled(boolean);
}
@@ -17096,6 +17106,7 @@
method public final int getAudioChannelCount();
method public final int getAudioSampleRate();
method public final android.os.Bundle getExtra();
+ method public final java.lang.String getId();
method public final java.lang.String getLanguage();
method public final int getType();
method public final int getVideoHeight();
@@ -17108,7 +17119,7 @@
}
public static final class TvTrackInfo.Builder {
- ctor public TvTrackInfo.Builder(int);
+ ctor public TvTrackInfo.Builder(int, java.lang.String);
method public android.media.tv.TvTrackInfo build();
method public final android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
method public final android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
@@ -17123,18 +17134,17 @@
ctor public TvView(android.content.Context, android.util.AttributeSet);
ctor public TvView(android.content.Context, android.util.AttributeSet, int);
method public boolean dispatchUnhandledInputEvent(android.view.InputEvent);
- method public java.util.List<android.media.tv.TvTrackInfo> getSelectedTracks();
- method public java.util.List<android.media.tv.TvTrackInfo> getTracks();
+ method public java.lang.String getSelectedTrack(int);
+ method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
method protected void onLayout(boolean, int, int, int, int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void reset();
- method public void selectTrack(android.media.tv.TvTrackInfo);
+ method public void selectTrack(int, java.lang.String);
method public void setCaptionEnabled(boolean);
method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
method public void setStreamVolume(float);
method public void setTvInputListener(android.media.tv.TvView.TvInputListener);
method public void tune(java.lang.String, android.net.Uri);
- method public void unselectTrack(android.media.tv.TvTrackInfo);
field public static final int ERROR_INPUT_DISCONNECTED = 1; // 0x1
field public static final int ERROR_INPUT_NOT_CONNECTED = 0; // 0x0
}
@@ -17149,8 +17159,8 @@
method public void onContentAllowed(java.lang.String);
method public void onContentBlocked(java.lang.String, android.media.tv.TvContentRating);
method public void onError(java.lang.String, int);
- method public void onTrackInfoChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
- method public void onTrackSelectionChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
+ method public void onTrackSelected(java.lang.String, int, java.lang.String);
+ method public void onTracksChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
method public void onVideoAvailable(java.lang.String);
method public void onVideoSizeChanged(java.lang.String, int, int);
method public void onVideoUnavailable(java.lang.String, int);
diff --git a/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java b/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java
index dd45d39..5ea7352 100644
--- a/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java
+++ b/cmds/appwidget/src/com/android/commands/appwidget/AppWidget.java
@@ -150,7 +150,7 @@
IBinder binder = ServiceManager.getService(Context.APPWIDGET_SERVICE);
IAppWidgetService appWidgetService = IAppWidgetService.Stub.asInterface(binder);
try {
- appWidgetService.setBindAppWidgetPermission(mPackageName, mGranted, mUserId);
+ appWidgetService.setBindAppWidgetPermission(mPackageName, mUserId, mGranted);
} catch (RemoteException re) {
re.printStackTrace();
}
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 7757b91c..d9faf4c 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -191,7 +191,7 @@
@Override
public void onMetadataChanged(MediaMetadata metadata) {
String mmString = metadata == null ? null : "title=" + metadata
- .getString(MediaMetadata.METADATA_KEY_TITLE);
+ .getDescription();
System.out.println("onMetadataChanged " + mmString);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index caadecb..a52186a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -459,15 +459,15 @@
null, //POST_NOTIFICATION
null, //NEIGHBORING_CELLS
null, //CALL_PHONE
- null, //READ_SMS
- null, //WRITE_SMS
- null, //RECEIVE_SMS
- null, //RECEIVE_EMERGECY_SMS
- null, //RECEIVE_MMS
+ UserManager.DISALLOW_SMS, //READ_SMS
+ UserManager.DISALLOW_SMS, //WRITE_SMS
+ UserManager.DISALLOW_SMS, //RECEIVE_SMS
+ null, //RECEIVE_EMERGENCY_SMS
+ UserManager.DISALLOW_SMS, //RECEIVE_MMS
null, //RECEIVE_WAP_PUSH
- null, //SEND_SMS
- null, //READ_ICC_SMS
- null, //WRITE_ICC_SMS
+ UserManager.DISALLOW_SMS, //SEND_SMS
+ UserManager.DISALLOW_SMS, //READ_ICC_SMS
+ UserManager.DISALLOW_SMS, //WRITE_ICC_SMS
null, //WRITE_SETTINGS
UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
null, //ACCESS_NOTIFICATIONS
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 18ba8c4..c75c8b7 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1384,7 +1384,18 @@
public void replacePreferredActivity(IntentFilter filter,
int match, ComponentName[] set, ComponentName activity) {
try {
- mPM.replacePreferredActivity(filter, match, set, activity);
+ mPM.replacePreferredActivity(filter, match, set, activity, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public void replacePreferredActivityAsUser(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity,
+ int userId) {
+ try {
+ mPM.replacePreferredActivity(filter, match, set, activity, userId);
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5f606a61..4cf8cb4 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -18,10 +18,12 @@
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageStatsManager;
+import android.appwidget.AppWidgetManager;
import android.os.Build;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
+import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.policy.PolicyManager;
import com.android.internal.util.Preconditions;
@@ -148,7 +150,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
-import com.android.internal.appwidget.IAppWidgetService.Stub;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.telecomm.ITelecommService;
@@ -771,6 +772,12 @@
return new MediaProjectionManager(ctx);
}
});
+
+ registerService(APPWIDGET_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(APPWIDGET_SERVICE);
+ return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -2100,6 +2107,25 @@
}
@Override
+ public Context createApplicationContext(ApplicationInfo application, int flags)
+ throws NameNotFoundException {
+ LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
+ flags | CONTEXT_REGISTER_PACKAGE);
+ if (pi != null) {
+ final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
+ new UserHandle(UserHandle.getUserId(application.uid)), restricted,
+ mDisplay, mOverrideConfiguration);
+ if (c.mResources != null) {
+ return c;
+ }
+ }
+
+ throw new PackageManager.NameNotFoundException(
+ "Application package " + application.packageName + " not found");
+ }
+
+ @Override
public Context createPackageContext(String packageName, int flags)
throws NameNotFoundException {
return createPackageContextAsUser(packageName, flags,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 21525bc..797a0a0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1707,28 +1707,6 @@
}
}
- /** {@hide} */
- public void setUser(UserHandle user) {
- if (user.getIdentifier() == UserHandle.USER_ALL) {
- user = UserHandle.OWNER;
- }
- if (tickerView != null) {
- tickerView.setUser(user);
- }
- if (contentView != null) {
- contentView.setUser(user);
- }
- if (bigContentView != null) {
- bigContentView.setUser(user);
- }
- if (headsUpContentView != null) {
- headsUpContentView.setUser(user);
- }
- if (publicVersion != null) {
- publicVersion.setUser(user);
- }
- }
-
/**
* @hide
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e28f00c..ca6b1e8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -20,6 +20,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.app.Activity;
+import android.app.admin.IDevicePolicyManager;
import android.content.AbstractRestrictionsProvider;
import android.content.ComponentName;
import android.content.Context;
@@ -28,8 +29,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.RestrictionsManager;
-import android.media.AudioService;
import android.net.ProxyInfo;
import android.os.Bundle;
import android.os.Handler;
@@ -40,7 +39,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.trust.TrustAgentService;
import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -55,6 +53,7 @@
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -3079,4 +3078,85 @@
}
return false;
}
+
+ /**
+ * Called by the profile owner to enable widget providers from a given package
+ * to be available in the parent profile. As a result the user will be able to
+ * add widgets from the white-listed package running under the profile to a widget
+ * host which runs under the device owner, for example the home screen. Note that
+ * a package may have zero or more provider components, where each component
+ * provides a different widget type.
+ * <p>
+ * <strong>Note:</strong> By default no widget provider package is white-listed.
+ * </p>
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The package from which widget providers are white-listed.
+ * @return Whether the package was added.
+ *
+ * @see #removeCrossProfileWidgetProvider(android.content.ComponentName, String)
+ * @see #getCrossProfileWidgetProviders(android.content.ComponentName)
+ */
+ public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ if (mService != null) {
+ try {
+ return mService.addCrossProfileWidgetProvider(admin, packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Error calling addCrossProfileWidgetProvider", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by the profile owner to disable widget providers from a given package
+ * to be available in the parent profile. For this method to take effect the
+ * package should have been added via {@link #addCrossProfileWidgetProvider(
+ * android.content.ComponentName, String)}.
+ * <p>
+ * <strong>Note:</strong> By default no widget provider package is white-listed.
+ * </p>
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The package from which widget providers are no longer
+ * white-listed.
+ * @return Whether the package was removed.
+ *
+ * @see #addCrossProfileWidgetProvider(android.content.ComponentName, String)
+ * @see #getCrossProfileWidgetProviders(android.content.ComponentName)
+ */
+ public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ if (mService != null) {
+ try {
+ return mService.removeCrossProfileWidgetProvider(admin, packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Error calling removeCrossProfileWidgetProvider", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by the profile owner to query providers from which packages are
+ * available in the parent profile.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return The white-listed package list.
+ *
+ * @see #addCrossProfileWidgetProvider(android.content.ComponentName, String)
+ * @see #removeCrossProfileWidgetProvider(android.content.ComponentName, String)
+ */
+ public List<String> getCrossProfileWidgetProviders(ComponentName admin) {
+ if (mService != null) {
+ try {
+ List<String> providers = mService.getCrossProfileWidgetProviders(admin);
+ if (providers != null) {
+ return providers;
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Error calling getCrossProfileWidgetProviders", re);
+ }
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
new file mode 100644
index 0000000..edd8199
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.app.admin;
+
+import java.util.List;
+
+/**
+ * Device policy manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class DevicePolicyManagerInternal {
+
+ /**
+ * Gets the packages whose widget providers are white-listed to be
+ * available in the parent user.
+ *
+ * @param profileId The profile id.
+ * @return The list of packages if such or empty list if there are
+ * no white-listed packages or the profile id is not a managed
+ * profile.
+ */
+ public abstract List<String> getCrossProfileWidgetProviders(int profileId);
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 6ce737a..8954c0d 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -175,4 +175,7 @@
void setTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, in List<String> features, int userId);
List<String> getTrustAgentFeaturesEnabled(in ComponentName admin, in ComponentName agent, int userId);
+ boolean addCrossProfileWidgetProvider(in ComponentName admin, String packageName);
+ boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName);
+ List<String> getCrossProfileWidgetProviders(in ComponentName admin);
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 84d3835..e7b68f5 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -19,7 +19,11 @@
import java.util.ArrayList;
import java.util.HashMap;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -61,32 +65,30 @@
private OnClickHandler mOnClickHandler;
class Callbacks extends IAppWidgetHost.Stub {
- public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) {
+ public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
- views.setUser(new UserHandle(userId));
}
- Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views);
+ Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
msg.sendToTarget();
}
- public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) {
+ public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
if (isLocalBinder() && info != null) {
info = info.clone();
}
Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
- appWidgetId, userId, info);
+ appWidgetId, 0, info);
msg.sendToTarget();
}
- public void providersChanged(int userId) {
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0);
- msg.sendToTarget();
+ public void providersChanged() {
+ mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
}
- public void viewDataChanged(int appWidgetId, int viewId, int userId) {
+ public void viewDataChanged(int appWidgetId, int viewId) {
Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
- appWidgetId, viewId, userId);
+ appWidgetId, viewId);
msg.sendToTarget();
}
}
@@ -99,7 +101,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
- updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj, msg.arg2);
+ updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
break;
}
case HANDLE_PROVIDER_CHANGED: {
@@ -111,7 +113,7 @@
break;
}
case HANDLE_VIEW_DATA_CHANGED: {
- viewDataChanged(msg.arg1, msg.arg2, (Integer) msg.obj);
+ viewDataChanged(msg.arg1, msg.arg2);
break;
}
}
@@ -151,25 +153,20 @@
public void startListening() {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
-
- final int userId = mContext.getUserId();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- updatedIds = sService.startListening(
- mCallbacks, mPackageName, mHostId, updatedViews, userId);
+ updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId,
+ updatedViews);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
final int N = updatedIds.length;
- for (int i=0; i<N; i++) {
- if (updatedViews.get(i) != null) {
- updatedViews.get(i).setUser(new UserHandle(userId));
- }
- updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId);
+ for (int i = 0; i < N; i++) {
+ updateAppWidgetView(updatedIds[i], updatedViews.get(i));
}
}
@@ -179,7 +176,7 @@
*/
public void stopListening() {
try {
- sService.stopListening(mHostId, mContext.getUserId());
+ sService.stopListening(mPackageName, mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -200,7 +197,7 @@
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
+ return sService.allocateAppWidgetId(mPackageName, mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -208,18 +205,39 @@
}
/**
- * Get a appWidgetId for a host in the given package.
+ * Starts an app widget provider configure activity for result on behalf of the caller.
+ * Use this method if the provider is in another profile as you are not allowed to start
+ * an activity in another profile. The provided intent must have action {@link
+ * AppWidgetManager#ACTION_APPWIDGET_CONFIGURE}. The widget id must be specified by
+ * the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} extra. The provider configure
+ * activity must be specified as the component name of the intent via {@link
+ * Intent#setComponent(android.content.ComponentName)}. The user profile under which
+ * the provider operates is specified via the {@link
+ * AppWidgetManager#EXTRA_APPWIDGET_PROVIDER_PROFILE} extra. If a user profile is
+ * not provided, the current user is used. Finally, you can optionally provide a
+ * request code that is returned in {@link Activity#onActivityResult(int, int,
+ * android.content.Intent)}.
*
- * @return a appWidgetId
- * @hide
+ * @param activity The activity from which to start the configure one.
+ * @param intent Properly populated intent for launching the configuration activity.
+ * @param requestCode Optional request code retuned with the result.
+ *
+ * @throws android.content.ActivityNotFoundException If the activity is not found.
+ *
+ * @see AppWidgetProviderInfo#getProfile()
*/
- public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) {
- checkCallerIsSystem();
+ public final void startAppWidgetConfigureActivityForResult(Activity activity, Intent intent,
+ int requestCode) {
try {
- if (sService == null) {
- bindService();
+ IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
+ mContext.getPackageName(), intent);
+ if (intentSender != null) {
+ activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0);
+ } else {
+ throw new ActivityNotFoundException();
}
- return sService.allocateAppWidgetId(packageName, hostId, userId);
+ } catch (IntentSender.SendIntentException e) {
+ throw new ActivityNotFoundException();
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -235,20 +253,12 @@
if (sService == null) {
bindService();
}
- return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId());
+ return sService.getAppWidgetIdsForHost(mContext.getPackageName(), mHostId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
- private static void checkCallerIsSystem() {
- int uid = Process.myUid();
- if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
- return;
- }
- throw new SecurityException("Disallowed call for uid " + uid);
- }
-
private boolean isLocalBinder() {
return Process.myPid() == Binder.getCallingPid();
}
@@ -260,7 +270,7 @@
synchronized (mViews) {
mViews.remove(appWidgetId);
try {
- sService.deleteAppWidgetId(appWidgetId, mContext.getUserId());
+ sService.deleteAppWidgetId(mContext.getPackageName(), appWidgetId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -269,22 +279,6 @@
}
/**
- * Stop listening to changes for this AppWidget.
- * @hide
- */
- public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) {
- checkCallerIsSystem();
- try {
- if (sService == null) {
- bindService();
- }
- sService.deleteAppWidgetId(appWidgetId, userId);
- } catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-
- /**
* Remove all records about this host from the AppWidget manager.
* <ul>
* <li>Call this when initializing your database, as it might be because of a data wipe.</li>
@@ -294,7 +288,7 @@
*/
public void deleteHost() {
try {
- sService.deleteHost(mHostId, mContext.getUserId());
+ sService.deleteHost(mContext.getPackageName(), mHostId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -310,16 +304,8 @@
* </ul>
*/
public static void deleteAllHosts() {
- deleteAllHosts(UserHandle.myUserId());
- }
-
- /**
- * Private method containing a userId
- * @hide
- */
- public static void deleteAllHosts(int userId) {
try {
- sService.deleteAllHosts(userId);
+ sService.deleteAllHosts();
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -332,9 +318,7 @@
*/
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- final int userId = mContext.getUserId();
AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
- view.setUserId(userId);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
@@ -342,10 +326,7 @@
}
RemoteViews views;
try {
- views = sService.getAppWidgetViews(appWidgetId, userId);
- if (views != null) {
- views.setUser(new UserHandle(mContext.getUserId()));
- }
+ views = sService.getAppWidgetViews(mPackageName, appWidgetId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -397,7 +378,7 @@
// Does nothing
}
- void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {
+ void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
@@ -407,7 +388,7 @@
}
}
- void viewDataChanged(int appWidgetId, int viewId, int userId) {
+ void viewDataChanged(int appWidgetId, int viewId) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 700bba8..1ff476e 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -87,7 +87,6 @@
Bitmap mOld;
Paint mOldPaint = new Paint();
private OnClickHandler mOnClickHandler;
- private UserHandle mUser;
/**
* Create a host view. Uses default fade animations.
@@ -115,17 +114,11 @@
public AppWidgetHostView(Context context, int animationIn, int animationOut) {
super(context);
mContext = context;
- mUser = Process.myUserHandle();
// We want to segregate the view ids within AppWidgets to prevent
// problems when those ids collide with view ids in the AppWidgetHost.
setIsRootNamespace(true);
}
- /** @hide */
- public void setUserId(int userId) {
- mUser = new UserHandle(userId);
- }
-
/**
* Pass the given handler to RemoteViews when updating this widget. Unless this
* is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
@@ -380,7 +373,7 @@
} else {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
- mRemoteContext = getRemoteContext(remoteViews);
+ mRemoteContext = getRemoteContext();
int layoutId = remoteViews.getLayoutId();
// If our stale view has been prepared to match active, and the new
@@ -466,17 +459,14 @@
* Build a {@link Context} cloned into another package name, usually for the
* purposes of reading remote resources.
*/
- private Context getRemoteContext(RemoteViews views) {
- // Bail if missing package name
- final String packageName = views.getPackage();
- if (packageName == null) return mContext;
-
+ private Context getRemoteContext() {
try {
// Return if cloned successfully, otherwise default
- return mContext.createPackageContextAsUser(packageName, Context.CONTEXT_RESTRICTED,
- mUser);
+ return mContext.createApplicationContext(
+ mInfo.providerInfo.applicationInfo,
+ Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
- Log.e(TAG, "Package name " + packageName + " not found");
+ Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found");
return mContext;
}
}
@@ -548,8 +538,7 @@
try {
if (mInfo != null) {
- Context theirContext = mContext.createPackageContextAsUser(
- mInfo.provider.getPackageName(), Context.CONTEXT_RESTRICTED, mUser);
+ Context theirContext = getRemoteContext();
mRemoteContext = theirContext;
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -572,8 +561,6 @@
} else {
Log.w(TAG, "can't inflate defaultView because mInfo is missing");
}
- } catch (PackageManager.NameNotFoundException e) {
- exception = e;
} catch (RuntimeException e) {
exception = e;
}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index e5bf7d0..e5d1d95 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -21,8 +21,8 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -30,9 +30,8 @@
import com.android.internal.appwidget.IAppWidgetService;
-import java.lang.ref.WeakReference;
+import java.util.Collections;
import java.util.List;
-import java.util.WeakHashMap;
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -45,7 +44,6 @@
* </div>
*/
public class AppWidgetManager {
- static final String TAG = "AppWidgetManager";
/**
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
@@ -72,9 +70,9 @@
* <p>
* When you receive the result from the AppWidget pick activity, if the resultCode is
* {@link android.app.Activity#RESULT_OK}, an AppWidget has been selected. You should then
- * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its configuration
- * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete
- * the appWidgetId.
+ * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its
+ * configuration activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you
+ * should delete the appWidgetId.
*
* @see #ACTION_APPWIDGET_CONFIGURE
*/
@@ -103,6 +101,12 @@
* <td>The BroadcastReceiver that will be the AppWidget provider for this AppWidget.
* </td>
* </tr>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_PROVIDER_PROFILE}</td>
+ * <td>An optional handle to a user profile under which runs the provider
+ * for this AppWidget.
+ * </td>
+ * </tr>
* </table>
*
* <p>
@@ -119,8 +123,7 @@
* {@link android.app.Activity#RESULT_OK}, the AppWidget has been bound. You should then
* check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its
* configuration activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you
- * should delete
- * the appWidgetId.
+ * should delete the appWidgetId.
*
* @see #ACTION_APPWIDGET_CONFIGURE
*
@@ -130,7 +133,8 @@
/**
* Sent when it is time to configure your AppWidget while it is being added to a host.
* This action is not sent as a broadcast to the AppWidget provider, but as a startActivity
- * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}.
+ * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo
+ * meta-data}.
*
* <p>
* The intent will contain the following extras:
@@ -145,7 +149,8 @@
* {@link android.app.Activity#setResult Activity.setResult()}, the AppWidget will be added,
* and you will receive an {@link #ACTION_APPWIDGET_UPDATE} broadcast for this AppWidget.
* If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add
- * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} broadcast.
+ * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED}
+ * broadcast.
*/
public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE";
@@ -188,7 +193,9 @@
/**
* An intent extra which points to a bundle of extra information for a particular widget id.
- * In particular this bundle can contain EXTRA_APPWIDGET_WIDTH and EXTRA_APPWIDGET_HEIGHT.
+ * In particular this bundle can contain {@link #OPTION_APPWIDGET_MIN_WIDTH},
+ * {@link #OPTION_APPWIDGET_MIN_HEIGHT}, {@link #OPTION_APPWIDGET_MAX_WIDTH},
+ * {@link #OPTION_APPWIDGET_MAX_HEIGHT}.
*/
public static final String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
@@ -203,11 +210,19 @@
/**
* An intent extra that contains the component name of a AppWidget provider.
* <p>
- * The value will be an ComponentName.
+ * The value will be an {@link android.content.ComponentName}.
*/
public static final String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
/**
+ * An intent extra that contains the user handle of the profile under
+ * which an AppWidget provider is registered.
+ * <p>
+ * The value will be a {@link android.os.UserHandle}.
+ */
+ public static final String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
+
+ /**
* An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
* {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are
* installed. (This is how the launcher shows the search widget).
@@ -295,12 +310,12 @@
public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
/**
- * Sent when an instance of an AppWidget is removed from the last host.
+ * Sent when the last AppWidget of this provider is removed from the last host.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
- * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
+ * @see AppWidgetProvider#onEnabled AppWidgetProvider.onDisabled(Context context)
*/
public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
@@ -403,46 +418,36 @@
*/
public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
- static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache =
- new WeakHashMap<Context, WeakReference<AppWidgetManager>>();
- static IAppWidgetService sService;
+ private final String mPackageName;
- Context mContext;
+ private final IAppWidgetService mService;
- private DisplayMetrics mDisplayMetrics;
+ private final DisplayMetrics mDisplayMetrics;
/**
* Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
* Context} object.
*/
public static AppWidgetManager getInstance(Context context) {
- synchronized (sManagerCache) {
- if (sService == null) {
- IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
- sService = IAppWidgetService.Stub.asInterface(b);
- }
-
- WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
- AppWidgetManager result = null;
- if (ref != null) {
- result = ref.get();
- }
- if (result == null) {
- result = new AppWidgetManager(context);
- sManagerCache.put(context, new WeakReference<AppWidgetManager>(result));
- }
- return result;
- }
+ return (AppWidgetManager) context.getSystemService(Context.APPWIDGET_SERVICE);
}
- private AppWidgetManager(Context context) {
- mContext = context;
+ /**
+ * Creates a new instance.
+ *
+ * @param context The current context in which to operate.
+ * @param service The backing system service.
+ * @hide
+ */
+ public AppWidgetManager(Context context, IAppWidgetService service) {
+ mPackageName = context.getPackageName();
+ mService = service;
mDisplayMetrics = context.getResources().getDisplayMetrics();
}
/**
* Set the RemoteViews to use for the specified appWidgetIds.
- *
+ * <p>
* Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
* contain a complete representation of the widget. For performing partial widget updates, see
* {@link #partiallyUpdateAppWidget(int[], RemoteViews)}.
@@ -456,12 +461,15 @@
* The total Bitmap memory used by the RemoteViews object cannot exceed that required to
* fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes.
*
- * @param appWidgetIds The AppWidget instances for which to set the RemoteViews.
- * @param views The RemoteViews object to show.
+ * @param appWidgetIds The AppWidget instances for which to set the RemoteViews.
+ * @param views The RemoteViews object to show.
*/
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
+ mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -470,18 +478,21 @@
/**
* Update the extras for a given widget instance.
- *
+ * <p>
* The extras can be used to embed additional information about this widget to be accessed
* by the associated widget's AppWidgetProvider.
*
* @see #getAppWidgetOptions(int)
*
- * @param appWidgetId The AppWidget instances for which to set the RemoteViews.
- * @param options The options to associate with this widget
+ * @param appWidgetId The AppWidget instances for which to set the RemoteViews.
+ * @param options The options to associate with this widget
*/
public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.updateAppWidgetOptions(appWidgetId, options, mContext.getUserId());
+ mService.updateAppWidgetOptions(mPackageName, appWidgetId, options);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -490,18 +501,21 @@
/**
* Get the extras associated with a given widget instance.
- *
+ * <p>
* The extras can be used to embed additional information about this widget to be accessed
* by the associated widget's AppWidgetProvider.
*
* @see #updateAppWidgetOptions(int, Bundle)
*
- * @param appWidgetId The AppWidget instances for which to set the RemoteViews.
- * @return The options associated with the given widget instance.
+ * @param appWidgetId The AppWidget instances for which to set the RemoteViews.
+ * @return The options associated with the given widget instance.
*/
public Bundle getAppWidgetOptions(int appWidgetId) {
+ if (mService == null) {
+ return Bundle.EMPTY;
+ }
try {
- return sService.getAppWidgetOptions(appWidgetId, mContext.getUserId());
+ return mService.getAppWidgetOptions(mPackageName, appWidgetId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -510,7 +524,7 @@
/**
* Set the RemoteViews to use for the specified appWidgetId.
- *
+ * <p>
* Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
* contain a complete representation of the widget. For performing partial widget updates, see
* {@link #partiallyUpdateAppWidget(int, RemoteViews)}.
@@ -528,12 +542,15 @@
* @param views The RemoteViews object to show.
*/
public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ if (mService == null) {
+ return;
+ }
updateAppWidget(new int[] { appWidgetId }, views);
}
/**
* Perform an incremental update or command on the widget(s) specified by appWidgetIds.
- *
+ * <p>
* This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
* RemoteViews object which is passed is understood to be an incomplete representation of the
* widget, and hence does not replace the cached representation of the widget. As of API
@@ -556,8 +573,11 @@
* @param views The RemoteViews object containing the incremental update / command.
*/
public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.partiallyUpdateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
+ mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -565,7 +585,7 @@
/**
* Perform an incremental update or command on the widget specified by appWidgetId.
- *
+ * <p>
* This update differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews
* object which is passed is understood to be an incomplete representation of the widget, and
* hence is not cached by the AppWidgetService. Note that because these updates are not cached,
@@ -588,6 +608,9 @@
* @param views The RemoteViews object containing the incremental update / command.
*/
public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) {
+ if (mService == null) {
+ return;
+ }
partiallyUpdateAppWidget(new int[] { appWidgetId }, views);
}
@@ -605,8 +628,11 @@
* @param views The RemoteViews object to show.
*/
public void updateAppWidget(ComponentName provider, RemoteViews views) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.updateAppWidgetProvider(provider, views, mContext.getUserId());
+ mService.updateAppWidgetProvider(provider, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -621,8 +647,11 @@
* @param viewId The collection view id.
*/
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId, mContext.getUserId());
+ mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -637,37 +666,106 @@
* @param viewId The collection view id.
*/
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
+ if (mService == null) {
+ return;
+ }
notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, viewId);
}
/**
+ * Gets the AppWidget providers for the given user profiles. User profiles can only
+ * be the current user or a profile of the current user. For example, the current
+ * user may have a corporate profile. In this case the parent user profile has a
+ * child profile, the corporate one.
+ *
+ * @param profiles The profiles for which to get providers. Passing null is equivaled
+ * to passing only the current user handle.
+ * @return The intalled providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ */
+ public List<AppWidgetProviderInfo> getInstalledProvidersForProfiles(UserHandle[] profiles) {
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+ return getInstalledProvidersForProfiles(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ profiles);
+ }
+
+ /**
* Return a list of the AppWidget providers that are currently installed.
*/
public List<AppWidgetProviderInfo> getInstalledProviders() {
- return getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+ return getInstalledProvidersForProfiles(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ null);
}
/**
- * Return a list of the AppWidget providers that are currently installed.
+ * Gets the AppWidget providers for the current user.
*
* @param categoryFilter Will only return providers which register as any of the specified
* specified categories. See {@link AppWidgetProviderInfo#widgetCategory}.
+ * @return The intalled providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ *
* @hide
*/
public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+ return getInstalledProvidersForProfiles(categoryFilter, null);
+ }
+
+ /**
+ * Gets the AppWidget providers for the given user profiles. User profiles can only
+ * be the current user or a profile of the current user. For example, the current
+ * user may have a corporate profile. In this case the parent user profile has a
+ * child profile, the corporate one.
+ *
+ * @param categoryFilter Will only return providers which register as any of the specified
+ * specified categories. See {@link AppWidgetProviderInfo#widgetCategory}.
+ * @param profiles Child profiles of the current user which to be queried. The user
+ * is itself also a profile. If null, the providers only for the current user
+ * are returned.
+ * @return The intalled providers.
+ *
+ * @see android.os.Process#myUserHandle()
+ * @see android.os.UserManager#getUserProfiles()
+ *
+ * @hide
+ */
+ public List<AppWidgetProviderInfo> getInstalledProvidersForProfiles(int categoryFilter,
+ UserHandle[] profiles) {
+ if (mService == null) {
+ return Collections.emptyList();
+ }
+
+ int[] profileIds = null;
+
+ if (profiles != null) {
+ final int profileCount = profiles.length;
+ profileIds = new int[profileCount];
+ for (int i = 0; i < profileCount; i++) {
+ profileIds[i] = profiles[i].getIdentifier();
+ }
+ }
+
try {
- List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter,
- mContext.getUserId());
+ List<AppWidgetProviderInfo> providers = mService.getInstalledProviders(categoryFilter,
+ profileIds);
+ if (providers == null) {
+ return Collections.emptyList();
+ }
for (AppWidgetProviderInfo info : providers) {
// Converting complex to dp.
- info.minWidth =
- TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
- info.minHeight =
- TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
- info.minResizeWidth =
- TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics);
- info.minResizeHeight =
- TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics);
+ convertSizesToPixels(info);
}
return providers;
}
@@ -683,19 +781,14 @@
* you don't have access to that appWidgetId, null is returned.
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
+ if (mService == null) {
+ return null;
+ }
try {
- AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId,
- mContext.getUserId());
+ AppWidgetProviderInfo info = mService.getAppWidgetInfo(mPackageName, appWidgetId);
if (info != null) {
// Converting complex to dp.
- info.minWidth =
- TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
- info.minHeight =
- TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
- info.minResizeWidth =
- TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics);
- info.minResizeHeight =
- TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics);
+ convertSizesToPixels(info);
}
return info;
}
@@ -717,12 +810,10 @@
* @hide
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
- try {
- sService.bindAppWidgetId(appWidgetId, provider, null, mContext.getUserId());
+ if (mService == null) {
+ return;
}
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
+ bindAppWidgetId(appWidgetId, provider, null);
}
/**
@@ -741,12 +832,10 @@
* @hide
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
- try {
- sService.bindAppWidgetId(appWidgetId, provider, options, mContext.getUserId());
+ if (mService == null) {
+ return;
}
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
+ bindAppWidgetIdIfAllowed(appWidgetId, Process.myUserHandle(), provider, options);
}
/**
@@ -757,22 +846,16 @@
* method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
* bind
*
- * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param appWidgetId The AppWidget id under which to bind the provider.
* @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget
* provider for this AppWidget.
* @return true if this component has permission to bind the AppWidget
*/
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider) {
- if (mContext == null) {
+ if (mService == null) {
return false;
}
- try {
- return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider, null, mContext.getUserId());
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
+ return bindAppWidgetIdIfAllowed(appWidgetId, UserHandle.myUserId(), provider, null);
}
/**
@@ -783,7 +866,7 @@
* method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
* bind
*
- * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param appWidgetId The AppWidget id under which to bind the provider.
* @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget
* provider for this AppWidget.
* @param options Bundle containing options for the AppWidget. See also
@@ -793,12 +876,52 @@
*/
public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider,
Bundle options) {
- if (mContext == null) {
+ if (mService == null) {
+ return false;
+ }
+ return bindAppWidgetIdIfAllowed(appWidgetId, UserHandle.myUserId(), provider, options);
+ }
+
+ /**
+ * Set the provider for a given appWidgetId if the caller has a permission.
+ * <p>
+ * <strong>Note:</strong> You need the {@link android.Manifest.permission#BIND_APPWIDGET}
+ * permission or the user must have enabled binding widgets always for your component.
+ * Should be used by apps that host widgets. If this method returns false, call {@link
+ * #ACTION_APPWIDGET_BIND} to request permission to bind.
+ * </p>
+ *
+ * @param appWidgetId The AppWidget id under which to bind the provider.
+ * @param user The user id in which the provider resides.
+ * @param provider The component name of the provider.
+ * @param options An optional Bundle containing options for the AppWidget.
+ *
+ * @return true if this component has permission to bind the AppWidget
+ */
+ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, UserHandle user,
+ ComponentName provider, Bundle options) {
+ if (mService == null) {
+ return false;
+ }
+ return bindAppWidgetIdIfAllowed(appWidgetId, user.getIdentifier(), provider, options);
+ }
+
+ /**
+ * Query if a given package was granted permission by the user to bind app widgets
+ *
+ * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission
+ *
+ * @param packageName The package for which the permission is being queried
+ * @param userId The user id of the user under which the package runs.
+ * @return true if the package was granted permission by the user to bind app widgets
+ * @hide
+ */
+ public boolean hasBindAppWidgetPermission(String packageName, int userId) {
+ if (mService == null) {
return false;
}
try {
- return sService.bindAppWidgetIdIfAllowed(mContext.getPackageName(), appWidgetId,
- provider, options, mContext.getUserId());
+ return mService.hasBindAppWidgetPermission(packageName, userId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -815,8 +938,11 @@
* @hide
*/
public boolean hasBindAppWidgetPermission(String packageName) {
+ if (mService == null) {
+ return false;
+ }
try {
- return sService.hasBindAppWidgetPermission(packageName, mContext.getUserId());
+ return mService.hasBindAppWidgetPermission(packageName, UserHandle.myUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -828,13 +954,35 @@
*
* <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission
*
- * @param provider The package whose permission is being changed
- * @param permission Whether to give the package permission to bind widgets
+ * @param packageName The package whose permission is being changed
+ * @param permission Whether to give the package permission to bind widgets
+ *
* @hide
*/
public void setBindAppWidgetPermission(String packageName, boolean permission) {
+ if (mService == null) {
+ return;
+ }
+ setBindAppWidgetPermission(packageName, UserHandle.myUserId(), permission);
+ }
+
+ /**
+ * Changes any user-granted permission for the given package to bind app widgets
+ *
+ * <p class="note">You need the MODIFY_APPWIDGET_BIND_PERMISSIONS permission
+ *
+ * @param packageName The package whose permission is being changed
+ * @param userId The user under which the package is running.
+ * @param permission Whether to give the package permission to bind widgets
+ *
+ * @hide
+ */
+ public void setBindAppWidgetPermission(String packageName, int userId, boolean permission) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.setBindAppWidgetPermission(packageName, permission, mContext.getUserId());
+ mService.setBindAppWidgetPermission(packageName, userId, permission);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -847,18 +995,20 @@
* The appWidgetId specified must already be bound to the calling AppWidgetHost via
* {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
*
+ * @param packageName The package from which the binding is requested.
* @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
* @param connection The callback interface to be notified when a connection is made or lost.
- * @param userHandle The user to bind to.
* @hide
*/
- public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
- UserHandle userHandle) {
+ public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
+ IBinder connection) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.bindRemoteViewsService(appWidgetId, intent, connection,
- userHandle.getIdentifier());
+ mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -871,15 +1021,18 @@
* The appWidgetId specified muse already be bound to the calling AppWidgetHost via
* {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
*
+ * @param packageName The package from which the binding is requested.
* @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
- * @param userHandle The user to unbind from.
* @hide
*/
- public void unbindRemoteViewsService(int appWidgetId, Intent intent, UserHandle userHandle) {
+ public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
+ if (mService == null) {
+ return;
+ }
try {
- sService.unbindRemoteViewsService(appWidgetId, intent, userHandle.getIdentifier());
+ mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -894,12 +1047,40 @@
* AppWidget provider to find appWidgetIds for.
*/
public int[] getAppWidgetIds(ComponentName provider) {
+ if (mService == null) {
+ return new int[0];
+ }
try {
- return sService.getAppWidgetIds(provider, mContext.getUserId());
+ return mService.getAppWidgetIds(provider);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
-}
+ private boolean bindAppWidgetIdIfAllowed(int appWidgetId, int profileId,
+ ComponentName provider, Bundle options) {
+ if (mService == null) {
+ return false;
+ }
+ try {
+ return mService.bindAppWidgetId(mPackageName, appWidgetId,
+ profileId, provider, options);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ private void convertSizesToPixels(AppWidgetProviderInfo info) {
+ // Converting complex to dp.
+ info.minWidth = TypedValue.complexToDimensionPixelSize(info.minWidth,
+ mDisplayMetrics);
+ info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight,
+ mDisplayMetrics);
+ info.minResizeWidth = TypedValue.complexToDimensionPixelSize(info.minResizeWidth,
+ mDisplayMetrics);
+ info.minResizeHeight = TypedValue.complexToDimensionPixelSize(info.minResizeHeight,
+ mDisplayMetrics);
+ }
+}
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 8b9c7f0..e4dad5a 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -16,9 +16,17 @@
package android.appwidget;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.content.ComponentName;
+import android.os.UserHandle;
+import android.os.UserManager;
/**
* Describes the meta data for an installed AppWidget provider. The fields in this class
@@ -145,21 +153,23 @@
public ComponentName configure;
/**
- * The label to display to the user in the AppWidget picker. If not supplied in the
- * xml, the application label will be used.
+ * The label to display to the user in the AppWidget picker.
*
- * <p>This field corresponds to the <code>android:label</code> attribute in
- * the <code><receiver></code> element in the AndroidManifest.xml file.
+ * @deprecated Use {@link #loadLabel(android.content.pm.PackageManager)}.
*/
+ @Deprecated
public String label;
/**
- * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the
+ * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the
* xml, the application icon will be used.
*
* <p>This field corresponds to the <code>android:icon</code> attribute in
* the <code><receiver></code> element in the AndroidManifest.xml file.
+ *
+ * @deprecated Use {@link #loadIcon(android.content.Context, int)}.
*/
+ @Deprecated
public int icon;
/**
@@ -176,7 +186,10 @@
*
* <p>This field corresponds to the <code>android:previewImage</code> attribute in
* the <code><receiver></code> element in the AndroidManifest.xml file.
+ *
+ * @deprecated User {@link #loadPreviewImage(android.content.Context, int)}.
*/
+ @Deprecated
public int previewImage;
/**
@@ -200,12 +213,17 @@
*/
public int widgetCategory;
+ /** @hide */
+ public ActivityInfo providerInfo;
+
public AppWidgetProviderInfo() {
+
}
/**
* Unflatten the AppWidgetProviderInfo from a parcel.
*/
+ @SuppressWarnings("deprecation")
public AppWidgetProviderInfo(Parcel in) {
if (0 != in.readInt()) {
this.provider = new ComponentName(in);
@@ -226,8 +244,86 @@
this.autoAdvanceViewId = in.readInt();
this.resizeMode = in.readInt();
this.widgetCategory = in.readInt();
+ this.providerInfo = in.readParcelable(null);
}
+ /**
+ * Loads the localized label to display to the user in the AppWidget picker.
+ *
+ * @param packageManager Package manager instance for loading resources.
+ * @return The label for the current locale.
+ */
+ public final String loadLabel(PackageManager packageManager) {
+ CharSequence label = providerInfo.loadLabel(packageManager);
+ if (label != null) {
+ return label.toString().trim();
+ }
+ return null;
+ }
+
+ /**
+ * Loads the icon to display for this AppWidget in the AppWidget picker. If not
+ * supplied in the xml, the application icon will be used. A client can optionally
+ * provide a desired density such as {@link android.util.DisplayMetrics#DENSITY_LOW}
+ * {@link android.util.DisplayMetrics#DENSITY_MEDIUM}, etc. If no density is
+ * provided, the density of the current display will be used.
+ * <p>
+ * The loaded icon corresponds to the <code>android:icon</code> attribute in
+ * the <code><receiver></code> element in the AndroidManifest.xml file.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> If you care about widgets from different profiles, you
+ * should use this method to load the icon as the system will apply the correct
+ * badging when applicable, so the user knows which profile a widget comes from.
+ * </p>
+ *
+ * @param context Context for accessing resources.
+ * @param density The optional desired density as per
+ * {@link android.util.DisplayMetrics#densityDpi}.
+ * @return The potentially badged provider icon.
+ */
+ public final Drawable loadIcon(Context context, int density) {
+ return loadDrawable(context, density, providerInfo.getIconResource());
+ }
+
+ /**
+ * Loads a preview of what the AppWidget will look like after it's configured.
+ * If not supplied, the AppWidget's icon will be used. A client can optionally
+ * provide a desired deinsity such as {@link android.util.DisplayMetrics#DENSITY_LOW}
+ * {@link android.util.DisplayMetrics#DENSITY_MEDIUM}, etc. If no density is
+ * provided, the density of the current display will be used.
+ * <p>
+ * The loaded image corresponds to the <code>android:previewImage</code> attribute
+ * in the <code><receiver></code> element in the AndroidManifest.xml file.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> If you care about widgets from different profiles, you
+ * should use this method to load the preview image as the system will apply the
+ * correct badging when applicable, so the user knows which profile a previewed
+ * widget comes from.
+ * </p>
+ *
+ * @param context Context for accessing resources.
+ * @param density The optional desired density as per
+ * {@link android.util.DisplayMetrics#densityDpi}.
+ * @return The potentially badged widget preview image.
+ */
+ @SuppressWarnings("deprecation")
+ public final Drawable loadPreviewImage(Context context, int density) {
+ return loadDrawable(context, density, previewImage);
+ }
+
+ /**
+ * Gets the user profile in which the provider resides.
+ *
+ * @return The hosting user profile.
+ */
+ public final UserHandle getProfile() {
+ return new UserHandle(UserHandle.getUserId(providerInfo.applicationInfo.uid));
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
public void writeToParcel(android.os.Parcel out, int flags) {
if (this.provider != null) {
out.writeInt(1);
@@ -254,9 +350,11 @@
out.writeInt(this.autoAdvanceViewId);
out.writeInt(this.resizeMode);
out.writeInt(this.widgetCategory);
+ out.writeParcelable(this.providerInfo, flags);
}
@Override
+ @SuppressWarnings("deprecation")
public AppWidgetProviderInfo clone() {
AppWidgetProviderInfo that = new AppWidgetProviderInfo();
that.provider = this.provider == null ? null : this.provider.clone();
@@ -273,7 +371,8 @@
that.previewImage = this.previewImage;
that.autoAdvanceViewId = this.autoAdvanceViewId;
that.resizeMode = this.resizeMode;
- that.widgetCategory = this.widgetCategory;
+ that.widgetCategory = this.widgetCategory;
+ that.providerInfo = this.providerInfo;
return that;
}
@@ -281,6 +380,33 @@
return 0;
}
+ private Drawable loadDrawable(Context context, int density, int resourceId) {
+ try {
+ Resources resources = context.getPackageManager().getResourcesForApplication(
+ providerInfo.applicationInfo);
+
+ final Drawable drawable;
+ if (resourceId > 0) {
+ if (density <= 0) {
+ density = context.getResources().getDisplayMetrics().densityDpi;
+ }
+ drawable = resources.getDrawableForDensity(resourceId, density);
+ } else {
+ drawable = providerInfo.loadIcon(context.getPackageManager());
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ UserManager userManager = (UserManager) context.getSystemService(
+ Context.USER_SERVICE);
+ return userManager.getBadgedDrawableForUser(drawable, getProfile());
+ }
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ /* ignore */
+ }
+
+ return null;
+ }
+
/**
* Parcelable.Creator that instantiates AppWidgetProviderInfo objects
*/
@@ -299,6 +425,6 @@
};
public String toString() {
- return "AppWidgetProviderInfo(provider=" + this.provider + ")";
+ return "AppWidgetProviderInfo(" + getProfile() + '/' + provider + ')';
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f3a7b1c1..7417208 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2076,7 +2076,7 @@
CLIPBOARD_SERVICE,
INPUT_METHOD_SERVICE,
TEXT_SERVICES_MANAGER_SERVICE,
- //@hide: APPWIDGET_SERVICE,
+ APPWIDGET_SERVICE,
//@hide: BACKUP_SERVICE,
DROPBOX_SERVICE,
DEVICE_POLICY_SERVICE,
@@ -2596,7 +2596,6 @@
* Use with {@link #getSystemService} to retrieve a
* {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
*
- * @hide
* @see #getSystemService
*/
public static final String APPWIDGET_SERVICE = "appwidget";
@@ -3306,6 +3305,14 @@
throws PackageManager.NameNotFoundException;
/**
+ * Creates a context given an {@link android.content.pm.ApplicationInfo}.
+ *
+ * @hide
+ */
+ public abstract Context createApplicationContext(ApplicationInfo application,
+ int flags) throws PackageManager.NameNotFoundException;
+
+ /**
* Get the userId associated with this context
* @return user id
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 4e1c4a7..ad7c350 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -666,6 +666,12 @@
}
/** @hide */
+ public Context createApplicationContext(ApplicationInfo application,
+ int flags) throws PackageManager.NameNotFoundException {
+ return mBase.createApplicationContext(application, flags);
+ }
+
+ /** @hide */
@Override
public int getUserId() {
return mBase.getUserId();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7196372..e27ad7d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -233,7 +233,7 @@
in ComponentName[] set, in ComponentName activity, int userId);
void replacePreferredActivity(in IntentFilter filter, int match,
- in ComponentName[] set, in ComponentName activity);
+ in ComponentName[] set, in ComponentName activity, int userId);
void clearPackagePreferredActivities(String packageName);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c5dcd8e..e482bb0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3523,6 +3523,15 @@
ComponentName[] set, ComponentName activity);
/**
+ * @hide
+ */
+ @Deprecated
+ public void replacePreferredActivityAsUser(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 1b9a0c5..8b44f3b 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -416,6 +416,15 @@
}
}
+ public void resizeVirtualDisplay(IVirtualDisplayCallbacks token,
+ int width, int height, int densityDpi) {
+ try {
+ mDm.resizeVirtualDisplay(token, width, height, densityDpi);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to resize virtual display.", ex);
+ }
+ }
+
public void releaseVirtualDisplay(IVirtualDisplayCallbacks token) {
try {
mDm.releaseVirtualDisplay(token);
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 44ffbc4..cfaa5a0 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -65,6 +65,10 @@
in IMediaProjection projectionToken, String packageName, String name,
int width, int height, int densityDpi, in Surface surface, int flags);
+ // No permissions required, but must be same Uid as the creator.
+ void resizeVirtualDisplay(in IVirtualDisplayCallbacks token,
+ int width, int height, int densityDpi);
+
// No permissions required but must be same Uid as the creator.
void setVirtualDisplaySurface(in IVirtualDisplayCallbacks token, in Surface surface);
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index df6116b..1dd6978 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -80,6 +80,18 @@
}
/**
+ * Asks the virtual display to resize.
+ *<p>
+ * This is really just a convenience to allow applications using
+ * virtual displays to adapt to changing conditions without having
+ * to tear down and recreate the display.
+ * </p>
+ */
+ public void resize(int width, int height, int densityDpi) {
+ mGlobal.resizeVirtualDisplay(mToken, width, height, densityDpi);
+ }
+
+ /**
* Releases the virtual display and destroys its underlying surface.
* <p>
* All remaining windows on the virtual display will be forcibly removed
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index adee740..7a49eb5 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -16,6 +16,7 @@
package android.hardware.soundtrigger;
+import android.media.AudioFormat;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
@@ -86,11 +87,15 @@
/** Rated power consumption when detection is active with TDB silence/sound/speech ratio */
public final int powerConsumptionMw;
+ /** Returns the trigger (key phrase) capture in the binary data of the
+ * recognition callback event */
+ public final boolean returnsTriggerInEvent;
+
ModuleProperties(int id, String implementor, String description,
String uuid, int version, int maxSoundModels, int maxKeyphrases,
int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
int maxBufferMs, boolean supportsConcurrentCapture,
- int powerConsumptionMw) {
+ int powerConsumptionMw, boolean returnsTriggerInEvent) {
this.id = id;
this.implementor = implementor;
this.description = description;
@@ -104,6 +109,7 @@
this.maxBufferMs = maxBufferMs;
this.supportsConcurrentCapture = supportsConcurrentCapture;
this.powerConsumptionMw = powerConsumptionMw;
+ this.returnsTriggerInEvent = returnsTriggerInEvent;
}
public static final Parcelable.Creator<ModuleProperties> CREATOR
@@ -131,10 +137,11 @@
int maxBufferMs = in.readInt();
boolean supportsConcurrentCapture = in.readByte() == 1;
int powerConsumptionMw = in.readInt();
+ boolean returnsTriggerInEvent = in.readByte() == 1;
return new ModuleProperties(id, implementor, description, uuid, version,
maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
- powerConsumptionMw);
+ powerConsumptionMw, returnsTriggerInEvent);
}
@Override
@@ -152,6 +159,7 @@
dest.writeInt(maxBufferMs);
dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
dest.writeInt(powerConsumptionMw);
+ dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
}
@Override
@@ -167,7 +175,8 @@
+ maxUsers + ", recognitionModes=" + recognitionModes
+ ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
+ maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
- + ", powerConsumptionMw=" + powerConsumptionMw + "]";
+ + ", powerConsumptionMw=" + powerConsumptionMw
+ + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
}
}
@@ -190,11 +199,15 @@
/** Sound model type (e.g. TYPE_KEYPHRASE); */
public final int type;
+ /** Unique sound model vendor identifier */
+ public final UUID vendorUuid;
+
/** Opaque data. For use by vendor implementation and enrollment application */
public final byte[] data;
- public SoundModel(UUID uuid, int type, byte[] data) {
+ public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
this.uuid = uuid;
+ this.vendorUuid = vendorUuid;
this.type = type;
this.data = data;
}
@@ -329,8 +342,9 @@
/** Key phrases in this sound model */
public final Keyphrase[] keyphrases; // keyword phrases in model
- public KeyphraseSoundModel(UUID id, byte[] data, Keyphrase[] keyphrases) {
- super(id, TYPE_KEYPHRASE, data);
+ public KeyphraseSoundModel(
+ UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+ super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
this.keyphrases = keyphrases;
}
@@ -347,9 +361,14 @@
private static KeyphraseSoundModel fromParcel(Parcel in) {
UUID uuid = UUID.fromString(in.readString());
+ UUID vendorUuid = null;
+ int length = in.readInt();
+ if (length >= 0) {
+ vendorUuid = UUID.fromString(in.readString());
+ }
byte[] data = in.readBlob();
Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
- return new KeyphraseSoundModel(uuid, data, keyphrases);
+ return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
}
@Override
@@ -360,14 +379,21 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(uuid.toString());
+ if (vendorUuid == null) {
+ dest.writeInt(-1);
+ } else {
+ dest.writeInt(vendorUuid.toString().length());
+ dest.writeString(vendorUuid.toString());
+ }
dest.writeBlob(data);
dest.writeTypedArray(keyphrases, flags);
}
@Override
public String toString() {
- return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid="
- + uuid + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
+ + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
+ + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
}
}
@@ -411,18 +437,26 @@
public final int captureDelayMs;
/** Duration in ms of audio captured before the start of the trigger. 0 if none. */
public final int capturePreambleMs;
+ /** True if the trigger (key phrase capture is present in binary data */
+ public final boolean triggerInData;
+ /** Audio format of either the trigger in event data or to use for capture of the
+ * rest of the utterance */
+ public AudioFormat captureFormat;
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
public final byte[] data;
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
- int captureSession, int captureDelayMs, int capturePreambleMs, byte[] data) {
+ int captureSession, int captureDelayMs, int capturePreambleMs,
+ boolean triggerInData, AudioFormat captureFormat, byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
this.captureSession = captureSession;
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
+ this.triggerInData = triggerInData;
+ this.captureFormat = captureFormat;
this.data = data;
}
@@ -444,9 +478,21 @@
int captureSession = in.readInt();
int captureDelayMs = in.readInt();
int capturePreambleMs = in.readInt();
+ boolean triggerInData = in.readByte() == 1;
+ AudioFormat captureFormat = null;
+ if (triggerInData) {
+ int sampleRate = in.readInt();
+ int encoding = in.readInt();
+ int channelMask = in.readInt();
+ captureFormat = (new AudioFormat.Builder())
+ .setChannelMask(channelMask)
+ .setEncoding(encoding)
+ .setSampleRate(sampleRate)
+ .build();
+ }
byte[] data = in.readBlob();
return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
- captureDelayMs, capturePreambleMs, data);
+ captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data);
}
@Override
@@ -462,6 +508,14 @@
dest.writeInt(captureSession);
dest.writeInt(captureDelayMs);
dest.writeInt(capturePreambleMs);
+ if (triggerInData && (captureFormat != null)) {
+ dest.writeByte((byte)1);
+ dest.writeInt(captureFormat.getSampleRate());
+ dest.writeInt(captureFormat.getEncoding());
+ dest.writeInt(captureFormat.getChannelMask());
+ } else {
+ dest.writeByte((byte)0);
+ }
dest.writeBlob(data);
}
@@ -473,6 +527,12 @@
result = prime * result + captureDelayMs;
result = prime * result + capturePreambleMs;
result = prime * result + captureSession;
+ result = prime * result + (triggerInData ? 1231 : 1237);
+ if (captureFormat != null) {
+ result = prime * result + captureFormat.getSampleRate();
+ result = prime * result + captureFormat.getEncoding();
+ result = prime * result + captureFormat.getChannelMask();
+ }
result = prime * result + Arrays.hashCode(data);
result = prime * result + soundModelHandle;
result = prime * result + status;
@@ -502,6 +562,14 @@
return false;
if (status != other.status)
return false;
+ if (triggerInData != other.triggerInData)
+ return false;
+ if (captureFormat.getSampleRate() != other.captureFormat.getSampleRate())
+ return false;
+ if (captureFormat.getEncoding() != other.captureFormat.getEncoding())
+ return false;
+ if (captureFormat.getChannelMask() != other.captureFormat.getChannelMask())
+ return false;
return true;
}
@@ -511,6 +579,13 @@
+ ", captureAvailable=" + captureAvailable + ", captureSession="
+ captureSession + ", captureDelayMs=" + captureDelayMs
+ ", capturePreambleMs=" + capturePreambleMs
+ + ", triggerInData=" + triggerInData
+ + ((captureFormat == null) ? "" :
+ (", sampleRate=" + captureFormat.getSampleRate()))
+ + ((captureFormat == null) ? "" :
+ (", encoding=" + captureFormat.getEncoding()))
+ + ((captureFormat == null) ? "" :
+ (", channelMask=" + captureFormat.getChannelMask()))
+ ", data=" + (data == null ? 0 : data.length) + "]";
}
}
@@ -673,14 +748,19 @@
/** Recognition modes matched for this event */
public final int recognitionModes;
+ /** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
+ * is not performed */
+ public final int coarseConfidenceLevel;
+
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
public final ConfidenceLevel[] confidenceLevels;
- public KeyphraseRecognitionExtra(int id, int recognitionModes,
- ConfidenceLevel[] confidenceLevels) {
+ public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
+ ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
+ this.coarseConfidenceLevel = coarseConfidenceLevel;
this.confidenceLevels = confidenceLevels;
}
@@ -698,14 +778,17 @@
private static KeyphraseRecognitionExtra fromParcel(Parcel in) {
int id = in.readInt();
int recognitionModes = in.readInt();
+ int coarseConfidenceLevel = in.readInt();
ConfidenceLevel[] confidenceLevels = in.createTypedArray(ConfidenceLevel.CREATOR);
- return new KeyphraseRecognitionExtra(id, recognitionModes, confidenceLevels);
+ return new KeyphraseRecognitionExtra(id, recognitionModes, coarseConfidenceLevel,
+ confidenceLevels);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeInt(recognitionModes);
+ dest.writeInt(coarseConfidenceLevel);
dest.writeTypedArray(confidenceLevels, flags);
}
@@ -721,6 +804,7 @@
result = prime * result + Arrays.hashCode(confidenceLevels);
result = prime * result + id;
result = prime * result + recognitionModes;
+ result = prime * result + coarseConfidenceLevel;
return result;
}
@@ -739,12 +823,15 @@
return false;
if (recognitionModes != other.recognitionModes)
return false;
+ if (coarseConfidenceLevel != other.coarseConfidenceLevel)
+ return false;
return true;
}
@Override
public String toString() {
return "KeyphraseRecognitionExtra [id=" + id + ", recognitionModes=" + recognitionModes
+ + ", coarseConfidenceLevel=" + coarseConfidenceLevel
+ ", confidenceLevels=" + Arrays.toString(confidenceLevels) + "]";
}
}
@@ -756,15 +843,12 @@
/** Indicates if the key phrase is present in the buffered audio available for capture */
public final KeyphraseRecognitionExtra[] keyphraseExtras;
- /** Additional data available for each recognized key phrases in the model */
- public final boolean keyphraseInCapture;
-
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
- int captureSession, int captureDelayMs, int capturePreambleMs, byte[] data,
- boolean keyphraseInCapture, KeyphraseRecognitionExtra[] keyphraseExtras) {
+ int captureSession, int captureDelayMs, int capturePreambleMs,
+ boolean triggerInData, AudioFormat captureFormat, byte[] data,
+ KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
- capturePreambleMs, data);
- this.keyphraseInCapture = keyphraseInCapture;
+ capturePreambleMs, triggerInData, captureFormat, data);
this.keyphraseExtras = keyphraseExtras;
}
@@ -786,13 +870,24 @@
int captureSession = in.readInt();
int captureDelayMs = in.readInt();
int capturePreambleMs = in.readInt();
+ boolean triggerInData = in.readByte() == 1;
+ AudioFormat captureFormat = null;
+ if (triggerInData) {
+ int sampleRate = in.readInt();
+ int encoding = in.readInt();
+ int channelMask = in.readInt();
+ captureFormat = (new AudioFormat.Builder())
+ .setChannelMask(channelMask)
+ .setEncoding(encoding)
+ .setSampleRate(sampleRate)
+ .build();
+ }
byte[] data = in.readBlob();
- boolean keyphraseInCapture = in.readByte() == 1;
KeyphraseRecognitionExtra[] keyphraseExtras =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
- captureSession, captureDelayMs, capturePreambleMs, data, keyphraseInCapture,
- keyphraseExtras);
+ captureSession, captureDelayMs, capturePreambleMs, triggerInData,
+ captureFormat, data, keyphraseExtras);
}
@Override
@@ -803,8 +898,15 @@
dest.writeInt(captureSession);
dest.writeInt(captureDelayMs);
dest.writeInt(capturePreambleMs);
+ if (triggerInData && (captureFormat != null)) {
+ dest.writeByte((byte)1);
+ dest.writeInt(captureFormat.getSampleRate());
+ dest.writeInt(captureFormat.getEncoding());
+ dest.writeInt(captureFormat.getChannelMask());
+ } else {
+ dest.writeByte((byte)0);
+ }
dest.writeBlob(data);
- dest.writeByte((byte) (keyphraseInCapture ? 1 : 0));
dest.writeTypedArray(keyphraseExtras, flags);
}
@@ -818,7 +920,6 @@
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(keyphraseExtras);
- result = prime * result + (keyphraseInCapture ? 1231 : 1237);
return result;
}
@@ -833,23 +934,127 @@
KeyphraseRecognitionEvent other = (KeyphraseRecognitionEvent) obj;
if (!Arrays.equals(keyphraseExtras, other.keyphraseExtras))
return false;
- if (keyphraseInCapture != other.keyphraseInCapture)
- return false;
return true;
}
@Override
public String toString() {
return "KeyphraseRecognitionEvent [keyphraseExtras=" + Arrays.toString(keyphraseExtras)
- + ", keyphraseInCapture=" + keyphraseInCapture + ", status=" + status
+ + ", status=" + status
+ ", soundModelHandle=" + soundModelHandle + ", captureAvailable="
+ captureAvailable + ", captureSession=" + captureSession + ", captureDelayMs="
+ captureDelayMs + ", capturePreambleMs=" + capturePreambleMs
+ + ", triggerInData=" + triggerInData
+ + ((captureFormat == null) ? "" :
+ (", sampleRate=" + captureFormat.getSampleRate()))
+ + ((captureFormat == null) ? "" :
+ (", encoding=" + captureFormat.getEncoding()))
+ + ((captureFormat == null) ? "" :
+ (", channelMask=" + captureFormat.getChannelMask()))
+ ", data=" + (data == null ? 0 : data.length) + "]";
}
}
/**
+ * Status codes for {@link SoundModelEvent}
+ */
+ /** Sound Model was updated */
+ public static final int SOUNDMODEL_STATUS_UPDATED = 0;
+
+ /**
+ * A SoundModelEvent is provided by the
+ * {@link StatusListener#onSoundModelUpdate(SoundModelEvent)}
+ * callback when a sound model has been updated by the implementation
+ */
+ public static class SoundModelEvent implements Parcelable {
+ /** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */
+ public final int status;
+ /** The updated sound model handle */
+ public final int soundModelHandle;
+ /** New sound model data */
+ public final byte[] data;
+
+ SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+ this.status = status;
+ this.soundModelHandle = soundModelHandle;
+ this.data = data;
+ }
+
+ public static final Parcelable.Creator<SoundModelEvent> CREATOR
+ = new Parcelable.Creator<SoundModelEvent>() {
+ public SoundModelEvent createFromParcel(Parcel in) {
+ return SoundModelEvent.fromParcel(in);
+ }
+
+ public SoundModelEvent[] newArray(int size) {
+ return new SoundModelEvent[size];
+ }
+ };
+
+ private static SoundModelEvent fromParcel(Parcel in) {
+ int status = in.readInt();
+ int soundModelHandle = in.readInt();
+ byte[] data = in.readBlob();
+ return new SoundModelEvent(status, soundModelHandle, data);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(status);
+ dest.writeInt(soundModelHandle);
+ dest.writeBlob(data);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(data);
+ result = prime * result + soundModelHandle;
+ result = prime * result + status;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SoundModelEvent other = (SoundModelEvent) obj;
+ if (!Arrays.equals(data, other.data))
+ return false;
+ if (soundModelHandle != other.soundModelHandle)
+ return false;
+ if (status != other.status)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "SoundModelEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
+ + ", data=" + (data == null ? 0 : data.length) + "]";
+ }
+ }
+
+ /**
+ * Native service state. {@link StatusListener#onServiceStateChange(int)}
+ */
+ // Keep in sync with system/core/include/system/sound_trigger.h
+ /** Sound trigger service is enabled */
+ public static final int SERVICE_STATE_ENABLED = 0;
+ /** Sound trigger service is disabled */
+ public static final int SERVICE_STATE_DISABLED = 1;
+
+ /**
* Returns a list of descriptors for all harware modules loaded.
* @param modules A ModuleProperties array where the list will be returned.
* @return - {@link #STATUS_OK} in case of success
@@ -891,6 +1096,18 @@
public abstract void onRecognition(RecognitionEvent event);
/**
+ * Called when a sound model has been updated
+ */
+ public abstract void onSoundModelUpdate(SoundModelEvent event);
+
+ /**
+ * Called when the sound trigger native service state changes.
+ * @param state Native service state. One of {@link SoundTrigger#SERVICE_STATE_ENABLED},
+ * {@link SoundTrigger#SERVICE_STATE_DISABLED}
+ */
+ public abstract void onServiceStateChange(int state);
+
+ /**
* Called when the sound trigger native service dies
*/
public abstract void onServiceDied();
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 4a54fd8..1a8723d 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -36,8 +36,11 @@
private int mId;
private NativeEventHandlerDelegate mEventHandlerDelegate;
+ // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
+ private static final int EVENT_SOUNDMODEL = 3;
+ private static final int EVENT_SERVICE_STATE_CHANGE = 4;
SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
mId = moduleId;
@@ -133,10 +136,7 @@
if (handler != null) {
looper = handler.getLooper();
} else {
- looper = Looper.myLooper();
- if (looper == null) {
- looper = Looper.getMainLooper();
- }
+ looper = Looper.getMainLooper();
}
// construct the event handler with this looper
@@ -152,6 +152,17 @@
(SoundTrigger.RecognitionEvent)msg.obj);
}
break;
+ case EVENT_SOUNDMODEL:
+ if (listener != null) {
+ listener.onSoundModelUpdate(
+ (SoundTrigger.SoundModelEvent)msg.obj);
+ }
+ break;
+ case EVENT_SERVICE_STATE_CHANGE:
+ if (listener != null) {
+ listener.onServiceStateChange(msg.arg1);
+ }
+ break;
case EVENT_SERVICE_DIED:
if (listener != null) {
listener.onServiceDied();
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index e5a5292..0eda692 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -63,7 +63,6 @@
this.score = score;
this.notification = notification;
this.user = user;
- this.notification.setUser(user);
this.postTime = postTime;
this.key = key();
this.groupKey = groupKey();
@@ -83,7 +82,6 @@
this.score = in.readInt();
this.notification = new Notification(in);
this.user = UserHandle.readFromParcel(in);
- this.notification.setUser(this.user);
this.postTime = in.readLong();
this.key = key();
this.groupKey = groupKey();
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 279bf40..b0ff947 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -82,6 +82,15 @@
private static final int STATE_NOT_READY = 0;
// Keyphrase management actions. Used in getManageIntent() ----//
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ MANAGE_ACTION_ENROLL,
+ MANAGE_ACTION_RE_ENROLL,
+ MANAGE_ACTION_UN_ENROLL
+ })
+ public @interface ManageActions {}
+
/** Indicates that we need to enroll. */
public static final int MANAGE_ACTION_ENROLL = 0;
/** Indicates that we need to re-enroll. */
@@ -360,7 +369,7 @@
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
- public Intent getManageIntent(int action) {
+ public Intent getManageIntent(@ManageActions int action) {
if (DBG) Slog.d(TAG, "getManageIntent(" + action + ")");
synchronized (mLock) {
return getManageIntentLocked(action);
@@ -426,7 +435,7 @@
KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
// TODO: Do we need to do something about the confidence level here?
recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id,
- mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]);
+ mKeyphraseMetadata.recognitionModeFlags, 0, new ConfidenceLevel[0]);
boolean captureTriggerAudio =
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 8b2ec7a..681717c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1722,6 +1722,11 @@
return false;
}
+ /** @hide */
+ public static final boolean isMetaKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT;
+ }
+
/** {@inheritDoc} */
@Override
public final int getDeviceId() {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 191ad64..1e28e33 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -74,6 +74,7 @@
IBinder displayToken, int orientation,
int l, int t, int r, int b,
int L, int T, int R, int B);
+ private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
IBinder displayToken);
private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -588,6 +589,17 @@
}
}
+ public static void setDisplaySize(IBinder displayToken, int width, int height) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be positive");
+ }
+
+ nativeSetDisplaySize(displayToken, width, height);
+ }
+
public static IBinder createDisplay(String name, boolean secure) {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 5b80648..1716dbd 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -111,7 +111,15 @@
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
+ // OK, this is gross but needed. This class is supported by the
+ // remote views machanism and as a part of that the remote views
+ // can be inflated by a context for another user without the app
+ // having interact users permission - just for loading resources.
+ // For exmaple, when adding widgets from a user profile to the
+ // home screen. Therefore, we register the receiver as the current
+ // user not the one the context is for.
+ getContext().registerReceiverAsUser(mIntentReceiver,
+ android.os.Process.myUserHandle(), filter, null, mHandler);
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 5c7a43b..1098fa2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -17,6 +17,7 @@
package android.widget;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
import android.app.PendingIntent;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
@@ -73,11 +74,11 @@
static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
/**
- * User that these views should be applied as. Requires
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} when
- * crossing user boundaries.
+ * Application that hosts the remote views.
+ *
+ * @hide
*/
- private UserHandle mUser = android.os.Process.myUserHandle();
+ private ApplicationInfo mApplication;
/**
* The package name of the package containing the layout
@@ -275,9 +276,9 @@
/**
* Merges the passed RemoteViews actions with this RemoteViews actions according to
* action-specific merge rules.
- *
+ *
* @param newRv
- *
+ *
* @hide
*/
public void mergeRemoteViews(RemoteViews newRv) {
@@ -1608,16 +1609,16 @@
int bpp = 4;
if (c != null) {
switch (c) {
- case ALPHA_8:
- bpp = 1;
- break;
- case RGB_565:
- case ARGB_4444:
- bpp = 2;
- break;
- case ARGB_8888:
- bpp = 4;
- break;
+ case ALPHA_8:
+ bpp = 1;
+ break;
+ case RGB_565:
+ case ARGB_4444:
+ bpp = 2;
+ break;
+ case ARGB_8888:
+ bpp = 4;
+ break;
}
}
increment(b.getWidth() * b.getHeight() * bpp);
@@ -1637,17 +1638,13 @@
mPackage = packageName;
mLayoutId = layoutId;
mBitmapCache = new BitmapCache();
+ mApplication = ActivityThread.currentApplication().getApplicationInfo();
// setup the memory usage statistics
mMemoryUsageCounter = new MemoryUsageCounter();
recalculateMemoryUsage();
}
- /** {@hide} */
- public void setUser(UserHandle user) {
- mUser = user;
- }
-
private boolean hasLandscapeAndPortraitLayouts() {
return (mLandscape != null) && (mPortrait != null);
}
@@ -1713,53 +1710,53 @@
for (int i=0; i<count; i++) {
int tag = parcel.readInt();
switch (tag) {
- case SetOnClickPendingIntent.TAG:
- mActions.add(new SetOnClickPendingIntent(parcel));
- break;
- case SetDrawableParameters.TAG:
- mActions.add(new SetDrawableParameters(parcel));
- break;
- case ReflectionAction.TAG:
- mActions.add(new ReflectionAction(parcel));
- break;
- case ViewGroupAction.TAG:
- mActions.add(new ViewGroupAction(parcel, mBitmapCache));
- break;
- case ReflectionActionWithoutParams.TAG:
- mActions.add(new ReflectionActionWithoutParams(parcel));
- break;
- case SetEmptyView.TAG:
- mActions.add(new SetEmptyView(parcel));
- break;
- case SetPendingIntentTemplate.TAG:
- mActions.add(new SetPendingIntentTemplate(parcel));
- break;
- case SetOnClickFillInIntent.TAG:
- mActions.add(new SetOnClickFillInIntent(parcel));
- break;
- case SetRemoteViewsAdapterIntent.TAG:
- mActions.add(new SetRemoteViewsAdapterIntent(parcel));
- break;
- case TextViewDrawableAction.TAG:
- mActions.add(new TextViewDrawableAction(parcel));
- break;
- case TextViewSizeAction.TAG:
- mActions.add(new TextViewSizeAction(parcel));
- break;
- case ViewPaddingAction.TAG:
- mActions.add(new ViewPaddingAction(parcel));
- break;
- case BitmapReflectionAction.TAG:
- mActions.add(new BitmapReflectionAction(parcel));
- break;
- case SetRemoteViewsAdapterList.TAG:
- mActions.add(new SetRemoteViewsAdapterList(parcel));
- break;
- case TextViewDrawableColorFilterAction.TAG:
- mActions.add(new TextViewDrawableColorFilterAction(parcel));
- break;
- default:
- throw new ActionException("Tag " + tag + " not found");
+ case SetOnClickPendingIntent.TAG:
+ mActions.add(new SetOnClickPendingIntent(parcel));
+ break;
+ case SetDrawableParameters.TAG:
+ mActions.add(new SetDrawableParameters(parcel));
+ break;
+ case ReflectionAction.TAG:
+ mActions.add(new ReflectionAction(parcel));
+ break;
+ case ViewGroupAction.TAG:
+ mActions.add(new ViewGroupAction(parcel, mBitmapCache));
+ break;
+ case ReflectionActionWithoutParams.TAG:
+ mActions.add(new ReflectionActionWithoutParams(parcel));
+ break;
+ case SetEmptyView.TAG:
+ mActions.add(new SetEmptyView(parcel));
+ break;
+ case SetPendingIntentTemplate.TAG:
+ mActions.add(new SetPendingIntentTemplate(parcel));
+ break;
+ case SetOnClickFillInIntent.TAG:
+ mActions.add(new SetOnClickFillInIntent(parcel));
+ break;
+ case SetRemoteViewsAdapterIntent.TAG:
+ mActions.add(new SetRemoteViewsAdapterIntent(parcel));
+ break;
+ case TextViewDrawableAction.TAG:
+ mActions.add(new TextViewDrawableAction(parcel));
+ break;
+ case TextViewSizeAction.TAG:
+ mActions.add(new TextViewSizeAction(parcel));
+ break;
+ case ViewPaddingAction.TAG:
+ mActions.add(new ViewPaddingAction(parcel));
+ break;
+ case BitmapReflectionAction.TAG:
+ mActions.add(new BitmapReflectionAction(parcel));
+ break;
+ case SetRemoteViewsAdapterList.TAG:
+ mActions.add(new SetRemoteViewsAdapterList(parcel));
+ break;
+ case TextViewDrawableColorFilterAction.TAG:
+ mActions.add(new TextViewDrawableColorFilterAction(parcel));
+ break;
+ default:
+ throw new ActionException("Tag " + tag + " not found");
}
}
}
@@ -1771,6 +1768,8 @@
mLayoutId = mPortrait.getLayoutId();
}
+ mApplication = parcel.readParcelable(null);
+
// setup the memory usage statistics
mMemoryUsageCounter = new MemoryUsageCounter();
recalculateMemoryUsage();
@@ -2557,22 +2556,32 @@
}
private Context prepareContext(Context context) {
- Context c;
- String packageName = mPackage;
-
- if (packageName != null) {
- try {
- c = context.createPackageContextAsUser(
- packageName, Context.CONTEXT_RESTRICTED, mUser);
- } catch (NameNotFoundException e) {
- Log.e(LOG_TAG, "Package name " + packageName + " not found");
- c = context;
+ if (mApplication != null) {
+ if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
+ && context.getPackageName().equals(mApplication.packageName)) {
+ return context;
}
- } else {
- c = context;
+ try {
+ return context.createApplicationContext(mApplication,
+ Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "Package name " + mPackage + " not found");
+ }
}
- return c;
+ if (mPackage != null) {
+ if (context.getPackageName().equals(mPackage)) {
+ return context;
+ }
+ try {
+ return context.createPackageContext(
+ mPackage, Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "Package name " + mPackage + " not found");
+ }
+ }
+
+ return context;
}
/**
@@ -2629,6 +2638,8 @@
mLandscape.writeToParcel(dest, flags);
mPortrait.writeToParcel(dest, flags);
}
+
+ dest.writeParcelable(mApplication, 0);
}
/**
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index bbe6f9e..5d21e0b 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -114,8 +114,6 @@
// construction (happens when we have a cached FixedSizeRemoteViewsCache).
private boolean mDataReady = false;
- int mUserId;
-
/**
* An interface for the RemoteAdapter to notify other classes when adapters
* are actually connected to/disconnected from their actual services.
@@ -159,9 +157,8 @@
RemoteViewsAdapter adapter;
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
if ((adapter = mAdapter.get()) != null) {
- checkInteractAcrossUsersPermission(context, adapter.mUserId);
- mgr.bindRemoteViewsService(appWidgetId, intent, asBinder(),
- new UserHandle(adapter.mUserId));
+ mgr.bindRemoteViewsService(context.getPackageName(), appWidgetId,
+ intent, asBinder());
} else {
Slog.w(TAG, "bind: adapter was null");
}
@@ -179,9 +176,7 @@
RemoteViewsAdapter adapter;
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
if ((adapter = mAdapter.get()) != null) {
- checkInteractAcrossUsersPermission(context, adapter.mUserId);
- mgr.unbindRemoteViewsService(appWidgetId, intent,
- new UserHandle(adapter.mUserId));
+ mgr.unbindRemoteViewsService(context.getPackageName(), appWidgetId, intent);
} else {
Slog.w(TAG, "unbind: adapter was null");
}
@@ -796,12 +791,10 @@
static class RemoteViewsCacheKey {
final Intent.FilterComparison filter;
final int widgetId;
- final int userId;
- RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId, int userId) {
+ RemoteViewsCacheKey(Intent.FilterComparison filter, int widgetId) {
this.filter = filter;
this.widgetId = widgetId;
- this.userId = userId;
}
@Override
@@ -810,29 +803,28 @@
return false;
}
RemoteViewsCacheKey other = (RemoteViewsCacheKey) o;
- return other.filter.equals(filter) && other.widgetId == widgetId
- && other.userId == userId;
+ return other.filter.equals(filter) && other.widgetId == widgetId;
}
@Override
public int hashCode() {
- return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2) ^ (userId << 10);
+ return (filter == null ? 0 : filter.hashCode()) ^ (widgetId << 2);
}
}
- public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
+ public RemoteViewsAdapter(Context context, Intent intent,
+ RemoteAdapterConnectionCallback callback) {
mContext = context;
mIntent = intent;
+
mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
+
mLayoutInflater = LayoutInflater.from(context);
if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified.");
}
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
- checkInteractAcrossUsersPermission(context, UserHandle.myUserId());
- mUserId = context.getUserId();
-
// Strip the previously injected app widget id from service intent
if (intent.hasExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID)) {
intent.removeExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID);
@@ -855,7 +847,7 @@
mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
- mAppWidgetId, mUserId);
+ mAppWidgetId);
synchronized(sCachedRemoteViewsCaches) {
if (sCachedRemoteViewsCaches.containsKey(key)) {
@@ -876,15 +868,6 @@
}
}
- private static void checkInteractAcrossUsersPermission(Context context, int userId) {
- if (context.getUserId() != userId
- && context.checkCallingOrSelfPermission(MULTI_USER_PERM)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Must have permission " + MULTI_USER_PERM
- + " to inflate another user's widget");
- }
- }
-
@Override
protected void finalize() throws Throwable {
try {
@@ -906,7 +889,7 @@
public void saveRemoteViewsCache() {
final RemoteViewsCacheKey key = new RemoteViewsCacheKey(
- new Intent.FilterComparison(mIntent), mAppWidgetId, mUserId);
+ new Intent.FilterComparison(mIntent), mAppWidgetId);
synchronized(sCachedRemoteViewsCaches) {
// If we already have a remove runnable posted for this key, remove it.
@@ -1028,7 +1011,6 @@
long itemId = 0;
try {
remoteViews = factory.getViewAt(position);
- remoteViews.setUser(new UserHandle(mUserId));
itemId = factory.getItemId(position);
} catch (RemoteException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index b152297..cf1f554 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -21,8 +21,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
-import android.os.Handler;
-import android.os.Message;
+import android.os.*;
import android.util.AttributeSet;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
@@ -90,7 +89,16 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
- getContext().registerReceiver(mReceiver, filter, null, mHandler);
+
+ // OK, this is gross but needed. This class is supported by the
+ // remote views machanism and as a part of that the remote views
+ // can be inflated by a context for another user without the app
+ // having interact users permission - just for loading resources.
+ // For exmaple, when adding widgets from a user profile to the
+ // home screen. Therefore, we register the receiver as the current
+ // user not the one the context is for.
+ getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
+ filter, null, mHandler);
if (mAutoStart) {
// Automatically start when requested
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f0e7215..59891d6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -229,7 +229,7 @@
}
mAlwaysUseOption = alwaysUseOption;
- int count = mAdapter.getCount();
+ int count = mAdapter.mList.size();
if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
// Gulp!
finish();
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 6d51d38..a7f7fe1 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -16,15 +16,16 @@
package com.android.internal.appwidget;
+import android.content.pm.ApplicationInfo;
import android.content.ComponentName;
import android.appwidget.AppWidgetProviderInfo;
import android.widget.RemoteViews;
/** {@hide} */
oneway interface IAppWidgetHost {
- void updateAppWidget(int appWidgetId, in RemoteViews views, int userId);
- void providerChanged(int appWidgetId, in AppWidgetProviderInfo info, int userId);
- void providersChanged(int userId);
- void viewDataChanged(int appWidgetId, int viewId, int userId);
+ void updateAppWidget(int appWidgetId, in RemoteViews views);
+ void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
+ void providersChanged();
+ void viewDataChanged(int appWidgetId, int viewId);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 5214dd9..9da1c9d 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -18,6 +18,8 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
import android.appwidget.AppWidgetProviderInfo;
import com.android.internal.appwidget.IAppWidgetHost;
import android.os.Bundle;
@@ -30,34 +32,37 @@
//
// for AppWidgetHost
//
- int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- out List<RemoteViews> updatedViews, int userId);
- void stopListening(int hostId, int userId);
- int allocateAppWidgetId(String packageName, int hostId, int userId);
- void deleteAppWidgetId(int appWidgetId, int userId);
- void deleteHost(int hostId, int userId);
- void deleteAllHosts(int userId);
- RemoteViews getAppWidgetViews(int appWidgetId, int userId);
- int[] getAppWidgetIdsForHost(int hostId, int userId);
+ int[] startListening(IAppWidgetHost host, String callingPackage, int hostId,
+ out List<RemoteViews> updatedViews);
+ void stopListening(String callingPackage, int hostId);
+ int allocateAppWidgetId(String callingPackage, int hostId);
+ void deleteAppWidgetId(String callingPackage, int appWidgetId);
+ void deleteHost(String packageName, int hostId);
+ void deleteAllHosts();
+ RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId);
+ int[] getAppWidgetIdsForHost(String callingPackage, int hostId);
+ IntentSender createAppWidgetConfigIntentSender(String callingPackage, in Intent intent);
//
// for AppWidgetManager
//
- void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
- void updateAppWidgetOptions(int appWidgetId, in Bundle extras, int userId);
- Bundle getAppWidgetOptions(int appWidgetId, int userId);
- void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
- void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views, int userId);
- void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId, int userId);
- List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId);
- AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId);
+ void updateAppWidgetIds(String callingPackage, in int[] appWidgetIds, in RemoteViews views);
+ void updateAppWidgetOptions(String callingPackage, int appWidgetId, in Bundle extras);
+ Bundle getAppWidgetOptions(String callingPackage, int appWidgetId);
+ void partiallyUpdateAppWidgetIds(String callingPackage, in int[] appWidgetIds,
+ in RemoteViews views);
+ void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
+ void notifyAppWidgetViewDataChanged(String packageName, in int[] appWidgetIds, int viewId);
+ List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter,
+ in int[] profileIds);
+ AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId);
boolean hasBindAppWidgetPermission(in String packageName, int userId);
- void setBindAppWidgetPermission(in String packageName, in boolean permission, int userId);
- void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options, int userId);
- boolean bindAppWidgetIdIfAllowed(in String packageName, int appWidgetId,
- in ComponentName provider, in Bundle options, int userId);
- void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
- void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
- int[] getAppWidgetIds(in ComponentName provider, int userId);
+ void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
+ boolean bindAppWidgetId(in String callingPackage, int appWidgetId,
+ int providerProfileId, in ComponentName providerComponent, in Bundle options);
+ void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
+ in IBinder connection);
+ void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent);
+ int[] getAppWidgetIds(in ComponentName providerComponent);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 7901379..69ffbfe 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -39,7 +39,7 @@
void notificationLightPulse(int argb, int millisOn, int millisOff);
void showRecentApps(boolean triggeredFromAltTab);
- void hideRecentApps(boolean triggeredFromAltTab);
+ void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecentApps();
void preloadRecentApps();
void cancelPreloadRecentApps();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 243ce97..50c82bb 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -55,7 +55,7 @@
void setWindowState(int window, int state);
void showRecentApps(boolean triggeredFromAltTab);
- void hideRecentApps(boolean triggeredFromAltTab);
+ void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecentApps();
void preloadRecentApps();
void cancelPreloadRecentApps();
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index c9a0b1e..f0d7a35 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -29,6 +29,7 @@
#include <utils/Vector.h>
#include <binder/IMemory.h>
#include <binder/MemoryDealer.h>
+#include "android_media_AudioFormat.h"
using namespace android;
@@ -63,6 +64,7 @@
static jclass gSoundModelClass;
static struct {
jfieldID uuid;
+ jfieldID vendorUuid;
jfieldID data;
} gSoundModelFields;
@@ -110,6 +112,7 @@
static struct {
jfieldID id;
jfieldID recognitionModes;
+ jfieldID coarseConfidenceLevel;
jfieldID confidenceLevels;
} gKeyphraseRecognitionExtraFields;
@@ -122,6 +125,16 @@
jfieldID confidenceLevel;
} gConfidenceLevelFields;
+static const char* const kAudioFormatClassPathName =
+ "android/media/AudioFormat";
+static jclass gAudioFormatClass;
+static jmethodID gAudioFormatCstor;
+
+static const char* const kSoundModelEventClassPathName =
+ "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
+static jclass gSoundModelEventClass;
+static jmethodID gSoundModelEventCstor;
+
static Mutex gLock;
enum {
@@ -137,6 +150,8 @@
enum {
SOUNDTRIGGER_EVENT_RECOGNITION = 1,
SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
+ SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
+ SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
};
// ----------------------------------------------------------------------------
@@ -148,6 +163,8 @@
~JNISoundTriggerCallback();
virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
+ virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
+ virtual void onServiceStateChange(sound_trigger_service_state_t state);
virtual void onServiceDied();
private:
@@ -183,10 +200,9 @@
void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
-
- jobject jEvent;
-
+ jobject jEvent = NULL;
jbyteArray jData = NULL;
+
if (event->data_size) {
jData = env->NewByteArray(event->data_size);
jbyte *nData = env->GetByteArrayElements(jData, NULL);
@@ -194,6 +210,15 @@
env->ReleaseByteArrayElements(jData, nData, 0);
}
+ jobject jAudioFormat = NULL;
+ if (event->trigger_in_data) {
+ jAudioFormat = env->NewObject(gAudioFormatClass,
+ gAudioFormatCstor,
+ audioFormatFromNative(event->audio_config.format),
+ event->audio_config.sample_rate,
+ inChannelMaskFromNative(event->audio_config.channel_mask));
+
+ }
if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
struct sound_trigger_phrase_recognition_event *phraseEvent =
(struct sound_trigger_phrase_recognition_event *)event;
@@ -225,6 +250,7 @@
gKeyphraseRecognitionExtraCstor,
phraseEvent->phrase_extras[i].id,
phraseEvent->phrase_extras[i].recognition_modes,
+ phraseEvent->phrase_extras[i].confidence_level,
jConfidenceLevels);
if (jNewExtra == NULL) {
@@ -236,19 +262,63 @@
}
jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, jData,
- phraseEvent->key_phrase_in_capture, jExtras);
+ event->capture_session, event->capture_delay_ms,
+ event->capture_preamble_ms, event->trigger_in_data,
+ jAudioFormat, jData, jExtras);
+ env->DeleteLocalRef(jAudioFormat);
+ env->DeleteLocalRef(jData);
} else {
jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
event->status, event->model, event->capture_available,
event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, jData);
+ event->capture_preamble_ms, event->trigger_in_data,
+ jAudioFormat, jData);
+ env->DeleteLocalRef(jAudioFormat);
+ env->DeleteLocalRef(jData);
}
env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
+
+ env->DeleteLocalRef(jEvent);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred while notifying an event.");
+ env->ExceptionClear();
+ }
+}
+
+void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jobject jEvent = NULL;
+ jbyteArray jData = NULL;
+
+ if (event->data_size) {
+ jData = env->NewByteArray(event->data_size);
+ jbyte *nData = env->GetByteArrayElements(jData, NULL);
+ memcpy(nData, (char *)event + event->data_offset, event->data_size);
+ env->ReleaseByteArrayElements(jData, nData, 0);
+ }
+
+ jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
+ event->status, event->model, jData);
+
+ env->DeleteLocalRef(jData);
+ env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+ SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
+ env->DeleteLocalRef(jEvent);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred while notifying an event.");
+ env->ExceptionClear();
+ }
+}
+
+void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+ SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
@@ -336,7 +406,7 @@
SOUND_TRIGGER_MAX_STRING_LEN);
jstring uuid = env->NewStringUTF(str);
- ALOGV("listModules module %d id %d description %s maxSoundModels %d",
+ ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
i, nModules[i].handle, nModules[i].properties.description,
nModules[i].properties.max_sound_models);
@@ -351,7 +421,8 @@
nModules[i].properties.capture_transition,
nModules[i].properties.max_buffer_ms,
nModules[i].properties.concurrent_capture,
- nModules[i].properties.power_consumption_mw);
+ nModules[i].properties.power_consumption_mw,
+ nModules[i].properties.trigger_in_event);
env->DeleteLocalRef(implementor);
env->DeleteLocalRef(description);
@@ -463,6 +534,18 @@
env->ReleaseStringUTFChars(jUuidString, nUuidString);
env->DeleteLocalRef(jUuidString);
+ sound_trigger_uuid_t nVendorUuid;
+ jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
+ if (jUuid != NULL) {
+ jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
+ nUuidString = env->GetStringUTFChars(jUuidString, NULL);
+ SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
+ env->ReleaseStringUTFChars(jUuidString, nUuidString);
+ env->DeleteLocalRef(jUuidString);
+ } else {
+ SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
+ }
+
jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
if (jData == NULL) {
status = SOUNDTRIGGER_STATUS_BAD_VALUE;
@@ -491,6 +574,7 @@
nSoundModel->type = type;
nSoundModel->uuid = nUuid;
+ nSoundModel->vendor_uuid = nVendorUuid;
nSoundModel->data_size = size;
nSoundModel->data_offset = offset;
memcpy((char *)nSoundModel + offset, nData, size);
@@ -507,7 +591,7 @@
size_t numPhrases = env->GetArrayLength(jPhrases);
phraseModel->num_phrases = numPhrases;
- ALOGV("loadSoundModel numPhrases %d", numPhrases);
+ ALOGV("loadSoundModel numPhrases %zu", numPhrases);
for (size_t i = 0; i < numPhrases; i++) {
jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
phraseModel->phrases[i].id =
@@ -539,7 +623,7 @@
env->DeleteLocalRef(jLocale);
env->ReleaseStringUTFChars(jText, nText);
env->DeleteLocalRef(jText);
- ALOGV("loadSoundModel phrases %d text %s locale %s",
+ ALOGV("loadSoundModel phrases %zu text %s locale %s",
i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
env->DeleteLocalRef(jPhrase);
}
@@ -640,13 +724,15 @@
gKeyphraseRecognitionExtraFields.id);
config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
gKeyphraseRecognitionExtraFields.recognitionModes);
+ config->phrases[i].confidence_level = env->GetIntField(jPhrase,
+ gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
config->phrases[i].num_levels = 0;
jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
gKeyphraseRecognitionExtraFields.confidenceLevels);
if (jConfidenceLevels != NULL) {
config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
}
- ALOGV("startRecognition phrase %d num_levels %d", i, config->phrases[i].num_levels);
+ ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
@@ -655,7 +741,7 @@
gConfidenceLevelFields.confidenceLevel);
env->DeleteLocalRef(jConfidenceLevel);
}
- ALOGV("startRecognition phrases %d", i);
+ ALOGV("startRecognition phrases %zu", i);
env->DeleteLocalRef(jConfidenceLevels);
env->DeleteLocalRef(jPhrase);
}
@@ -734,11 +820,12 @@
jclass modulePropertiesClass = env->FindClass(kModulePropertiesClassPathName);
gModulePropertiesClass = (jclass) env->NewGlobalRef(modulePropertiesClass);
gModulePropertiesCstor = env->GetMethodID(modulePropertiesClass, "<init>",
- "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZI)V");
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
jclass soundModelClass = env->FindClass(kSoundModelClassPathName);
gSoundModelClass = (jclass) env->NewGlobalRef(soundModelClass);
gSoundModelFields.uuid = env->GetFieldID(soundModelClass, "uuid", "Ljava/util/UUID;");
+ gSoundModelFields.vendorUuid = env->GetFieldID(soundModelClass, "vendorUuid", "Ljava/util/UUID;");
gSoundModelFields.data = env->GetFieldID(soundModelClass, "data", "[B");
jclass keyphraseClass = env->FindClass(kKeyphraseClassPathName);
@@ -759,12 +846,12 @@
jclass recognitionEventClass = env->FindClass(kRecognitionEventClassPathName);
gRecognitionEventClass = (jclass) env->NewGlobalRef(recognitionEventClass);
gRecognitionEventCstor = env->GetMethodID(recognitionEventClass, "<init>",
- "(IIZIII[B)V");
+ "(IIZIIIZLandroid/media/AudioFormat;[B)V");
jclass keyphraseRecognitionEventClass = env->FindClass(kKeyphraseRecognitionEventClassPathName);
gKeyphraseRecognitionEventClass = (jclass) env->NewGlobalRef(keyphraseRecognitionEventClass);
gKeyphraseRecognitionEventCstor = env->GetMethodID(keyphraseRecognitionEventClass, "<init>",
- "(IIZIII[BZ[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
+ "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
jclass keyRecognitionConfigClass = env->FindClass(kRecognitionConfigClassPathName);
@@ -782,9 +869,12 @@
jclass keyphraseRecognitionExtraClass = env->FindClass(kKeyphraseRecognitionExtraClassPathName);
gKeyphraseRecognitionExtraClass = (jclass) env->NewGlobalRef(keyphraseRecognitionExtraClass);
gKeyphraseRecognitionExtraCstor = env->GetMethodID(keyphraseRecognitionExtraClass, "<init>",
- "(II[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
+ "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
gKeyphraseRecognitionExtraFields.id = env->GetFieldID(gKeyphraseRecognitionExtraClass, "id", "I");
- gKeyphraseRecognitionExtraFields.recognitionModes = env->GetFieldID(gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
+ gKeyphraseRecognitionExtraFields.recognitionModes = env->GetFieldID(gKeyphraseRecognitionExtraClass,
+ "recognitionModes", "I");
+ gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = env->GetFieldID(gKeyphraseRecognitionExtraClass,
+ "coarseConfidenceLevel", "I");
gKeyphraseRecognitionExtraFields.confidenceLevels = env->GetFieldID(gKeyphraseRecognitionExtraClass,
"confidenceLevels",
"[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
@@ -796,6 +886,16 @@
gConfidenceLevelFields.confidenceLevel = env->GetFieldID(confidenceLevelClass,
"confidenceLevel", "I");
+ jclass audioFormatClass = env->FindClass(kAudioFormatClassPathName);
+ gAudioFormatClass = (jclass) env->NewGlobalRef(audioFormatClass);
+ gAudioFormatCstor = env->GetMethodID(audioFormatClass, "<init>", "(III)V");
+
+ jclass soundModelEventClass = env->FindClass(kSoundModelEventClassPathName);
+ gSoundModelEventClass = (jclass) env->NewGlobalRef(soundModelEventClass);
+ gSoundModelEventCstor = env->GetMethodID(soundModelEventClass, "<init>",
+ "(II[B)V");
+
+
int status = AndroidRuntime::registerNativeMethods(env,
kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9783e91..3fb084a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -369,6 +369,13 @@
SurfaceComposerClient::setDisplayProjection(token, orientation, layerStackRect, displayRect);
}
+static void nativeSetDisplaySize(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jint width, jint height) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+ SurfaceComposerClient::setDisplaySize(token, width, height);
+}
+
static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz,
jobject tokenObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
@@ -620,6 +627,8 @@
(void*)nativeSetDisplayLayerStack },
{"nativeSetDisplayProjection", "(Landroid/os/IBinder;IIIIIIIII)V",
(void*)nativeSetDisplayProjection },
+ {"nativeSetDisplaySize", "(Landroid/os/IBinder;II)V",
+ (void*)nativeSetDisplaySize },
{"nativeGetDisplayConfigs", "(Landroid/os/IBinder;)[Landroid/view/SurfaceControl$PhysicalDisplayInfo;",
(void*)nativeGetDisplayConfigs },
{"nativeGetActiveConfig", "(Landroid/os/IBinder;)I",
diff --git a/core/res/res/anim/progress_indeterminate_horizontal_rect1_scale.xml b/core/res/res/anim/progress_indeterminate_horizontal_rect1_scale.xml
new file mode 100644
index 0000000..7c782a6
--- /dev/null
+++ b/core/res/res/anim/progress_indeterminate_horizontal_rect1_scale.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="2016"
+ android:pathData="M 0.1 1 l 0 0 l 0.00882427215576 0 l 0.00982859611511 0
+l 0.01086503982544 0 l 0.01193084716797 0 l 0.0130220413208 0 l 0.01413340568542 0
+l 0.01525821685791 0 l 0.01638801574707 0 l 0.01751272201538 0 l 0.01862035751343 0
+l 0.01969732284546 0 l 0.02072854995728 0 l 0.02169786453247 0 l 0.02258871078491 0
+l 0.02338474273682 0 l 0.02407070159912 0 l 0.02463348388672 0 l 0.0250626373291 0
+l 0.02535140991211 0 l 0.02549694061279 0 l 0.02550048828125 0 l 0.02536708831787 0
+l 0.02510528564453 0 l 0.02472625732422 0 l 0.0242431640625 0 l 0.02367015838623 0
+l 0.02302188873291 0 l 0.02231246948242 0 l 0.02155555725098 0 l 0.02076324462891 0
+l 0.01994682312012 0 l 0.01911575317383 0 l 0.01827827453613 0 l 0.01732414245605 0
+l 0.01522109985352 0 l 0.01262580871582 0 l 0.00973388671875 0 l 0.00647575378418 0
+l 0.0027661895752 0 l -0.00149223327637 0 l -0.00639404296875 0 l -0.01199066162109 0
+l -0.01820671081543 0 l -0.02470901489258 0 l -0.03080444335937 0 l -0.0355574798584 0
+l -0.03823974609375 0 l -0.03876884460449 0 l -0.03766212463379 0 l -0.03562252044678 0
+l -0.03321434020996 0 l -0.03078151702881 0 l -0.02849582672119 0 l -0.02642543792725 0
+l -0.02458423614502 0 l -0.02296115875244 0 l -0.02153518676758 0 l -0.02028285980225 0
+l -0.01918155670166 0 l -0.01821084976196 0 l -0.01735286712646 0 l -0.01659231185913 0
+l -0.01591604232788 0 l -0.0153129196167 0 l -0.01477350234985 0 l -0.01413362503052 0
+l -0.01339265823364 0 l -0.01270362854004 0 l -0.01206108093262 0 l -0.01146033287048 0
+l -0.01089729309082 0 l -0.01036835670471 0 l -0.00987038612366 0 l -0.00940062522888 0
+l -0.00895661354065 0 l -0.00853617668152 0"
+ android:propertyXName="scaleX"
+ android:repeatCount="infinite" />
+
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/progress_indeterminate_horizontal_rect1_translate.xml b/core/res/res/anim/progress_indeterminate_horizontal_rect1_translate.xml
new file mode 100644
index 0000000..c26bb5d
--- /dev/null
+++ b/core/res/res/anim/progress_indeterminate_horizontal_rect1_translate.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="2016"
+ android:pathData="M -522.599975585938 0 l 0 0 l 0.12939453125 0
+l 0.33831787109375 0 l 0.55450439453125 0 l 0.7708740234375 0 l 0.98065185546875 0
+l 1.1964111328125 0 l 1.41351318359375 0 l 1.63153076171875 0 l 1.85052490234375 0
+l 2.07052612304688 0 l 2.29080200195312 0 l 2.51150512695312 0 l 2.73260498046875 0
+l 2.95355224609375 0 l 3.17404174804688 0 l 3.39422607421875 0 l 3.61355590820312 0
+l 3.83163452148438 0 l 4.04849243164062 0 l 4.263671875 0 l 5.74725341796875 0
+l 6.1026611328125 0 l 6.45980834960938 0 l 6.81781005859375 0 l 7.17654418945312 0
+l 7.53366088867188 0 l 7.88861083984375 0 l 8.23974609375 0 l 8.58447265625 0
+l 8.92156982421875 0 l 9.24810791015625 0 l 9.56137084960938 0 l 9.85906982421875 0
+l 10.1377868652344 0 l 10.3955688476562 0 l 10.6287536621094 0 l 10.8357238769531 0
+l 11.0149230957031 0 l 11.1639709472656 0 l 11.2832336425781 0 l 11.3713989257812 0
+l 11.4301147460938 0 l 11.4596557617188 0 l 11.4611053466797 0 l 11.4369049072266 0
+l 11.3887786865234 0 l 11.3183441162109 0 l 11.2276000976562 0 l 11.1185607910156 0
+l 10.9933776855469 0 l 10.8534698486328 0 l 10.6995391845703 0 l 10.533935546875 0
+l 10.3744659423828 0 l 10.3707733154297 0 l 10.4309463500977 0 l 10.5275726318359 0
+l 10.671501159668 0 l 10.8763961791992 0 l 11.1566543579102 0 l 11.5270767211914 0
+l 11.9947967529297 0 l 12.5502433776855 0 l 13.1453399658203 0 l 13.680793762207 0
+l 14.0223298072815 0 l 14.0650296211243 0 l 13.798041343689 0 l 13.2949924468994 0
+l 12.6584892272949 0 l 11.9693031311035 0 l 11.2772979736328 0 l 10.607666015625 0
+l 9.97052764892578 0 l 9.36723327636719 0 l 8.79751586914062 0 l 8.25792694091797 0
+l 7.74495697021484 0 l 7.25632476806641 0 l 6.78855895996094 0 l 6.33934020996094 0
+l 5.9071044921875 0 l 5.48941040039062 0 l 5.08502197265625 0 l 4.69291687011719 0
+l 4.33430480957031 0 l 4.00733947753906 0 l 3.68829345703125 0 l 3.37684631347656 0
+l 3.07246398925781 0 l 2.77439880371094 0 l 2.48252868652344 0 l 2.20101928710938 0
+l 1.91748046875 0 l 1.63726806640625 0 l 1.36772155761719 0"
+ android:propertyXName="translateX"
+ android:repeatCount="infinite" />
+
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/progress_indeterminate_horizontal_rect2_scale.xml b/core/res/res/anim/progress_indeterminate_horizontal_rect2_scale.xml
new file mode 100644
index 0000000..ef1677d
--- /dev/null
+++ b/core/res/res/anim/progress_indeterminate_horizontal_rect2_scale.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="2016"
+ android:pathData="M 0.1 1 l 0.00930031776428 0 l 0.01123028755188 0
+l 0.01313143730164 0 l 0.01497107505798 0 l 0.01671510696411 0 l 0.01833034515381 0
+l 0.01978672027588 0 l 0.02105976104736 0 l 0.02213228225708 0 l 0.02299520492554 0
+l 0.02364795684814 0 l 0.02409727096558 0 l 0.02435619354248 0 l 0.02444213867188 0
+l 0.02437515258789 0 l 0.02417644500732 0 l 0.02386695861816 0 l 0.02346652984619 0
+l 0.02299335479736 0 l 0.0224634552002 0 l 0.02189086914062 0 l 0.02128746032715 0
+l 0.02066318511963 0 l 0.02002624511719 0 l 0.01938335418701 0 l 0.01873977661133 0
+l 0.01809989929199 0 l 0.01746696472168 0 l 0.01684349060059 0 l 0.01623161315918 0
+l 0.0156324005127 0 l 0.0150471496582 0 l 0.01447631835938 0 l 0.01392051696777 0
+l 0.01337966918945 0 l 0.0128540802002 0 l 0.01234344482422 0 l 0.01184753417969 0
+l 0.0113663482666 0 l 0.01089920043945 0 l 0.01044593811035 0 l 0.00998542785645 0
+l 0.00933837890625 0 l 0.00863349914551 0 l 0.00791206359863 0 l 0.00717010498047 0
+l 0.00640274047852 0 l 0.00560478210449 0 l 0.00477012634277 0 l 0.00389221191406 0
+l 0.00296325683594 0 l 0.0019751739502 0 l 0.00091903686523 0 l -0.00021408081055 0
+l -0.00143287658691 0 l -0.00274444580078 0 l -0.00415267944336 0 l -0.00565589904785 0
+l -0.00724327087402 0 l -0.00889205932617 0 l -0.01056480407715 0 l -0.01220878601074 0
+l -0.01376045227051 0 l -0.01515449523926 0 l -0.01633560180664 0 l -0.01726905822754 0
+l -0.01794639587402 0 l -0.0183829498291 0 l -0.01861137390137 0 l -0.01867179870605 0
+l -0.01860504150391 0 l -0.01844764709473 0 l -0.01822959899902 0 l -0.01797431945801 0
+l -0.0176993560791 0 l -0.0174169921875 0 l -0.01713603973389 0 l -0.01686214447021 0
+l -0.01651359558105 0 l -0.01609485626221 0 l -0.01569358825684 0 l -0.01531024932861 0
+l -0.0149446105957 0 l -0.01459632873535 0 l -0.01426464080811 0 l -0.0139489364624 0
+l -0.01364833831787 0 l -0.01336200714111 0 l -0.01308917999268 0 l -0.01282897949219 0
+l -0.01258075714111 0 l -0.01234363555908 0 l -0.01211700439453 0 l -0.01190029144287 0
+l -0.01169273376465 0 l -0.01149394989014 0 l -0.01130325317383 0 l -0.01112024307251 0
+l -0.01094444274902 0 l -0.01077545166016 0 l -0.0106128692627 0 l -0.01045631408691 0
+l -0.01030544281006 0 l -0.01016000747681 0 l -0.01001962661743 0 l -0.0098840713501 0
+l -0.00975311279297 0 l -0.00962644577026 0 l -0.00950393676758 0 l -0.00938529968262 0
+l -0.00927038192749 0 l -0.00915899276733 0 l -0.00905097961426 0 l -0.00894614219666 0
+l -0.00884438514709 0 l -0.00874552726746 0 l -0.00864946365356 0 l -0.00855606079102 0
+l -0.00846519470215 0 l -0.00837676048279 0 "
+ android:propertyXName="scaleX"
+ android:repeatCount="infinite" />
+
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/progress_indeterminate_horizontal_rect2_translate.xml b/core/res/res/anim/progress_indeterminate_horizontal_rect2_translate.xml
new file mode 100644
index 0000000..f4cf83d
--- /dev/null
+++ b/core/res/res/anim/progress_indeterminate_horizontal_rect2_translate.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="2016"
+ android:pathData="M -197.600006103516 0 l 1.42625427246094 0
+l 1.80754089355469 0 l 2.18778991699219 0 l 2.56109619140625 0 l 2.91810607910156 0
+l 3.25482177734375 0 l 3.57159423828125 0 l 3.862548828125 0 l 4.12493896484375 0
+l 4.35758972167969 0 l 4.56034851074219 0 l 4.73426818847656 0 l 4.88090515136719 0
+l 5.00271606445312 0 l 5.10273742675781 0 l 5.18400573730469 0 l 5.24911499023438 0
+l 5.30097961425781 0 l 5.34226226806641 0 l 5.37535095214844 0 l 5.40180206298828 0
+l 5.42322540283203 0 l 5.44123077392578 0 l 5.45704650878906 0 l 5.47099304199219 0
+l 5.48395538330078 0 l 5.4967041015625 0 l 5.50949859619141 0 l 5.52214813232422 0
+l 5.53528594970703 0 l 5.54912567138672 0 l 5.56306457519531 0 l 5.57742691040039 0
+l 5.59244155883789 0 l 5.60744094848633 0 l 5.62243270874023 0 l 5.6376781463623 0
+l 5.65262794494629 0 l 5.66689777374268 0 l 5.68069934844971 0 l 5.69401162862778 0
+l 5.70898681879044 0 l 5.75169992446899 0 l 5.80327129364014 0 l 5.85710144042969 0
+l 5.91399765014648 0 l 5.97450065612793 0 l 6.03849411010742 0 l 6.10729217529297 0
+l 6.18125534057617 0 l 6.26116561889648 0 l 6.34840393066406 0 l 6.44406127929688 0
+l 6.54866790771484 0 l 6.66371917724609 0 l 6.79020690917969 0 l 6.92859649658203 0
+l 7.07807159423828 0 l 7.23712158203125 0 l 7.40253448486328 0 l 7.56884765625 0
+l 7.72840881347656 0 l 7.87199401855469 0 l 7.98992919921875 0 l 8.07417297363281 0
+l 8.12013244628906 0 l 8.12655639648438 0 l 8.09510803222656 0 l 8.03091430664062 0
+l 7.93995666503906 0 l 7.827880859375 0 l 7.69976806640625 0 l 7.56065368652344 0
+l 7.41322326660156 0 l 7.26063537597656 0 l 7.10470581054688 0 l 6.94624328613281 0
+l 6.78694152832031 0 l 6.6390380859375 0 l 6.50302124023438 0 l 6.36688232421875 0
+l 6.23043823242188 0 l 6.09356689453125 0 l 5.95706176757812 0 l 5.82064819335938 0
+l 5.6839599609375 0 l 5.5477294921875 0 l 5.41143798828125 0 l 5.27532958984375 0
+l 5.13922119140625 0 l 5.00347900390625 0 l 4.8680419921875 0 l 4.73251342773438 0
+l 4.59732055664062 0 l 4.46258544921875 0 l 4.328125 0 l 4.1937255859375 0
+l 4.0599365234375 0 l 3.92672729492188 0 l 3.79376220703125 0 l 3.66119384765625 0
+l 3.52935791015625 0 l 3.398193359375 0 l 3.26748657226562 0 l 3.13726806640625 0
+l 3.00796508789062 0 l 2.87939453125 0 l 2.7515869140625 0 l 2.62445068359375 0
+l 2.49810791015625 0 l 2.3726806640625 0 l 2.2481689453125 0 l 2.12457275390625 0
+l 2.00173950195312 0 l 1.87997436523438 0 l 1.7618408203125 0 l 1.64154052734375 0
+l 1.51962280273438 0 l 1.40017700195312 0 l 1.28421020507812 0 "
+ android:propertyXName="translateX"
+ android:repeatCount="infinite" />
+
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable/progress_indeterminate_horizontal_material.xml b/core/res/res/drawable/progress_indeterminate_horizontal_material.xml
new file mode 100644
index 0000000..4fc68ce
--- /dev/null
+++ b/core/res/res/drawable/progress_indeterminate_horizontal_material.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal" >
+
+ <target
+ android:name="path1"
+ android:animation="@anim/progress_indeterminate_horizontal_rect1_translate" />
+ <target
+ android:name="path1"
+ android:animation="@anim/progress_indeterminate_horizontal_rect1_scale" />
+
+ <target
+ android:name="path2"
+ android:animation="@anim/progress_indeterminate_horizontal_rect2_translate" />
+ <target
+ android:name="path2"
+ android:animation="@anim/progress_indeterminate_horizontal_rect2_scale" />
+</animated-vector>
diff --git a/core/res/res/drawable/vector_drawable_progress_indeterminate_horizontal.xml b/core/res/res/drawable/vector_drawable_progress_indeterminate_horizontal.xml
new file mode 100644
index 0000000..0cc7202
--- /dev/null
+++ b/core/res/res/drawable/vector_drawable_progress_indeterminate_horizontal.xml
@@ -0,0 +1,60 @@
+<!--
+ 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.
+-->
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="4dp"
+ android:viewportHeight="4"
+ android:viewportWidth="360"
+ android:width="360dp" >
+
+ <group
+ android:name="linear_indeterminate"
+ android:translateX="180.0"
+ android:translateY="0.0" >
+ <group
+ android:name="path1"
+ android:scaleX="0.1"
+ android:translateX="-522.59" >
+ <path
+ android:name="rect1"
+ android:fillColor="?attr/colorControlActivated"
+ android:pathData="m 0 1.6 l 288 0 l 0 0.8 l -288 0 z" />
+ </group>
+ <group
+ android:name="path2"
+ android:scaleX="0.1"
+ android:translateX="-197.6" >
+ <path
+ android:name="rect2"
+ android:fillColor="?attr/colorControlActivated"
+ android:pathData="m 0 1.6 l 288 0 l 0 0.8 l -288 0 z" />
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e94a046..2311e67 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4865,6 +4865,15 @@
<!-- TV content rating system strings for CO TV -->
<!-- TV content rating system strings for DE TV -->
+ <string name="display_name_detv" translatable="false">DE-TV</string>
+ <string name="display_name_detv_all" translatable="false">ab 0 Jahren</string>
+ <string name="display_name_detv_12" translatable="false">ab 12 Jahren</string>
+ <string name="display_name_detv_16" translatable="false">ab 16 Jahren</string>
+ <string name="display_name_detv_18" translatable="false">ab 18 Jahren</string>
+ <string name="description_detv_all">Die nachfolgende Sendung ist für alle alters geeignet.</string>
+ <string name="description_detv_12">Die nachfolgende Sendung ist für Zuschauer unter 12 Jahren nicht geeignet.</string>
+ <string name="description_detv_16">Die nachfolgende Sendung ist für Zuschauer unter 16 Jahren nicht geeignet.</string>
+ <string name="description_detv_18">Die nachfolgende Sendung ist für Zuschauer unter 18 Jahren nicht geeignet.</string>
<!-- TV content rating system strings for DK TV -->
@@ -4873,6 +4882,17 @@
<!-- TV content rating system strings for FI TV -->
<!-- TV content rating system strings for FR TV -->
+ <string name="display_name_frtv" translatable="false">FR-TV</string>
+ <string name="display_name_frtv_all" translatable="false">Les programmes tous publics</string>
+ <string name="display_name_frtv_10" translatable="false">Déconseillé aux -10 ans</string>
+ <string name="display_name_frtv_12" translatable="false">Déconseillé aux -12 ans</string>
+ <string name="display_name_frtv_16" translatable="false">Déconseillé aux -16 ans</string>
+ <string name="display_name_frtv_18" translatable="false">Déconseillé aux -18 ans</string>
+ <string name="description_frtv_all">Les programmes tous publics</string>
+ <string name="description_frtv_10">Programmes comportant certaines scènes susceptibles de heurter les -10 ans.</string>
+ <string name="description_frtv_12">Programmes pouvant troubler les -12 ans, notamment lorsque le scénario recourt de façon systématique et répétée à la violence physique ou psychologique.</string>
+ <string name="description_frtv_16">Programmes à caractère érotique ou de grande violence, susceptibles de nuire à l’épanouissement physique, mental ou moral des -16 ans.</string>
+ <string name="description_frtv_18">Programmes pornographiques ou de très grande violence, réservés à un public adulte averti et susceptibles de nuire à l’épanouissement physique, mental ou moral des -18 ans.</string>
<!-- TV content rating system strings for GR TV -->
@@ -4914,6 +4934,29 @@
<!-- TV content rating system strings for MY TV -->
<!-- TV content rating system strings for NL TV -->
+ <string name="display_name_nltv" translatable="false">NL-TV</string>
+ <string name="display_name_nltv_v" translatable="false">Geweld</string>
+ <string name="display_name_nltv_f" translatable="false">Angst</string>
+ <string name="display_name_nltv_s" translatable="false">Seks</string>
+ <string name="display_name_nltv_d" translatable="false">Discriminatie</string>
+ <string name="display_name_nltv_da" translatable="false">Drugs- en/of alcoholmisbruik</string>
+ <string name="display_name_nltv_l" translatable="false">Grof taalgebruik</string>
+ <string name="display_name_nltv_al" translatable="false">Alle leeftijden</string>
+ <string name="display_name_nltv_6" translatable="false">Let op met kinderen tot 6 jaar</string>
+ <string name="display_name_nltv_9" translatable="false">Let op met kinderen tot 9 jaar</string>
+ <string name="display_name_nltv_12" translatable="false">Let op met kinderen tot 12 jaar</string>
+ <string name="display_name_nltv_16" translatable="false">Let op met kinderen tot 16 jaar</string>
+ <string name="description_nltv_v">Geweld</string>
+ <string name="description_nltv_f">Angst</string>
+ <string name="description_nltv_s">Seks</string>
+ <string name="description_nltv_d">Discriminatie</string>
+ <string name="description_nltv_da">Drugs- en/of alcoholmisbruik</string>
+ <string name="description_nltv_l">Grof taalgebruik</string>
+ <string name="description_nltv_al">De leeftijdscategorie Alle Leeftijden geeft aan dat een mediaproductie geen schadelijke elementen bevat.</string>
+ <string name="description_nltv_6">Mogelijk schadelijk voor kinderen onder de 6 jaar.</string>
+ <string name="description_nltv_9">Mogelijk schadelijk voor kinderen onder de 9 jaar.</string>
+ <string name="description_nltv_12">Mogelijk schadelijk voor kinderen onder de 12 jaar.</string>
+ <string name="description_nltv_16">Mogelijk schadelijk voor kinderen onder de 16 jaar.</string>
<!-- TV content rating system strings for NZ TV -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 9ee377f..2dc0438 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -652,7 +652,7 @@
<style name="Widget.Material.ProgressBar.Horizontal" parent="Widget.ProgressBar.Horizontal">
<item name="progressDrawable">@drawable/progress_horizontal_material</item>
- <item name="indeterminateDrawable">@drawable/progress_indeterminate_horizontal_holo</item>
+ <item name="indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material</item>
<item name="minHeight">16dip</item>
<item name="maxHeight">16dip</item>
</style>
diff --git a/core/res/res/xml/tv_content_rating_systems.xml b/core/res/res/xml/tv_content_rating_systems.xml
index 57fd2ad..2df091d 100644
--- a/core/res/res/xml/tv_content_rating_systems.xml
+++ b/core/res/res/xml/tv_content_rating_systems.xml
@@ -36,6 +36,32 @@
<!-- TV content rating system for CO TV -->
<!-- TV content rating system for DE TV -->
+ <rating-system-definition id="DE_TV"
+ displayName="@string/display_name_detv"
+ country="DE">
+ <rating-definition id="DE_TV_ALL"
+ displayName="@string/display_name_detv_all"
+ description="@string/description_detv_all"
+ ageHint="0" />
+ <rating-definition id="DE_TV_12"
+ displayName="@string/display_name_detv_12"
+ description="@string/description_detv_12"
+ ageHint="12" />
+ <rating-definition id="DE_TV_16"
+ displayName="@string/display_name_detv_16"
+ description="@string/description_detv_16"
+ ageHint="16" />
+ <rating-definition id="DE_TV_18"
+ displayName="@string/display_name_detv_18"
+ description="@string/description_detv_18"
+ ageHint="18" />
+ <order>
+ <rating id="DE_TV_ALL" />
+ <rating id="DE_TV_12" />
+ <rating id="DE_TV_16" />
+ <rating id="DE_TV_18" />
+ </order>
+ </rating-system-definition>
<!-- TV content rating system for DK TV -->
@@ -44,6 +70,41 @@
<!-- TV content rating system for FI TV -->
<!-- TV content rating system for FR TV -->
+ <rating-system-definition id="FR_TV"
+ displayName="@string/display_name_frtv"
+ country="FR">
+ <rating-definition id="FR_TV_ALL"
+ displayName="@string/display_name_frtv_all"
+ description="@string/description_frtv_all"
+ ageHint="0" />
+ <rating-definition id="FR_TV_10"
+ displayName="@string/display_name_frtv_10"
+ description="@string/description_frtv_10"
+ ageHint="10">
+ </rating-definition>
+ <rating-definition id="FR_TV_12"
+ displayName="@string/display_name_frtv_12"
+ description="@string/description_frtv_12"
+ ageHint="12">
+ </rating-definition>
+ <rating-definition id="FR_TV_16"
+ displayName="@string/display_name_frtv_16"
+ description="@string/description_frtv_16"
+ ageHint="16">
+ </rating-definition>
+ <rating-definition id="FR_TV_18"
+ displayName="@string/display_name_frtv_18"
+ description="@string/description_frtv_18"
+ ageHint="18">
+ </rating-definition>
+ <order>
+ <rating id="FR_TV_ALL" />
+ <rating id="FR_TV_10" />
+ <rating id="FR_TV_12" />
+ <rating id="FR_TV_16" />
+ <rating id="FR_TV_18" />
+ </order>
+ </rating-system-definition>
<!-- TV content rating system for GR TV -->
@@ -105,6 +166,92 @@
<!-- TV content rating system for MY TV -->
<!-- TV content rating system for NL TV -->
+ <rating-system-definition id="NL_TV"
+ displayName="@string/display_name_nltv"
+ country="NL">
+ <sub-rating-definition id="NL_TV_V"
+ displayName="@string/display_name_nltv_v"
+ description="@string/description_nltv_v" />
+ <sub-rating-definition id="NL_TV_F"
+ displayName="@string/display_name_nltv_f"
+ description="@string/description_nltv_f" />
+ <sub-rating-definition id="NL_TV_S"
+ displayName="@string/display_name_nltv_s"
+ description="@string/description_nltv_s" />
+ <sub-rating-definition id="NL_TV_D"
+ displayName="@string/display_name_nltv_d"
+ description="@string/description_nltv_d" />
+ <sub-rating-definition id="NL_TV_DA"
+ displayName="@string/display_name_nltv_da"
+ description="@string/description_nltv_da" />
+ <sub-rating-definition id="NL_TV_L"
+ displayName="@string/display_name_nltv_l"
+ description="@string/description_nltv_l" />
+
+ <rating-definition id="NL_TV_AL"
+ displayName="@string/display_name_nltv_al"
+ description="@string/description_nltv_al"
+ ageHint="0">
+ <sub-rating id="NL_TV_V" />
+ <sub-rating id="NL_TV_F" />
+ <sub-rating id="NL_TV_S" />
+ <sub-rating id="NL_TV_D" />
+ <sub-rating id="NL_TV_DA" />
+ <sub-rating id="NL_TV_L" />
+ </rating-definition>
+ <rating-definition id="NL_TV_6"
+ displayName="@string/display_name_nltv_6"
+ description="@string/description_nltv_6"
+ ageHint="6">
+ <sub-rating id="NL_TV_V" />
+ <sub-rating id="NL_TV_F" />
+ <sub-rating id="NL_TV_S" />
+ <sub-rating id="NL_TV_D" />
+ <sub-rating id="NL_TV_DA" />
+ <sub-rating id="NL_TV_L" />
+ </rating-definition>
+ <rating-definition id="NL_TV_9"
+ displayName="@string/display_name_nltv_9"
+ description="@string/description_nltv_9"
+ ageHint="9">
+ <sub-rating id="NL_TV_V" />
+ <sub-rating id="NL_TV_F" />
+ <sub-rating id="NL_TV_S" />
+ <sub-rating id="NL_TV_D" />
+ <sub-rating id="NL_TV_DA" />
+ <sub-rating id="NL_TV_L" />
+ </rating-definition>
+ <rating-definition id="NL_TV_12"
+ displayName="@string/display_name_nltv_12"
+ description="@string/description_nltv_12"
+ ageHint="12">
+ <sub-rating id="NL_TV_V" />
+ <sub-rating id="NL_TV_F" />
+ <sub-rating id="NL_TV_S" />
+ <sub-rating id="NL_TV_D" />
+ <sub-rating id="NL_TV_DA" />
+ <sub-rating id="NL_TV_L" />
+ </rating-definition>
+ <rating-definition id="NL_TV_16"
+ displayName="@string/display_name_nltv_16"
+ description="@string/description_nltv_16"
+ ageHint="16">
+ <sub-rating id="NL_TV_V" />
+ <sub-rating id="NL_TV_F" />
+ <sub-rating id="NL_TV_S" />
+ <sub-rating id="NL_TV_D" />
+ <sub-rating id="NL_TV_DA" />
+ <sub-rating id="NL_TV_L" />
+ </rating-definition>
+
+ <order>
+ <rating id="NL_TV_AL" />
+ <rating id="NL_TV_6" />
+ <rating id="NL_TV_9" />
+ <rating id="NL_TV_12" />
+ <rating id="NL_TV_16" />
+ </order>
+ </rating-system-definition>
<!-- TV content rating system for NZ TV -->
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 4b00e22..4d0bb75 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Trace;
import android.util.DisplayMetrics;
import java.io.OutputStream;
@@ -1004,8 +1005,11 @@
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
- return nativeCompress(mNativeBitmap, format.nativeInt, quality,
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
+ boolean result = nativeCompress(mNativeBitmap, format.nativeInt, quality,
stream, new byte[WORKING_COMPRESS_STORAGE]);
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ return result;
}
/**
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index f451690..3ef2a71 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -334,8 +334,10 @@
const char* gFS_Main_FragColor_HasRoundRectClip =
" mediump vec2 fragToLT = roundRectInnerRectLTRB.xy - roundRectPos;\n"
" mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTRB.zw;\n"
- " mediump vec2 dist = max(max(fragToLT, fragFromRB), vec2(0.0, 0.0));\n"
- " mediump float linearDist = roundRectRadius - length(dist);\n"
+
+ // divide + multiply by 128 to avoid falling out of range in length() function
+ " mediump vec2 dist = max(max(fragToLT, fragFromRB), vec2(0.0, 0.0)) / 128.0;\n"
+ " mediump float linearDist = roundRectRadius - (length(dist) * 128.0);\n"
" gl_FragColor *= clamp(linearDist, 0.0, 1.0);\n";
const char* gFS_Main_DebugHighlight =
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index fa1b21d..237d500 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -184,7 +184,9 @@
return;
}
- if (!dirty.isEmpty()) {
+
+ if (dirty.intersect(0, 0, getWidth(), getHeight())) {
+ dirty.roundOut();
mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
}
// This is not inside the above if because we may have called
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 8d99d6a1..d93d81b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -249,6 +249,20 @@
private AudioFormat(int ignoredArgument) {
}
+ /**
+ * Constructor used by the JNI
+ */
+ // Update sound trigger JNI in core/jni/android_hardware_SoundTrigger.cpp when modifying this
+ // constructor
+ private AudioFormat(int encoding, int sampleRate, int channelMask) {
+ mEncoding = encoding;
+ mSampleRate = sampleRate;
+ mChannelMask = channelMask;
+ mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING |
+ AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE |
+ AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+ }
+
/** @hide */
public final static int AUDIO_FORMAT_HAS_PROPERTY_NONE = 0x0;
/** @hide */
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ac63ea6..bd50142 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -834,6 +834,7 @@
/**
* Get the stream type whose volume is driving the UI sounds volume.
* UI sounds are screen lock/unlock, camera shutter, key clicks...
+ * It is assumed that this stream type is also tied to ringer mode changes.
* @hide
*/
public int getMasterStreamType() {
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index e1c6e75..705d9c0 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -291,14 +291,14 @@
};
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
- AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM
AudioSystem.STREAM_RING, // STREAM_RING
AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
AudioSystem.STREAM_ALARM, // STREAM_ALARM
AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
- AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
- AudioSystem.STREAM_MUSIC, // STREAM_DTMF
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
+ AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC // STREAM_TTS
};
private int[] mStreamVolumeAlias;
@@ -1572,15 +1572,7 @@
/** @see AudioManager#getMasterStreamType() */
public int getMasterStreamType() {
- switch (mPlatformType) {
- case PLATFORM_VOICE:
- return AudioSystem.STREAM_RING;
- case PLATFORM_TELEVISION:
- return AudioSystem.STREAM_MUSIC;
- default:
- break;
- }
- return AudioSystem.STREAM_NOTIFICATION;
+ return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
}
/** @see AudioManager#setMicrophoneMute(boolean) */
@@ -4340,7 +4332,7 @@
AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ALL_USB;
+ AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE;
// must be called before removing the device from mConnectedDevices
private int checkSendBecomingNoisyIntent(int device, int state) {
@@ -4386,7 +4378,9 @@
connType = AudioRoutesInfo.MAIN_HEADSET;
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 1);
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) {
+ } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
+ device == AudioSystem.DEVICE_OUT_LINE) {
+ /*do apps care about line-out vs headphones?*/
connType = AudioRoutesInfo.MAIN_HEADPHONES;
intent.setAction(Intent.ACTION_HEADSET_PLUG);
intent.putExtra("microphone", 0);
@@ -4429,7 +4423,8 @@
{
synchronized (mConnectedDevices) {
if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
- (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) {
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE))) {
setBluetoothA2dpOnInt(true);
}
boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) ||
@@ -4438,7 +4433,8 @@
handleDeviceConnection((state == 1), device, (isUsb ? name : ""));
if (state != 0) {
if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
- (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) {
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE)) {
setBluetoothA2dpOnInt(false);
}
if ((device & mSafeMediaVolumeDevices) != 0) {
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 3f7ebce..bbf6a3b 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -15,10 +15,14 @@
*/
package android.media;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.text.format.Time;
import android.util.ArrayMap;
import android.util.Log;
@@ -115,7 +119,7 @@
public static final String METADATA_KEY_ART = "android.media.metadata.ART";
/**
- * The artwork for the media as a Uri style String.
+ * The artwork for the media as a Uri.
*/
public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
@@ -126,8 +130,7 @@
public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
/**
- * The artwork for the album of the media's original source as a Uri style
- * String.
+ * The artwork for the album of the media's original source as a Uri.
*/
public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
@@ -145,36 +148,104 @@
*/
public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ /**
+ * A title that is suitable for display to the user. This will generally be
+ * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
+ * When displaying media described by this metadata this should be preferred
+ * if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+
+ /**
+ * A subtitle that is suitable for display to the user. When displaying a
+ * second line for media described by this metadata this should be preferred
+ * to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_SUBTITLE
+ = "android.media.metadata.DISPLAY_SUBTITLE";
+
+ /**
+ * A description that is suitable for display to the user. When displaying
+ * more information for media described by this metadata this should be
+ * preferred to other fields if present.
+ */
+ public static final String METADATA_KEY_DISPLAY_DESCRIPTION
+ = "android.media.metadata.DISPLAY_DESCRIPTION";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying an icon for media described by this metadata this should be
+ * preferred to other fields if present. This must be a {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON
+ = "android.media.metadata.DISPLAY_ICON";
+
+ /**
+ * An icon or thumbnail that is suitable for display to the user. When
+ * displaying more information for media described by this metadata the
+ * display description should be preferred to other fields when present.
+ */
+ public static final String METADATA_KEY_DISPLAY_ICON_URI
+ = "android.media.metadata.DISPLAY_ICON_URI";
+
+ private static final String[] PREFERRED_DESCRIPTION_ORDER = {
+ METADATA_KEY_TITLE,
+ METADATA_KEY_ALBUM,
+ METADATA_KEY_ARTIST,
+ METADATA_KEY_ALBUM_ARTIST,
+ METADATA_KEY_WRITER,
+ METADATA_KEY_AUTHOR,
+ METADATA_KEY_COMPOSER
+ };
+
+ private static final String[] PREFERRED_BITMAP_ORDER = {
+ METADATA_KEY_DISPLAY_ICON,
+ METADATA_KEY_ART,
+ METADATA_KEY_ALBUM_ART
+ };
+
+ private static final String[] PREFERRED_URI_ORDER = {
+ METADATA_KEY_DISPLAY_ICON_URI,
+ METADATA_KEY_ART_URI,
+ METADATA_KEY_ALBUM_ART_URI
+ };
+
private static final int METADATA_TYPE_INVALID = -1;
private static final int METADATA_TYPE_LONG = 0;
- private static final int METADATA_TYPE_STRING = 1;
+ private static final int METADATA_TYPE_TEXT = 1;
private static final int METADATA_TYPE_BITMAP = 2;
private static final int METADATA_TYPE_RATING = 3;
+ private static final int METADATA_TYPE_URI = 4;
private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
static {
METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
- METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_URI);
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
- METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_URI);
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_URI);
}
private static final SparseArray<String> EDITOR_KEY_MAPPING;
@@ -207,6 +278,7 @@
}
private final Bundle mBundle;
+ private Description mDescription;
private MediaMetadata(Bundle bundle) {
mBundle = new Bundle(bundle);
@@ -232,10 +304,27 @@
* associated with the key.
*
* @param key The key the value is stored under
+ * @return a CharSequence value, or null
+ */
+ public CharSequence getText(String key) {
+ return mBundle.getCharSequence(key);
+ }
+
+ /**
+ * Returns the text value associated with the given key as a String, or null
+ * if no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key. This is equivalent to
+ * calling {@link #getText getText().toString()} if the value is not null.
+ *
+ * @param key The key the value is stored under
* @return a String value, or null
*/
public String getString(String key) {
- return mBundle.getString(key);
+ CharSequence text = getText(key);
+ if (text != null) {
+ return text.toString();
+ }
+ return null;
}
/**
@@ -250,8 +339,8 @@
}
/**
- * Return a {@link Rating} for the given key or null if no rating exists for
- * the given key.
+ * Returns a {@link Rating} for the given key or null if no rating exists
+ * for the given key.
*
* @param key The key the value is stored under
* @return A {@link Rating} or null
@@ -268,8 +357,8 @@
}
/**
- * Return a {@link Bitmap} for the given key or null if no bitmap exists for
- * the given key.
+ * Returns a {@link Bitmap} for the given key or null if no bitmap exists
+ * for the given key.
*
* @param key The key the value is stored under
* @return A {@link Bitmap} or null
@@ -296,7 +385,7 @@
}
/**
- * Get the number of fields in this metadata.
+ * Returns the number of fields in this metadata.
*
* @return The number of fields in the metadata.
*/
@@ -314,6 +403,64 @@
}
/**
+ * Returns a simple description of this metadata for display purposes.
+ *
+ * @return A simple description of this metadata.
+ * @hide
+ */
+ public @NonNull Description getDescription() {
+ if (mDescription != null) {
+ return mDescription;
+ }
+
+ CharSequence[] text = new CharSequence[3];
+ Bitmap icon = null;
+ Uri iconUri = null;
+
+ // First handle the case where display data is set already
+ CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
+ if (!TextUtils.isEmpty(displayText)) {
+ // If they have a display title use only display data, otherwise use
+ // our best bets
+ text[0] = displayText;
+ text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
+ text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
+ } else {
+ // Use whatever fields we can
+ int textIndex = 0;
+ int keyIndex = 0;
+ while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
+ CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
+ if (!TextUtils.isEmpty(next)) {
+ // Fill in the next empty bit of text
+ text[textIndex++] = next;
+ }
+ }
+ }
+
+ // Get the best art bitmap we can find
+ for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
+ Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
+ if (next != null) {
+ icon = next;
+ break;
+ }
+ }
+
+ // Get the best Uri we can find
+ for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
+ String next = getString(PREFERRED_URI_ORDER[i]);
+ if (!TextUtils.isEmpty(next)) {
+ iconUri = Uri.parse(next);
+ break;
+ }
+ }
+
+ mDescription = new Description(text[0], text[1], text[2], icon, iconUri);
+ return mDescription;
+ }
+
+ /**
* Helper for getting the String key used by {@link MediaMetadata} from the
* integer key that {@link MediaMetadataEditor} uses.
*
@@ -365,6 +512,43 @@
}
/**
+ * Put a CharSequence value into the metadata. Custom keys may be used,
+ * but if the METADATA_KEYs defined in this class are used they may only
+ * be one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_AUTHOR}</li>
+ * <li>{@link #METADATA_KEY_WRITER}</li>
+ * <li>{@link #METADATA_KEY_COMPOSER}</li>
+ * <li>{@link #METADATA_KEY_DATE}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The CharSequence value to store
+ * @return The Builder to allow chaining
+ */
+ public Builder putText(String key, CharSequence value) {
+ if (METADATA_KEYS_TYPE.containsKey(key)) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a CharSequence");
+ }
+ }
+ mBundle.putCharSequence(key, value);
+ return this;
+ }
+
+ /**
* Put a String value into the metadata. Custom keys may be used, but if
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
@@ -380,6 +564,10 @@
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
* <li>{@link #METADATA_KEY_ART_URI}</li>
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
* </ul>
*
* @param key The key for referencing this value
@@ -388,12 +576,12 @@
*/
public Builder putString(String key, String value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
- if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_STRING) {
+ if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a String");
}
}
- mBundle.putString(key, value);
+ mBundle.putCharSequence(key, value);
return this;
}
@@ -410,7 +598,7 @@
* </ul>
*
* @param key The key for referencing this value
- * @param value The String value to store
+ * @param value The long value to store
* @return The Builder to allow chaining
*/
public Builder putLong(String key, long value) {
@@ -434,7 +622,7 @@
* </ul>
*
* @param key The key for referencing this value
- * @param value The String value to store
+ * @param value The Rating value to store
* @return The Builder to allow chaining
*/
public Builder putRating(String key, Rating value) {
@@ -455,6 +643,7 @@
* <ul>
* <li>{@link #METADATA_KEY_ART}</li>
* <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+ * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
* </ul>
*
* @param key The key for referencing this value
@@ -482,4 +671,46 @@
}
}
+ /**
+ * A simple form of the metadata that can be used for display.
+ *
+ * @hide
+ */
+ public final class Description {
+ /**
+ * A primary title suitable for display or null.
+ */
+ public final CharSequence title;
+ /**
+ * A subtitle suitable for display or null.
+ */
+ public final CharSequence subtitle;
+ /**
+ * A description suitable for display or null.
+ */
+ public final CharSequence description;
+ /**
+ * A bitmap icon suitable for display or null.
+ */
+ public final Bitmap icon;
+ /**
+ * A Uri for an icon suitable for display or null.
+ */
+ public final Uri iconUri;
+
+ private Description(CharSequence title, CharSequence subtitle, CharSequence description,
+ Bitmap icon, Uri iconUri) {
+ this.title = title;
+ this.subtitle = subtitle;
+ this.description = description;
+ this.icon = icon;
+ this.iconUri = iconUri;
+ }
+
+ @Override
+ public String toString() {
+ return title + ", " + subtitle + ", " + description;
+ }
+ }
+
}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index db6315a..96c66c5 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -473,7 +473,7 @@
String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
// But just in case, don't add things we don't understand
if (metadataKey != null) {
- mMetadataBuilder.putString(metadataKey, value);
+ mMetadataBuilder.putText(metadataKey, value);
}
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 2c39afa..37c7553 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -33,8 +33,8 @@
void onSessionReleased(int seq);
void onSessionEvent(in String name, in Bundle args, int seq);
void onChannelRetuned(in Uri channelUri, int seq);
- void onTrackInfoChanged(in List<TvTrackInfo> tracks, int seq);
- void onTrackSelectionChanged(in List<TvTrackInfo> selectedTracks, int seq);
+ void onTracksChanged(in List<TvTrackInfo> tracks, int seq);
+ void onTrackSelected(int type, in String trackId, int seq);
void onVideoAvailable(int seq);
void onVideoUnavailable(int reason, int seq);
void onContentAllowed(int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a2b7d6b..d5719c8 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -60,8 +60,7 @@
void setVolume(in IBinder sessionToken, float volume, int userId);
void tune(in IBinder sessionToken, in Uri channelUri, in Bundle params, int userId);
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
- void selectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
- void unselectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
+ void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
void sendAppPrivateCommand(in IBinder sessionToken, in String action, in Bundle data,
int userId);
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index 26a0d20..df648e7 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -27,5 +27,4 @@
void addHardwareTvInput(in int deviceID, in TvInputInfo inputInfo);
void addHdmiCecTvInput(in int logicalAddress, in TvInputInfo inputInfo);
void removeTvInput(in String inputId);
- void setWrappedInputId(in String inputId, in String wrappedInputId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 9a0be25..99fb911 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -37,8 +37,7 @@
void setVolume(float volume);
void tune(in Uri channelUri, in Bundle params);
void setCaptionEnabled(boolean enabled);
- void selectTrack(in TvTrackInfo track);
- void unselectTrack(in TvTrackInfo track);
+ void selectTrack(int type, in String trackId);
void appPrivateCommand(in String action, in Bundle data);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 3773987..8a918e1 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -27,11 +27,11 @@
* @hide
*/
oneway interface ITvInputSessionCallback {
- void onSessionCreated(ITvInputSession session);
+ void onSessionCreated(ITvInputSession session, in IBinder hardwareSessionToken);
void onSessionEvent(in String name, in Bundle args);
void onChannelRetuned(in Uri channelUri);
- void onTrackInfoChanged(in List<TvTrackInfo> tracks);
- void onTrackSelectionChanged(in List<TvTrackInfo> selectedTracks);
+ void onTracksChanged(in List<TvTrackInfo> tracks);
+ void onTrackSelected(int type, in String trackId);
void onVideoAvailable();
void onVideoUnavailable(int reason);
void onContentAllowed();
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index a809da9..5022cc1 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -49,12 +49,11 @@
private static final int DO_TUNE = 6;
private static final int DO_SET_CAPTION_ENABLED = 7;
private static final int DO_SELECT_TRACK = 8;
- private static final int DO_UNSELECT_TRACK = 9;
- private static final int DO_APP_PRIVATE_COMMAND = 10;
- private static final int DO_CREATE_OVERLAY_VIEW = 11;
- private static final int DO_RELAYOUT_OVERLAY_VIEW = 12;
- private static final int DO_REMOVE_OVERLAY_VIEW = 13;
- private static final int DO_REQUEST_UNBLOCK_CONTENT = 14;
+ private static final int DO_APP_PRIVATE_COMMAND = 9;
+ private static final int DO_CREATE_OVERLAY_VIEW = 10;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 11;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 12;
+ private static final int DO_REQUEST_UNBLOCK_CONTENT = 13;
private final HandlerCaller mCaller;
@@ -121,11 +120,9 @@
return;
}
case DO_SELECT_TRACK: {
- mTvInputSessionImpl.selectTrack((TvTrackInfo) msg.obj);
- return;
- }
- case DO_UNSELECT_TRACK: {
- mTvInputSessionImpl.unselectTrack((TvTrackInfo) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.selectTrack((Integer) args.arg1, (String) args.arg2);
+ args.recycle();
return;
}
case DO_APP_PRIVATE_COMMAND: {
@@ -196,13 +193,8 @@
}
@Override
- public void selectTrack(TvTrackInfo track) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SELECT_TRACK, track));
- }
-
- @Override
- public void unselectTrack(TvTrackInfo track) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UNSELECT_TRACK, track));
+ public void selectTrack(int type, String trackId) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
}
@Override
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index a1711e5..fc3ff81 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -124,10 +124,10 @@
* <td>CO_TV</td>
* <td></td>
* </tr-->
- * <!--tr>
+ * <tr>
* <td>DE_TV</td>
- * <td></td>
- * </tr-->
+ * <td>The Germany television rating system</td>
+ * </tr>
* <!--tr>
* <td>DK_TV</td>
* <td></td>
@@ -140,10 +140,10 @@
* <td>FI_TV</td>
* <td></td>
* </tr-->
- * <!--tr>
+ * <tr>
* <td>FR_TV</td>
- * <td></td>
- * </tr-->
+ * <td>The content rating system in French</td>
+ * </tr>
* <!--tr>
* <td>GR_TV</td>
* <td></td>
@@ -200,10 +200,10 @@
* <td>MY_TV</td>
* <td></td>
* </tr-->
- * <!--tr>
+ * <tr>
* <td>NL_TV</td>
- * <td></td>
- * </tr-->
+ * <td>The television rating system in the Netherlands</td>
+ * </tr>
* <!--tr>
* <td>NZ_TV</td>
* <td></td>
@@ -316,10 +316,31 @@
* <td>CO_TV_ALL</td>
* <td></td>
* </tr-->
- * <!--tr>
+ * <tr>
* <td>DE_TV_ALL</td>
- * <td></td>
- * </tr-->
+ * <td>Without restriction. There are time schedules and certain age groups which have to be
+ * considered. {@code DE_TV_ALL} is scheduled in daytime (6:00AM – 8:00PM). However, cinema
+ * films classified with "12" may be shown during the daytime, if they are not considered
+ * harmful to younger children.</td>
+ * </tr>
+ * <tr>
+ * <td>DE_TV_12</td>
+ * <td>Suitable for 12 years and above. There are time schedules and certain age groups
+ * which have to be considered. {@code DE_TV_12} is scheduled in primetime (from 8:00PM
+ * – 10.00 p.m.).</td>
+ * </tr>
+ * <tr>
+ * <td>DE_TV_16</td>
+ * <td>Suitable for 16 years and above. There are time schedules and certain age groups
+ * which have to be considered. {@code DE_TV_16} is scheduled in late evening (from 10:00PM
+ * - 11:00PM). </td>
+ * </tr>
+ * <tr>
+ * <td>DE_TV_18</td>
+ * <td>Suitable for 18 years and above. There are time schedules and certain age groups
+ * which have to be considered. {@code DE_TV_18} is scheduled in late night (from 11:00PM
+ * - 6:00AM). </td>
+ * </tr>
* <!--tr>
* <td>DK_TV_ALL</td>
* <td></td>
@@ -332,10 +353,36 @@
* <td>FI_TV_ALL</td>
* <td></td>
* </tr-->
- * <!--tr>
+ * <tr>
* <td>FR_TV_ALL</td>
- * <td></td>
- * </tr-->
+ * <td>A rating string for {@code FR_TV}. According to CSA in France, if no rating appears,
+ * the program is most likely appropriate for all ages. In Android TV, however,
+ * {@code RATING_FR_ALL} is used for handling that case.</td>
+ * </tr>
+ * <tr>
+ * <td>FR_TV_10</td>
+ * <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ * recommended for children under 10.</td>
+ * </tr>
+ * <tr>
+ * <td>FR_TV_12</td>
+ * <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ * recommended for children under 12. Programs rated this are not allowed to air before
+ * 10:00 pm (Some channels and programs are subject to exception). </td>
+ * </tr>
+ * <tr>
+ * <td>FR_TV_16</td>
+ * <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ * recommended for children under 16. Programs rated this are not allowed to air before
+ * 10:30 pm (Some channels and programs are subject to exception). </td>
+ * </tr>
+ * <tr>
+ * <td>FR_TV_18</td>
+ * <td>A rating string for {@code FR_TV}. This rating is for programs that are not
+ * recommended for persons under 18. Programs rated this are allowed between midnight and
+ * 5 am and only on some channels. The access to these programs is locked by a personal
+ * password.</td>
+ * </tr>
* <!--tr>
* <td>GR_TV_ALL</td>
* <td></td>
@@ -416,10 +463,31 @@
* <td>MY_TV_ALL</td>
* <td></td>
* </tr-->
- * <!--tr>
- * <td>NL_TV_ALL</td>
- * <td></td>
- * </tr-->
+ * <tr>
+ * <td>NL_TV_AL</td>
+ * <td>A rating string for {@code NL_TV}. This rating is for programs that are appropriate
+ * for all ages.</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_6</td>
+ * <td>A rating string for {@code NL_TV}. This rating is for programs that require parental
+ * advisory for children under 6.</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_9</td>
+ * <td>A rating string for {@code NL_TV}. This rating is for programs that require parental
+ * advisory for children under 9.</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_12</td>
+ * <td>A rating string for {@code NL_TV}. This rating is for programs that require parental
+ * advisory for children under 12.</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_16</td>
+ * <td>A rating string for {@code NL_TV}. This rating is for programs that require parental
+ * advisory for children under 16.</td>
+ * </tr>
* <!--tr>
* <td>NZ_TV_ALL</td>
* <td></td>
@@ -582,10 +650,6 @@
* <td></td>
* </tr-->
* <!--tr>
- * <td>DE_TV_</td>
- * <td></td>
- * </tr-->
- * <!--tr>
* <td>DK_TV_</td>
* <td></td>
* </tr-->
@@ -598,10 +662,6 @@
* <td></td>
* </tr-->
* <!--tr>
- * <td>FR_TV_</td>
- * <td></td>
- * </tr-->
- * <!--tr>
* <td>GR_TV_</td>
* <td></td>
* </tr-->
@@ -653,10 +713,30 @@
* <td>MY_TV_</td>
* <td></td>
* </tr-->
- * <!--tr>
- * <td>NL_TV_</td>
- * <td></td>
- * </tr-->
+ * <tr>
+ * <td>NL_TV_V</td>
+ * <td>Violence</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_F</td>
+ * <td>Fear</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_S</td>
+ * <td>Sex</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_D</td>
+ * <td>Discrimination</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_DA</td>
+ * <td>Drugs- and alcoholabuse</td>
+ * </tr>
+ * <tr>
+ * <td>NL_TV_L</td>
+ * <td>Coarse Language</td>
+ * </tr>
* <!--tr>
* <td>NZ_TV_</td>
* <td></td>
@@ -755,7 +835,6 @@
private static final String DELIMITER = "/";
private final String mDomain;
- private final String mCountryCode;
private final String mRatingSystem;
private final String mRating;
private final String[] mSubRatings;
@@ -764,21 +843,20 @@
* Creates a TvContentRating object.
*
* @param domain The domain name.
- * @param countryCode The country code in ISO 3166-2 format or {@code null}.
* @param ratingSystem The rating system id.
* @param rating The content rating string.
* @param subRatings The string array of sub-ratings.
* @return A TvContentRating object, or null if creation failed.
*/
- public static TvContentRating createRating(String domain, String countryCode,
- String ratingSystem, String rating, String... subRatings) {
+ public static TvContentRating createRating(String domain, String ratingSystem,
+ String rating, String... subRatings) {
if (TextUtils.isEmpty(domain)) {
throw new IllegalArgumentException("domain cannot be empty");
}
if (TextUtils.isEmpty(rating)) {
throw new IllegalArgumentException("rating cannot be empty");
}
- return new TvContentRating(domain, countryCode, ratingSystem, rating, subRatings);
+ return new TvContentRating(domain, ratingSystem, rating, subRatings);
}
/**
@@ -786,7 +864,7 @@
* {@link #flattenToString}.
*
* @param ratingString The String that was returned by flattenToString().
- * @return a new TvContentRating containing the domain, countryCode, rating system, rating and
+ * @return a new TvContentRating containing the domain, rating system, rating and
* sub-ratings information was encoded in {@code ratingString}.
* @see #flattenToString
*/
@@ -795,27 +873,28 @@
throw new IllegalArgumentException("ratingString cannot be empty");
}
String[] strs = ratingString.split(DELIMITER);
- if (strs.length < 4) {
+ if (strs.length < 3) {
throw new IllegalArgumentException("Invalid rating string: " + ratingString);
}
- if (strs.length > 4) {
- String[] subRatings = new String[strs.length - 4];
- System.arraycopy(strs, 4, subRatings, 0, subRatings.length);
- return new TvContentRating(strs[0], strs[1], strs[2], strs[3], subRatings);
+ if (strs.length > 3) {
+ String[] subRatings = new String[strs.length - 3];
+ System.arraycopy(strs, 3, subRatings, 0, subRatings.length);
+ return new TvContentRating(strs[0], strs[1], strs[2], subRatings);
}
- return new TvContentRating(strs[0], strs[1], strs[2], strs[3], null);
+ return new TvContentRating(strs[0], strs[1], strs[2], null);
}
/**
* Constructs a TvContentRating object from a given rating and sub-rating constants.
*
- * @param rating The rating constant defined in this class.
+ * @param domain The domain name.
+ * @param ratingSystem The rating system id.
+ * @param rating The content rating string.
* @param subRatings The String array of sub-rating constants defined in this class.
*/
- private TvContentRating(String domain, String countryCode,
- String ratingSystem, String rating, String[] subRatings) {
+ private TvContentRating(
+ String domain, String ratingSystem, String rating, String[] subRatings) {
mDomain = domain;
- mCountryCode = countryCode;
mRatingSystem = ratingSystem;
mRating = rating;
mSubRatings = subRatings;
@@ -829,13 +908,6 @@
}
/**
- * Returns the country code in ISO 3166-2 format or {@code null}.
- */
- public String getCountry() {
- return mCountryCode;
- }
-
- /**
* Returns the rating system id.
*/
public String getRatingSystem() {
@@ -872,8 +944,6 @@
StringBuilder builder = new StringBuilder();
builder.append(mDomain);
builder.append(DELIMITER);
- builder.append(mCountryCode);
- builder.append(DELIMITER);
builder.append(mRatingSystem);
builder.append(DELIMITER);
builder.append(mRating);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index a555454..fdf0d9c 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -176,16 +176,20 @@
* @param session A {@link TvInputManager.Session} associated with this callback.
* @param tracks A list which includes track information.
*/
- public void onTrackInfoChanged(Session session, List<TvTrackInfo> tracks) {
+ public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
}
/**
- * This is called when there is a change on the selected tracks in this session.
+ * This is called when a track for a given type is selected.
*
* @param session A {@link TvInputManager.Session} associated with this callback
- * @param selectedTracks A list of selected tracks.
+ * @param type The type of the selected track. The type can be
+ * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackId The ID of the selected track. When {@code null} the currently selected
+ * track for a given type should be unselected.
*/
- public void onTrackSelectionChanged(Session session, List<TvTrackInfo> selectedTracks) {
+ public void onTrackSelected(Session session, int type, String trackId) {
}
/**
@@ -282,22 +286,44 @@
});
}
- public void postTrackInfoChanged(final List<TvTrackInfo> tracks) {
+ public void postTracksChanged(final List<TvTrackInfo> tracks) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSession.mTracks = tracks;
- mSessionCallback.onTrackInfoChanged(mSession, tracks);
+ mSession.mAudioTracks.clear();
+ mSession.mVideoTracks.clear();
+ mSession.mSubtitleTracks.clear();
+ for (TvTrackInfo track : tracks) {
+ if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
+ mSession.mAudioTracks.add(track);
+ } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
+ mSession.mVideoTracks.add(track);
+ } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
+ mSession.mSubtitleTracks.add(track);
+ } else {
+ // Silently ignore.
+ }
+ }
+ mSessionCallback.onTracksChanged(mSession, tracks);
}
});
}
- public void postTrackSelectionChanged(final List<TvTrackInfo> selectedTracks) {
+ public void postTrackSelected(final int type, final String trackId) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSession.mSelectedTracks = selectedTracks;
- mSessionCallback.onTrackSelectionChanged(mSession, selectedTracks);
+ if (type == TvTrackInfo.TYPE_AUDIO) {
+ mSession.mSelectedAudioTrackId = trackId;
+ } else if (type == TvTrackInfo.TYPE_VIDEO) {
+ mSession.mSelectedVideoTrackId = trackId;
+ } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
+ mSession.mSelectedSubtitleTrackId = trackId;
+ } else {
+ // Silently ignore.
+ return;
+ }
+ mSessionCallback.onTrackSelected(mSession, type, trackId);
}
});
}
@@ -476,26 +502,26 @@
}
@Override
- public void onTrackInfoChanged(List<TvTrackInfo> tracks, int seq) {
+ public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
- record.postTrackInfoChanged(tracks);
+ record.postTracksChanged(tracks);
}
}
@Override
- public void onTrackSelectionChanged(List<TvTrackInfo> selectedTracks, int seq) {
+ public void onTrackSelected(int type, String trackId, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
- record.postTrackSelectionChanged(selectedTracks);
+ record.postTrackSelected(type, trackId);
}
}
@@ -911,8 +937,12 @@
private IBinder mToken;
private TvInputEventSender mSender;
private InputChannel mChannel;
- private List<TvTrackInfo> mTracks;
- private List<TvTrackInfo> mSelectedTracks;
+ private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>();
+ private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>();
+ private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>();
+ private String mSelectedAudioTrackId;
+ private String mSelectedVideoTrackId;
+ private String mSelectedSubtitleTrackId;
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
@@ -1045,7 +1075,12 @@
Log.w(TAG, "The session has been already released");
return;
}
- mTracks = null;
+ mAudioTracks.clear();
+ mVideoTracks.clear();
+ mSubtitleTracks.clear();
+ mSelectedAudioTrackId = null;
+ mSelectedVideoTrackId = null;
+ mSelectedSubtitleTrackId = null;
try {
mService.tune(mToken, channelUri, params, mUserId);
} catch (RemoteException e) {
@@ -1073,69 +1108,84 @@
/**
* Selects a track.
*
- * @param track The track to be selected.
+ * @param type The type of the track to select. The type can be
+ * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackId The ID of the track to select. When {@code null}, the currently selected
+ * track of the given type will be unselected.
* @see #getTracks()
*/
- public void selectTrack(TvTrackInfo track) {
- if (track == null) {
- throw new IllegalArgumentException("track cannot be null");
+ public void selectTrack(int type, String trackId) {
+ if (type == TvTrackInfo.TYPE_AUDIO) {
+ if (trackId != null && !mAudioTracks.contains(trackId)) {
+ Log.w(TAG, "Invalid audio trackId: " + trackId);
+ }
+ } else if (type == TvTrackInfo.TYPE_VIDEO) {
+ if (trackId != null && !mVideoTracks.contains(trackId)) {
+ Log.w(TAG, "Invalid video trackId: " + trackId);
+ }
+ } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
+ if (trackId != null && !mSubtitleTracks.contains(trackId)) {
+ Log.w(TAG, "Invalid subtitle trackId: " + trackId);
+ }
+ } else {
+ throw new IllegalArgumentException("invalid type: " + type);
}
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
- mService.selectTrack(mToken, track, mUserId);
+ mService.selectTrack(mToken, type, trackId, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
- * Unselects a track.
+ * Returns the list of tracks for a given type. Returns {@code null} if the information is
+ * not available.
*
- * @param track The track to be selected.
- * @see #getTracks()
+ * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
+ * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @return the list of tracks for the given type.
*/
- public void unselectTrack(TvTrackInfo track) {
- if (track == null) {
- throw new IllegalArgumentException("track cannot be null");
+ public List<TvTrackInfo> getTracks(int type) {
+ if (type == TvTrackInfo.TYPE_AUDIO) {
+ if (mAudioTracks == null) {
+ return null;
+ }
+ return mAudioTracks;
+ } else if (type == TvTrackInfo.TYPE_VIDEO) {
+ if (mVideoTracks == null) {
+ return null;
+ }
+ return mVideoTracks;
+ } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
+ if (mSubtitleTracks == null) {
+ return null;
+ }
+ return mSubtitleTracks;
}
- if (mToken == null) {
- Log.w(TAG, "The session has been already released");
- return;
- }
- try {
- mService.unselectTrack(mToken, track, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ throw new IllegalArgumentException("invalid type: " + type);
}
/**
- * Returns a list which includes track information. May return {@code null} if the
- * information is not available.
- * @see #selectTrack(TvTrackInfo)
- * @see #unselectTrack(TvTrackInfo)
+ * Returns the selected track for a given type. Returns {@code null} if the information is
+ * not available or any of the tracks for the given type is not selected.
+ *
+ * @return the ID of the selected track.
+ * @see #selectTrack
*/
- public List<TvTrackInfo> getTracks() {
- if (mTracks == null) {
- return null;
+ public String getSelectedTrack(int type) {
+ if (type == TvTrackInfo.TYPE_AUDIO) {
+ return mSelectedAudioTrackId;
+ } else if (type == TvTrackInfo.TYPE_VIDEO) {
+ return mSelectedVideoTrackId;
+ } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
+ return mSelectedSubtitleTrackId;
}
- return new ArrayList<TvTrackInfo>(mTracks);
- }
-
- /**
- * Returns a list of selected tracks May return {@code null} if the information is not
- * available.
- * @see #selectTrack(TvTrackInfo)
- * @see #unselectTrack(TvTrackInfo)
- */
- public List<TvTrackInfo> getSelectedTracks() {
- if (mSelectedTracks == null) {
- return null;
- }
- return new ArrayList<TvTrackInfo>(mSelectedTracks);
+ throw new IllegalArgumentException("invalid type: " + type);
}
/**
@@ -1404,6 +1454,10 @@
mPendingEventPool.release(p);
}
+ IBinder getToken() {
+ return mToken;
+ }
+
private void releaseInternal() {
mToken = null;
synchronized (mHandler) {
diff --git a/media/java/android/media/tv/TvInputPassthroughWrapperService.java b/media/java/android/media/tv/TvInputPassthroughWrapperService.java
deleted file mode 100644
index 08c802f6..0000000
--- a/media/java/android/media/tv/TvInputPassthroughWrapperService.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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 android.media.tv;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Surface;
-
-/**
- * TvInputPassthroughWrapperService represents a TV input which controls an external device
- * connected to a pass-through TV input (e.g. HDMI 1).
- * <p>
- * This service wraps around a pass-through TV input and delegates the {@link Surface} to the
- * connected TV input so that the application can show the pass-through TV input while
- * TvInputPassthroughWrapperService controls the underlying external device via a separate
- * connection. In the setup activity, the TV input should get the pass-through TV input ID, around
- * which this service will wrap. The service implementation should pass the ID via
- * {@link TvInputPassthroughWrapperService#getPassthroughInputId(String)}. In addition,
- * it needs to implement {@link TvInputPassthroughWrapperService#onCreatePassthroughWrapperSession}
- * to handle requests from the application.
- * </p>
- */
-public abstract class TvInputPassthroughWrapperService extends TvInputService {
- private static final String TAG = "TvInputPassthroughWrapperService";
- // STOPSHIP: Turn debugging off.
- private static final boolean DEBUG = true;
- private TvInputManager mTvInputManager;
- private Handler mHandler;
-
- @Override
- public void onCreate() {
- if (DEBUG) Log.d(TAG, "onCreate()");
- super.onCreate();
- mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
- mHandler = new Handler();
- }
-
- @Override
- public final Session onCreateSession(String inputId) {
- if (DEBUG) Log.d(TAG, "onCreateSession()");
- // Checks the pass-through TV input is properly setup.
- String passthroughInputId = getPassthroughInputId(inputId);
- if (passthroughInputId == null) {
- Log.w(TAG, "The passthrough TV input for input id(" + inputId + ") is not setup yet.");
- return null;
- }
- // Checks if input id from the derived class is really pass-through type.
- TvInputInfo info = mTvInputManager.getTvInputInfo(passthroughInputId);
- if (info == null || !info.isPassthroughInputType()) {
- Log.w(TAG, "Invalid TV input id from derived class: " + passthroughInputId);
- return null;
- }
- // Creates a PassthroughWrapperSession.
- PassthroughWrapperSession session = onCreatePassthroughWrapperSession();
- if (session == null) {
- return null;
- }
- // Connects to the pass-through input the external device is connected to.
- if (!session.connect(passthroughInputId)) {
- throw new IllegalStateException("WrapperSession cannot be reused.");
- }
- notifyWrappedInputId(inputId, passthroughInputId);
- return session;
- }
-
- /**
- * Returns an implementation of {@link PassthroughWrapperSession}.
- * <p>
- * May return {@code null} if {@link TvInputPassthroughWrapperService} fails to create a
- * session.
- * </p>
- */
- public abstract PassthroughWrapperSession onCreatePassthroughWrapperSession();
-
- /**
- * Returns the TV input id the external device is connected to.
- * <p>
- * {@link TvInputPassthroughWrapperService} is expected to identify the pass-though TV
- * input the external device is connected to in the setup phase of this TV input.
- * May return {@code null} if the pass-though TV input is not identified yet.
- * </p>
- * @param inputId The ID of the TV input which controls the external device.
- */
- public abstract String getPassthroughInputId(String inputId);
-
- /**
- * Base session class for derived classes to handle the request from the application. This
- * creates additional session to the pass-through TV input internally and delegates the
- * {@link Surface} given from the application.
- */
- public abstract class PassthroughWrapperSession extends Session {
- private static final float VOLUME_ON = 1.0f;
- private static final float VOLUME_OFF = 0f;
- private TvInputManager.Session mSession;
- private Surface mSurface;
- private Float mVolume;
- private boolean mReleased;
- private int mSurfaceFormat;
- private int mSurfaceWidth;
- private int mSurfaceHeight;
- private boolean mSurfaceChanged;
- private boolean mConnectionRequested;
-
- private final TvInputManager.SessionCallback mSessionCallback =
- new TvInputManager.SessionCallback() {
- @Override
- public void onSessionCreated(TvInputManager.Session session) {
- if (session == null) {
- Log.w(TAG, "Failed to create session.");
- onPassthroughSessionCreationFailed();
- return;
- }
- if (mReleased) {
- session.release();
- return;
- }
- if (mSurface != null) {
- session.setSurface(mSurface);
- mSurface = null;
- }
- if (mVolume != null) {
- session.setStreamVolume(mVolume);
- mVolume = null;
- }
- if (mSurfaceChanged) {
- session.dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
- mSurfaceChanged = false;
- }
- mSession = session;
- }
-
- @Override
- public void onSessionReleased(TvInputManager.Session session) {
- mReleased = true;
- mSession = null;
- onPassthroughSessionReleased();
- }
-
- @Override
- public void onVideoAvailable(TvInputManager.Session session) {
- if (mSession == session) {
- onPassthroughVideoAvailable();
- }
- }
-
- @Override
- public void onVideoUnavailable(TvInputManager.Session session, int reason) {
- if (mSession == session) {
- onPassthroughVideoUnavailable(reason);
- }
- }
-
- @Override
- public void onSessionEvent(TvInputManager.Session session, String eventType,
- Bundle eventArgs) {
- if (mSession == session) {
- notifySessionEvent(eventType, eventArgs);
- }
- }
- };
-
- /**
- * Called when failed to create a session for pass-through TV input.
- */
- public abstract void onPassthroughSessionCreationFailed();
-
- /**
- * Called when the pass-through TV input session is released. This typically happens when
- * the process hosting the pass-through TV input has crashed or been killed.
- */
- public abstract void onPassthroughSessionReleased();
-
- /**
- * Called when the underlying pass-through TV input session calls
- * {@link #notifyVideoAvailable()}.
- */
- public abstract void onPassthroughVideoAvailable();
-
- /**
- * Called when the underlying pass-through TV input session calls
- * {@link #notifyVideoUnavailable(int)}.
- *
- * @param reason The reason why the pass-through TV input stopped the playback.
- */
- public abstract void onPassthroughVideoUnavailable(int reason);
-
- @Override
- public final boolean onSetSurface(Surface surface) {
- if (DEBUG) Log.d(TAG, "onSetSurface(" + surface + ")");
- if (mSession == null) {
- mSurface = surface;
- } else {
- mSession.setSurface(surface);
- }
- return true;
- }
-
- private boolean connect(String inputId) {
- if (mConnectionRequested) {
- return false;
- }
- mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
- mConnectionRequested = true;
- return true;
- }
-
- @Override
- void release() {
- super.release();
- mReleased = true;
- if (mSession != null) {
- mSession.release();
- }
- }
-
- @Override
- void dispatchSurfaceChanged(int format, int width, int height) {
- super.dispatchSurfaceChanged(format, width, height);
- if (mSession == null) {
- mSurfaceFormat = format;
- mSurfaceWidth = width;
- mSurfaceHeight = height;
- mSurfaceChanged = true;
- } else {
- mSession.dispatchSurfaceChanged(format, width, height);
- }
- }
-
- @Override
- void setStreamVolume(float volume) {
- super.setStreamVolume(volume);
- // Here, we let the pass-through TV input know only whether volume is on or off and
- // make the fine control done in the derived class to prevent that the volume is
- // controlled in the both side.
- float volumeForPassthriughInput = (volume > 0.0f) ? VOLUME_ON : VOLUME_OFF;
- if (mSession == null) {
- mVolume = Float.valueOf(volumeForPassthriughInput);
- } else {
- mSession.setStreamVolume(volumeForPassthriughInput);
- }
- }
- }
-}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 7ce278c..0f90c2d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -31,6 +31,7 @@
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.InputChannel;
@@ -47,7 +48,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
@@ -159,6 +162,8 @@
* Returns a concrete implementation of {@link Session}.
* <p>
* May return {@code null} if this TV input service fails to create a session for some reason.
+ * If TV input represents an external device connected to a hardware TV input,
+ * {@link HardwareSession} should be returned.
* </p>
* @param inputId The ID of the TV input associated with the session.
*/
@@ -217,21 +222,6 @@
}
/**
- * Notify wrapped TV input ID of current input to TV input framework manager
- *
- * @param inputId The TV input ID of {@link TvInputPassthroughWrapperService}
- * @param wrappedInputId The ID of the wrapped TV input such as external pass-though TV input
- * @hide
- */
- public final void notifyWrappedInputId(String inputId, String wrappedInputId) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = inputId;
- args.arg2 = wrappedInputId;
- mHandler.obtainMessage(TvInputService.ServiceHandler.DO_SET_WRAPPED_TV_INPUT_ID,
- args).sendToTarget();
- }
-
- /**
* Base class for derived classes to implement to provide a TV input session.
*/
public abstract class Session implements KeyEvent.Callback {
@@ -319,42 +309,56 @@
}
/**
- * Sends the change on the track information. This is expected to be called whenever a
- * track is added/removed and the metadata of a track is modified.
+ * Sends the change on the track information. This is expected to be called whenever a track
+ * is added/removed and the metadata of a track is modified.
*
* @param tracks A list which includes track information.
+ * @throws IllegalArgumentException if {@code tracks} contains redundant tracks.
*/
- public void notifyTrackInfoChanged(final List<TvTrackInfo> tracks) {
+ public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
+ Set<String> trackIdSet = new HashSet<String>();
+ for (TvTrackInfo track : tracks) {
+ String trackId = track.getId();
+ if (trackIdSet.contains(trackId)) {
+ throw new IllegalArgumentException("redundant track ID: " + trackId);
+ }
+ trackIdSet.add(trackId);
+ }
+ trackIdSet.clear();
+
+ // TODO: Validate the track list.
mHandler.post(new Runnable() {
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTrackInfoChanged");
- mSessionCallback.onTrackInfoChanged(tracks);
+ if (DEBUG) Log.d(TAG, "notifyTracksChanged");
+ mSessionCallback.onTracksChanged(tracks);
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTrackInfoChanged");
+ Log.w(TAG, "error in notifyTracksChanged");
}
}
});
}
/**
- * Sends the list of selected tracks. This is expected to be called whenever there is a
- * change on track selection.
+ * Sends the ID of the selected track for a given track type. This is expected to be called
+ * whenever there is a change on track selection.
*
- * @param selectedTracks A list of selected tracks.
- * @see #onSelectTrack(TvTrackInfo)
- * @see #onUnselectTrack(TvTrackInfo)
+ * @param type The type of the selected track. The type can be
+ * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackId The ID of the selected track.
+ * @see #onSelectTrack
*/
- public void notifyTrackSelectionChanged(final List<TvTrackInfo> selectedTracks) {
+ public void notifyTrackSelected(final int type, final String trackId) {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
- if (DEBUG) Log.d(TAG, "notifyTrackSelectionChanged");
- mSessionCallback.onTrackSelectionChanged(selectedTracks);
+ if (DEBUG) Log.d(TAG, "notifyTrackSelected");
+ mSessionCallback.onTrackSelected(type, trackId);
} catch (RemoteException e) {
- Log.w(TAG, "error in notifyTrackSelectionChanged");
+ Log.w(TAG, "error in notifyTrackSelected");
}
}
});
@@ -382,7 +386,7 @@
* Informs the application that video is not available, so the TV input cannot continue
* playing the TV stream.
*
- * @param reason The reason why the TV input stopped the playback:
+ * @param reason The reason that the TV input stopped the playback:
* <ul>
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
@@ -598,34 +602,20 @@
}
/**
- * Selects a given track.
+ * Select a given track.
* <p>
- * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the
- * track selected previously should be unselected in the implementation of this method.
- * Also, if the select operation was successful, the implementation should call
- * {@link #notifyTrackSelectionChanged(List)} to report the selected track list.
+ * If this is done successfully, the implementation should call {@link #notifyTrackSelected}
+ * to help applications maintain the selcted track lists.
* </p>
*
- * @param track The track to be selected.
- * @return {@code true} if the select operation was successful, {@code false} otherwise.
- * @see #notifyTrackSelectionChanged(List)
+ * @param trackId The ID of the track to select. {@code null} means to unselect the current
+ * track for a given type.
+ * @param type The type of the track to select. The type can be
+ * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @see #notifyTrackSelected
*/
- public boolean onSelectTrack(TvTrackInfo track) {
- return false;
- }
-
- /**
- * Unselects a given track.
- * <p>
- * If the unselect operation was successful, the implementation should call
- * {@link #notifyTrackSelectionChanged(List)} to report the selected track list.
- * </p>
- *
- * @param track The track to be unselected.
- * @return {@code true} if the unselect operation was successful, {@code false} otherwise.
- * @see #notifyTrackSelectionChanged(List)
- */
- public boolean onUnselectTrack(TvTrackInfo track) {
+ public boolean onSelectTrack(int type, String trackId) {
return false;
}
@@ -837,17 +827,8 @@
/**
* Calls {@link #onSelectTrack}.
*/
- void selectTrack(TvTrackInfo track) {
- onSelectTrack(track);
- // TODO: Handle failure.
- }
-
- /**
- * Calls {@link #onUnselectTrack}.
- */
- void unselectTrack(TvTrackInfo track) {
- onUnselectTrack(track);
- // TODO: Handle failure.
+ void selectTrack(int type, String trackId) {
+ onSelectTrack(type, trackId);
}
/**
@@ -994,6 +975,97 @@
}
}
+ /**
+ * Base class for a TV input session which represents an external device connected to a
+ * hardware TV input. Once TV input returns an implementation of this class on
+ * {@link #onCreateSession(String)}, the framework will create a hardware session and forward
+ * the application's surface to the hardware TV input.
+ * @see #onCreateSession(String)
+ */
+ public abstract class HardwareSession extends Session {
+
+ private TvInputManager.Session mHardwareSession;
+ private ITvInputSession mProxySession;
+ private ITvInputSessionCallback mProxySessionCallback;
+
+ /**
+ * Returns the hardware TV input ID the external device is connected to.
+ * <p>
+ * TV input is expected to provide {@link android.R.attr#setupActivity} so that
+ * the application can launch it before using this TV input. The setup activity may let
+ * the user select the hardware TV input to which the external device is connected. The ID
+ * of the selected one should be stored in the TV input so that it can be returned here.
+ * </p>
+ */
+ public abstract String getHardwareInputId();
+
+ private final TvInputManager.SessionCallback mHardwareSessionCallback =
+ new TvInputManager.SessionCallback() {
+ @Override
+ public void onSessionCreated(TvInputManager.Session session) {
+ mHardwareSession = session;
+ SomeArgs args = SomeArgs.obtain();
+ if (session != null) {
+ args.arg1 = mProxySession;
+ args.arg2 = mProxySessionCallback;
+ args.arg3 = session.getToken();
+ } else {
+ args.arg1 = null;
+ args.arg2 = mProxySessionCallback;
+ args.arg3 = null;
+ onRelease();
+ }
+ mHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onVideoAvailable(final TvInputManager.Session session) {
+ if (mHardwareSession == session) {
+ onHardwareVideoAvailable();
+ }
+ }
+
+ @Override
+ public void onVideoUnavailable(final TvInputManager.Session session,
+ final int reason) {
+ if (mHardwareSession == session) {
+ onHardwareVideoUnavailable(reason);
+ }
+ }
+ };
+
+ /**
+ * This method will not be called in {@link HardwareSession}. Framework will
+ * forward the application's surface to the hardware TV input.
+ */
+ @Override
+ public final boolean onSetSurface(Surface surface) {
+ Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
+ return false;
+ }
+
+ /**
+ * Called when the underlying hardware TV input session calls
+ * {@link TvInputService.Session#notifyVideoAvailable()}.
+ */
+ public void onHardwareVideoAvailable() { }
+
+ /**
+ * Called when the underlying hardware TV input session calls
+ * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
+ *
+ * @param reason The reason that the hardware TV input stopped the playback:
+ * <ul>
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+ * </ul>
+ */
+ public void onHardwareVideoUnavailable(int reason) { }
+ }
+
/** @hide */
public static boolean isNavigationKey(int keyCode) {
switch (keyCode) {
@@ -1017,11 +1089,11 @@
@SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
- private static final int DO_ADD_HARDWARE_TV_INPUT = 2;
- private static final int DO_REMOVE_HARDWARE_TV_INPUT = 3;
- private static final int DO_ADD_HDMI_CEC_TV_INPUT = 4;
- private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 5;
- private static final int DO_SET_WRAPPED_TV_INPUT_ID = 6;
+ private static final int DO_NOTIFY_SESSION_CREATED = 2;
+ private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
+ private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
+ private static final int DO_ADD_HDMI_CEC_TV_INPUT = 5;
+ private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 6;
private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
int n = mCallbacks.beginBroadcast();
@@ -1060,18 +1132,6 @@
mCallbacks.finishBroadcast();
}
- private void broadcastSetWrappedTvInputId(String inputId, String wrappedInputId) {
- int n = mCallbacks.beginBroadcast();
- for (int i = 0; i < n; ++i) {
- try {
- mCallbacks.getBroadcastItem(i).setWrappedInputId(inputId, wrappedInputId);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while broadcasting.", e);
- }
- }
- mCallbacks.finishBroadcast();
- }
-
@Override
public final void handleMessage(Message msg) {
switch (msg.what) {
@@ -1080,17 +1140,58 @@
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
String inputId = (String) args.arg3;
- try {
- Session sessionImpl = onCreateSession(inputId);
- if (sessionImpl == null) {
+ Session sessionImpl = onCreateSession(inputId);
+ args.recycle();
+ if (sessionImpl == null) {
+ try {
// Failed to create a session.
- cb.onSessionCreated(null);
- } else {
- sessionImpl.setSessionCallback(cb);
- ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
- sessionImpl, channel);
- cb.onSessionCreated(stub);
+ cb.onSessionCreated(null, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
}
+ } else {
+ sessionImpl.setSessionCallback(cb);
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ sessionImpl, channel);
+ if (sessionImpl instanceof HardwareSession) {
+ HardwareSession proxySession =
+ ((HardwareSession) sessionImpl);
+ String harewareInputId = proxySession.getHardwareInputId();
+ if (!TextUtils.isEmpty(harewareInputId)) {
+ // TODO: check if the given ID is really hardware TV input.
+ proxySession.mProxySession = stub;
+ proxySession.mProxySessionCallback = cb;
+ TvInputManager manager = (TvInputManager) getSystemService(
+ Context.TV_INPUT_SERVICE);
+ manager.createSession(harewareInputId,
+ proxySession.mHardwareSessionCallback, mHandler);
+ } else {
+ sessionImpl.onRelease();
+ Log.w(TAG, "Hardware input id is not setup yet.");
+ try {
+ cb.onSessionCreated(null, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
+ }
+ }
+ } else {
+ SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = stub;
+ someArgs.arg2 = cb;
+ someArgs.arg3 = null;
+ mHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
+ someArgs).sendToTarget();
+ }
+ }
+ return;
+ }
+ case DO_NOTIFY_SESSION_CREATED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ ITvInputSession stub = (ITvInputSession) args.arg1;
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+ IBinder hardwareSessionToken = (IBinder) args.arg3;
+ try {
+ cb.onSessionCreated(stub, hardwareSessionToken);
} catch (RemoteException e) {
Log.e(TAG, "error in onSessionCreated");
}
@@ -1129,13 +1230,6 @@
}
return;
}
- case DO_SET_WRAPPED_TV_INPUT_ID: {
- SomeArgs args = (SomeArgs) msg.obj;
- String inputId = (String) args.arg1;
- String wrappedInputId = (String) args.arg2;
- broadcastSetWrappedTvInputId(inputId, wrappedInputId);
- return;
- }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index f296984..6ddb2a2 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -40,6 +40,7 @@
public static final int TYPE_SUBTITLE = 2;
private final int mType;
+ private final String mId;
private final String mLanguage;
private final int mAudioChannelCount;
private final int mAudioSampleRate;
@@ -47,9 +48,10 @@
private final int mVideoHeight;
private final Bundle mExtra;
- private TvTrackInfo(int type, String language, int audioChannelCount,
+ private TvTrackInfo(int type, String id, String language, int audioChannelCount,
int audioSampleRate, int videoWidth, int videoHeight, Bundle extra) {
mType = type;
+ mId = id;
mLanguage = language;
mAudioChannelCount = audioChannelCount;
mAudioSampleRate = audioSampleRate;
@@ -60,6 +62,7 @@
private TvTrackInfo(Parcel in) {
mType = in.readInt();
+ mId = in.readString();
mLanguage = in.readString();
mAudioChannelCount = in.readInt();
mAudioSampleRate = in.readInt();
@@ -77,6 +80,13 @@
}
/**
+ * Returns the ID of the track.
+ */
+ public final String getId() {
+ return mId;
+ }
+
+ /**
* Returns the language information encoded by either ISO 639-1 or ISO 639-2/T. If the language
* is unknown or could not be determined, the corresponding value will be {@code null}.
*/
@@ -147,6 +157,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
+ dest.writeString(mId);
dest.writeString(mLanguage);
dest.writeInt(mAudioChannelCount);
dest.writeInt(mAudioSampleRate);
@@ -172,7 +183,8 @@
* A builder class for creating {@link TvTrackInfo} objects.
*/
public static final class Builder {
- private int mType;
+ private final String mId;
+ private final int mType;
private String mLanguage;
private int mAudioChannelCount;
private int mAudioSampleRate;
@@ -185,14 +197,20 @@
* must be added.
*
* @param type The type of the track.
+ * @param id The ID of the track that uniquely identifies the current track among all the
+ * other tracks in the same TV program.
*/
- public Builder(int type) {
+ public Builder(int type, String id) {
if (type != TYPE_AUDIO
&& type != TYPE_VIDEO
&& type != TYPE_SUBTITLE) {
throw new IllegalArgumentException("Unknown type: " + type);
}
+ if (id == null) {
+ throw new IllegalArgumentException("id cannot be null");
+ }
mType = type;
+ mId = id;
}
/**
@@ -276,8 +294,8 @@
* @return The new {@link TvTrackInfo} instance
*/
public TvTrackInfo build() {
- return new TvTrackInfo(mType, mLanguage, mAudioChannelCount,
- mAudioSampleRate, mVideoWidth, mVideoHeight, mExtra);
+ return new TvTrackInfo(mType, mId, mLanguage, mAudioChannelCount, mAudioSampleRate,
+ mVideoWidth, mVideoHeight, mExtra);
}
}
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 78b1754..0959800 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -294,55 +294,49 @@
/**
* Selects a track.
- * <p>
- * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the track
- * selected in previous will be unselected. Note that this method does not take any effect
- * unless the current TvView is tuned.
- * </p>
*
- * @param track the track to be selected.
- * @see #getTracks()
+ * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO},
+ * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackId The ID of the track to select. {@code null} means to unselect the current
+ * track for a given type.
+ * @see #getTracks
+ * @see #getSelectedTrack
*/
- public void selectTrack(TvTrackInfo track) {
+ public void selectTrack(int type, String trackId) {
if (mSession != null) {
- mSession.selectTrack(track);
+ mSession.selectTrack(type, trackId);
}
}
/**
- * Unselects a track.
- * <p>
- * Note that this method does not take any effect unless the current TvView is tuned.
+ * Returns the list of tracks. Returns {@code null} if the information is not available.
*
- * @param track the track to be unselected.
- * @see #getTracks()
+ * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
+ * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @see #selectTrack
+ * @see #getSelectedTrack
*/
- public void unselectTrack(TvTrackInfo track) {
- if (mSession != null) {
- mSession.unselectTrack(track);
- }
- }
-
- /**
- * Returns a list which includes of track information. May return {@code null} if the
- * information is not available.
- */
- public List<TvTrackInfo> getTracks() {
+ public List<TvTrackInfo> getTracks(int type) {
if (mSession == null) {
return null;
}
- return mSession.getTracks();
+ return mSession.getTracks(type);
}
/**
- * Returns a list of selected tracks. May return {@code null} if the information is not
- * available.
+ * Returns the ID of the selected track for a given type. Returns {@code null} if the
+ * information is not available or the track is not selected.
+ *
+ * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
+ * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @see #selectTrack
+ * @see #getTracks
*/
- public List<TvTrackInfo> getSelectedTracks() {
+ public String getSelectedTrack(int type) {
if (mSession == null) {
return null;
}
- return mSession.getSelectedTracks();
+ return mSession.getSelectedTrack(type);
}
/**
@@ -592,22 +586,6 @@
location[0] + getWidth(), location[1] + getHeight());
}
- private void updateVideoSize(List<TvTrackInfo> tracks) {
- for (TvTrackInfo track : tracks) {
- if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
- int width = track.getVideoWidth();
- int height = track.getVideoHeight();
- if (width != mVideoWidth || height != mVideoHeight) {
- mVideoWidth = width;
- mVideoHeight = height;
- if (mListener != null) {
- mListener.onVideoSizeChanged(mSessionCallback.mInputId, width, height);
- }
- }
- }
- }
- }
-
/**
* Interface used to receive various status updates on the {@link TvView}.
*/
@@ -650,16 +628,19 @@
* @param inputId The ID of the TV input bound to this view.
* @param tracks A list which includes track information.
*/
- public void onTrackInfoChanged(String inputId, List<TvTrackInfo> tracks) {
+ public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
}
/**
* This is called when there is a change on the selected tracks.
*
* @param inputId The ID of the TV input bound to this view.
- * @param selectedTracks A list which includes track information.
+ * @param type The type of the track selected. The type can be
+ * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
+ * {@link TvTrackInfo#TYPE_SUBTITLE}.
+ * @param trackId The ID of the track selected.
*/
- public void onTrackSelectionChanged(String inputId, List<TvTrackInfo> selectedTracks) {
+ public void onTrackSelected(String inputId, int type, String trackId) {
}
/**
@@ -807,29 +788,29 @@
}
@Override
- public void onTrackInfoChanged(Session session, List<TvTrackInfo> tracks) {
+ public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
if (this != mSessionCallback) {
return;
}
if (DEBUG) {
- Log.d(TAG, "onTrackInfoChanged()");
+ Log.d(TAG, "onTracksChanged()");
}
if (mListener != null) {
- mListener.onTrackInfoChanged(mInputId, tracks);
+ mListener.onTracksChanged(mInputId, tracks);
}
}
@Override
- public void onTrackSelectionChanged(Session session, List<TvTrackInfo> selectedTracks) {
+ public void onTrackSelected(Session session, int type, String trackId) {
if (this != mSessionCallback) {
return;
}
if (DEBUG) {
- Log.d(TAG, "onTrackInfoChanged()");
+ Log.d(TAG, "onTrackSelected()");
}
- updateVideoSize(selectedTracks);
+ // TODO: Update the video size when the type is TYPE_VIDEO.
if (mListener != null) {
- mListener.onTrackSelectionChanged(mInputId, selectedTracks);
+ mListener.onTrackSelected(mInputId, type, trackId);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
index a23dd15..6c8ca20 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
@@ -27,6 +27,8 @@
import android.os.Bundle;
import android.provider.DocumentsContract.Document;
+import com.android.documentsui.model.DocumentInfo;
+
/**
* Cursor wrapper that presents a sorted view of the underlying cursor. Handles
* common {@link Document} sorting modes, such as ordering directories first.
@@ -68,7 +70,7 @@
final String displayName = getCursorString(
cursor, Document.COLUMN_DISPLAY_NAME);
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
- mValueString[i] = '\001' + displayName;
+ mValueString[i] = DocumentInfo.DIR_PREFIX + displayName;
} else {
mValueString[i] = displayName;
}
@@ -180,14 +182,7 @@
final String lhs = pivotValue;
final String rhs = value[mid];
- final int compare;
- if (lhs == null) {
- compare = -1;
- } else if (rhs == null) {
- compare = 1;
- } else {
- compare = lhs.compareToIgnoreCase(rhs);
- }
+ final int compare = DocumentInfo.compareToIgnoreCaseNullable(lhs, rhs);
if (compare < 0) {
right = mid;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 91d9124..1c5ca86 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -25,6 +25,7 @@
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
+import android.text.TextUtils;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.RootCursorWrapper;
@@ -36,6 +37,7 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ProtocolException;
+import java.text.Collator;
/**
* Representation of a {@link Document}.
@@ -44,6 +46,13 @@
private static final int VERSION_INIT = 1;
private static final int VERSION_SPLIT_URI = 2;
+ private static final Collator sCollator;
+
+ static {
+ sCollator = Collator.getInstance();
+ sCollator.setStrength(Collator.SECONDARY);
+ }
+
public String authority;
public String documentId;
public String mimeType;
@@ -268,9 +277,30 @@
throw fnfe;
}
+ /**
+ * String prefix used to indicate the document is a directory.
+ */
+ public static final char DIR_PREFIX = '\001';
+
+ /**
+ * Compare two strings against each other using system default collator in a
+ * case-insensitive mode. Clusters strings prefixed with {@link #DIR_PREFIX}
+ * before other items.
+ */
public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
- if (lhs == null) return -1;
- if (rhs == null) return 1;
- return lhs.compareToIgnoreCase(rhs);
+ final boolean leftEmpty = TextUtils.isEmpty(lhs);
+ final boolean rightEmpty = TextUtils.isEmpty(rhs);
+
+ if (leftEmpty && rightEmpty) return 0;
+ if (leftEmpty) return -1;
+ if (rightEmpty) return 1;
+
+ final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX);
+ final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX);
+
+ if (leftDir && !rightDir) return -1;
+ if (rightDir && !leftDir) return 1;
+
+ return sCollator.compare(lhs, rhs);
}
}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 9473eb9..c323a33 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -18,6 +18,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
@@ -28,7 +29,9 @@
import android.os.Environment;
import android.os.FileObserver;
import android.os.FileUtils;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract;
@@ -80,6 +83,7 @@
private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
private StorageManager mStorageManager;
+ private Handler mHandler;
private final Object mRootsLock = new Object();
@@ -96,6 +100,7 @@
@Override
public boolean onCreate() {
mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
+ mHandler = new Handler();
mRoots = Lists.newArrayList();
mIdToRoot = Maps.newHashMap();
@@ -422,7 +427,25 @@
String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException {
final File file = getFileForDocId(documentId);
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
+ final int pfdMode = ParcelFileDescriptor.parseMode(mode);
+ if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
+ return ParcelFileDescriptor.open(file, pfdMode);
+ } else {
+ try {
+ // When finished writing, kick off media scanner
+ return ParcelFileDescriptor.open(file, pfdMode, mHandler, new OnCloseListener() {
+ @Override
+ public void onClose(IOException e) {
+ final Intent intent = new Intent(
+ Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(Uri.fromFile(file));
+ getContext().sendBroadcast(intent);
+ }
+ });
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to open for writing: " + e);
+ }
+ }
}
@Override
diff --git a/packages/SystemUI/res/drawable/qs_navbar_scrim.xml b/packages/SystemUI/res/drawable/qs_navbar_scrim.xml
new file mode 100644
index 0000000..bbb2617
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_navbar_scrim.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:type="linear"
+ android:angle="90"
+ android:startColor="#55000000"
+ android:endColor="#00000000" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 53a832a..fa1077b 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -115,4 +115,12 @@
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
+ <com.android.systemui.statusbar.AlphaOptimizedView
+ android:id="@+id/qs_navbar_scrim"
+ android:layout_height="96dp"
+ android:layout_width="match_parent"
+ android:layout_gravity="bottom"
+ android:visibility="invisible"
+ android:background="@drawable/qs_navbar_scrim" />
+
</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel -->
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index f0f50e1..eff3758 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -30,25 +30,39 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
- <TextView
- android:id="@+id/more_text"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_marginStart="20dp"
- android:layout_gravity="center_vertical"
- android:background="@drawable/keyguard_overflow_number_background"
- android:gravity="center"
- android:textColor="#ff686868"
- android:textStyle="bold"
- android:textSize="14dp"
- />
- <com.android.systemui.statusbar.NotificationOverflowIconsView
- android:id="@+id/overflow_icons_view"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="68dp"
- android:layout_width="120dp"
- android:layout_height="wrap_content"
- />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/more_text"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="16dp"
+ android:layout_gravity="center_vertical"
+ android:background="@drawable/keyguard_overflow_number_background"
+ android:gravity="center"
+ android:textColor="#ff686868"
+ android:textStyle="bold"
+ android:textSize="14dp"
+ />
+ <com.android.systemui.statusbar.StatusBarIconView
+ android:id="@+id/more_icon_overflow"
+ android:layout_width="@dimen/status_bar_icon_size"
+ android:layout_height="match_parent"
+ android:src="@drawable/stat_notify_more"
+ android:tint="@color/keyguard_overflow_content_color"
+ android:visibility="gone"
+ />
+ <com.android.systemui.statusbar.NotificationOverflowIconsView
+ android:id="@+id/overflow_icons_view"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
<com.android.systemui.statusbar.NotificationScrimView
android:id="@+id/scrim_view"
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 9650435..41d5984 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -25,7 +25,7 @@
}
void showRecents(boolean triggeredFromAltTab, View statusBarView);
- void hideRecents(boolean triggeredFromAltTab);
+ void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
void toggleRecents(Display display, int layoutDirection, View statusBarView);
void preloadRecents();
void cancelPreloadingRecents();
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 6c30c89..a18b0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -384,10 +384,19 @@
}
if (!mDragging) {
- // We are not doing anything, make sure the long press callback
- // is not still ticking like a bomb waiting to go off.
- removeLongPressCallback();
- return false;
+ if (mCallback.getChildAtPosition(ev) != null) {
+
+ // We are dragging directly over a card, make sure that we also catch the gesture
+ // even if nobody else wants the touch event.
+ onInterceptTouchEvent(ev);
+ return true;
+ } else {
+
+ // We are not doing anything, make sure the long press callback
+ // is not still ticking like a bomb waiting to go off.
+ removeLongPressCallback();
+ return false;
+ }
}
mVelocityTracker.addMovement(ev);
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index e03c01c..4cf5fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -77,9 +77,9 @@
}
@Override
- public void hideRecents(boolean triggeredFromAltTab) {
+ public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mUseAlternateRecents) {
- mAlternateRecents.onHideRecents(triggeredFromAltTab);
+ mAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
} else {
Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
intent.setPackage("com.android.systemui");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 0b08b93..a55c0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -51,8 +51,13 @@
final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome";
final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail";
final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail";
+ final public static String EXTRA_FROM_TASK_ID = "recents.activeTaskId";
final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
- final public static String EXTRA_TRIGGERED_FROM_TASK_ID = "recents.activeTaskId";
+ final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
+
+ final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
+ final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
+ final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
final static int sMinToggleDelay = 425;
@@ -126,14 +131,15 @@
}
/** Hides the recents */
- public void onHideRecents(boolean triggeredFromAltTab) {
+ public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
if (isRecentsTopMost(getTopMostTask(), null)) {
// Notify recents to hide itself
- Intent intent = new Intent(RecentsActivity.ACTION_HIDE_RECENTS_ACTIVITY);
+ Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY);
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(RecentsActivity.EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+ intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
}
@@ -220,7 +226,7 @@
AtomicBoolean isTopTaskHome = new AtomicBoolean();
if (isRecentsTopMost(topTask, isTopTaskHome)) {
// Notify recents to toggle itself
- Intent intent = new Intent(RecentsActivity.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
@@ -403,7 +409,7 @@
intent.putExtra(extraFlag, true);
}
intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
- intent.putExtra(EXTRA_TRIGGERED_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
+ intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
@@ -442,7 +448,7 @@
public void onAnimationStarted() {
// Notify recents to start the enter animation
if (!mStartAnimationTriggered) {
- Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+ Intent intent = new Intent(ACTION_START_ENTER_ANIMATION);
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 75fbad8..417049c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -59,12 +59,6 @@
RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
DebugOverlayView.DebugOverlayViewCallbacks {
- // Actions and Extras sent from AlternateRecentsComponent
- final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
- final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
- final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
- final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
-
RecentsConfiguration mConfig;
boolean mVisible;
@@ -131,18 +125,22 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(ACTION_HIDE_RECENTS_ACTIVITY)) {
- if (intent.getBooleanExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
+ if (action.equals(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY)) {
+ // Mark Recents as no longer visible
+ AlternateRecentsComponent.notifyVisibilityChanged(false);
+ if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
dismissRecentsToFocusedTaskOrHome(false);
- } else {
+ } else if (intent.getBooleanExtra(AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
// Otherwise, dismiss Recents to Home
dismissRecentsToHome(true);
+ } else {
+ // Do nothing, another activity is being launched on top of Recents
}
- } else if (action.equals(ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+ } else if (action.equals(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
- } else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
+ } else if (action.equals(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION)) {
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(t));
@@ -195,11 +193,11 @@
AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false);
mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false);
+ mConfig.launchedToTaskId = launchIntent.getIntExtra(
+ AlternateRecentsComponent.EXTRA_FROM_TASK_ID, -1);
mConfig.launchedWithAltTab = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
mConfig.launchedWithNoRecentTasks = !root.hasTasks();
- mConfig.launchedToTaskId = launchIntent.getIntExtra(
- AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
// Mark the task that is the launch target
int taskStackCount = stacks.size();
@@ -444,9 +442,9 @@
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_HIDE_RECENTS_ACTIVITY);
- filter.addAction(ACTION_TOGGLE_RECENTS_ACTIVITY);
- filter.addAction(ACTION_START_ENTER_ANIMATION);
+ filter.addAction(AlternateRecentsComponent.ACTION_HIDE_RECENTS_ACTIVITY);
+ filter.addAction(AlternateRecentsComponent.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ filter.addAction(AlternateRecentsComponent.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a33fb3a..ebde080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -684,10 +684,11 @@
}
@Override
- public void hideRecentApps(boolean triggeredFromAltTab) {
+ public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
int msg = MSG_HIDE_RECENT_APPS;
mHandler.removeMessages(msg);
- mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget();
+ mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
+ triggeredFromHomeKey ? 1 : 0).sendToTarget();
}
@Override
@@ -797,9 +798,9 @@
}
}
- protected void hideRecents(boolean triggeredFromAltTab) {
+ protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mRecents != null) {
- mRecents.hideRecents(triggeredFromAltTab);
+ mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
}
}
@@ -890,13 +891,12 @@
protected class H extends Handler {
public void handleMessage(Message m) {
- Intent intent;
switch (m.what) {
case MSG_SHOW_RECENT_APPS:
showRecents(m.arg1 > 0);
break;
case MSG_HIDE_RECENT_APPS:
- hideRecents(m.arg1 > 0);
+ hideRecents(m.arg1 > 0, m.arg2 > 0);
break;
case MSG_TOGGLE_RECENTS_APPS:
toggleRecents();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 9107790..63dd1e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -89,7 +89,7 @@
boolean showImeSwitcher);
public void setHardKeyboardStatus(boolean available, boolean enabled);
public void showRecentApps(boolean triggeredFromAltTab);
- public void hideRecentApps(boolean triggeredFromAltTab);
+ public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
public void toggleRecentApps();
public void preloadRecentApps();
public void cancelPreloadRecentApps();
@@ -191,11 +191,12 @@
}
}
- public void hideRecentApps(boolean triggeredFromAltTab) {
+ public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
synchronized (mList) {
mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
mHandler.obtainMessage(MSG_HIDE_RECENT_APPS,
- triggeredFromAltTab ? 1 : 0, 0, null).sendToTarget();
+ triggeredFromAltTab ? 1 : 0, triggeredFromHomeKey ? 1 : 0,
+ null).sendToTarget();
}
}
@@ -306,7 +307,7 @@
mCallbacks.showRecentApps(msg.arg1 != 0);
break;
case MSG_HIDE_RECENT_APPS:
- mCallbacks.hideRecentApps(msg.arg1 != 0);
+ mCallbacks.hideRecentApps(msg.arg1 != 0, msg.arg2 != 0);
break;
case MSG_TOGGLE_RECENT_APPS:
mCallbacks.toggleRecentApps();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index 451c5c5..edfd205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -38,6 +38,7 @@
super.onFinishInflate();
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
+ mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow));
}
public NotificationOverflowIconsView getIconsView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 9cc559f..20ffba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -98,7 +98,7 @@
private void initDimens() {
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
- mTouchSlop = configuration.getScaledTouchSlop();
+ mTouchSlop = configuration.getScaledPagingTouchSlop();
mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_min_swipe_amount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 82e59c0..ef4a73e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -33,7 +33,6 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -72,6 +71,7 @@
private UnlockMethodCache mUnlockMethodCache;
private LockPatternUtils mLockPatternUtils;
private FlashlightController mFlashlightController;
+ private PreviewInflater mPreviewInflater;
public KeyguardBottomAreaView(Context context) {
super(context);
@@ -108,6 +108,7 @@
updateTrust();
setClipChildren(false);
setClipToPadding(false);
+ mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
inflatePreviews();
}
@@ -208,7 +209,8 @@
public void launchCamera() {
mFlashlightController.killFlashlight();
Intent intent = getCameraIntent();
- if (intent == SECURE_CAMERA_INTENT) {
+ if (intent == SECURE_CAMERA_INTENT &&
+ !mPreviewInflater.wouldLaunchResolverActivity(intent)) {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
} else {
mActivityStarter.startActivity(intent);
@@ -277,9 +279,8 @@
}
private void inflatePreviews() {
- PreviewInflater inflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
- mPhonePreview = inflater.inflatePreview(PHONE_INTENT);
- mCameraPreview = inflater.inflatePreview(getCameraIntent());
+ mPhonePreview = mPreviewInflater.inflatePreview(PHONE_INTENT);
+ mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
if (mPhonePreview != null) {
mPreviewContainer.addView(mPhonePreview);
mPhonePreview.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 98bb591..aa68b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -69,6 +69,7 @@
private TextView mClockView;
private View mReserveNotificationSpace;
private MirrorView mSystemIconsCopy;
+ private View mQsNavbarScrim;
private NotificationStackScrollLayout mNotificationStackScroller;
private int mNotificationTopPadding;
@@ -149,6 +150,8 @@
private boolean mShadeEmpty;
+ private boolean mQsScrimEnabled = true;
+
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
mSystemIconsCopy = new MirrorView(context);
@@ -183,6 +186,7 @@
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.linear_out_slow_in);
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
+ mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
}
@@ -446,7 +450,13 @@
mIntercepting = false;
break;
}
- return !mQsExpanded && super.onInterceptTouchEvent(event);
+
+ // Allow closing the whole panel when in SHADE state.
+ if (mStatusBarState == StatusBarState.SHADE) {
+ return super.onInterceptTouchEvent(event);
+ } else {
+ return !mQsExpanded && super.onInterceptTouchEvent(event);
+ }
}
private void resetDownStates(MotionEvent event) {
@@ -499,7 +509,7 @@
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
- && mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+ && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded) {
// Down in the empty area while fully expanded - go to QS.
mQsTracking = true;
@@ -512,7 +522,7 @@
if (mExpandedHeight != 0) {
handleQsDown(event);
}
- if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) {
+ if (!mTwoFingerQsExpand && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture) {
return true;
@@ -535,9 +545,15 @@
return true;
}
+ private boolean isInQsArea(float x, float y) {
+ return mStatusBarState != StatusBarState.SHADE
+ || y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+ || y <= mQsContainer.getY() + mQsContainer.getHeight();
+ }
+
private void handleQsDown(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && shouldQuickSettingsIntercept(event.getX(), event.getY(), 0)) {
+ && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -623,8 +639,9 @@
}
@Override
- public void onOverscrolled(int amount) {
- if (mIntercepting) {
+ public void onOverscrolled(float lastTouchX, float lastTouchY, int amount) {
+ if (mIntercepting && shouldQuickSettingsIntercept(lastTouchX, lastTouchY,
+ -1 /* yDiff: Not relevant here */)) {
onQsExpansionStarted(amount);
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = mLastTouchY;
@@ -882,6 +899,10 @@
mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
mScrollView.setTouchEnabled(mQsExpanded);
updateEmptyShadeView();
+ mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
+ && !mStackScrollerOverscrolling && mQsScrimEnabled
+ ? View.VISIBLE
+ : View.INVISIBLE);
}
private void setQsExpansion(float height) {
@@ -908,6 +929,10 @@
mKeyguardStatusBar.setAlpha(alpha);
}
}
+ if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
+ && !mStackScrollerOverscrolling && mQsScrimEnabled) {
+ mQsNavbarScrim.setAlpha(getQsExpansionFraction());
+ }
}
private void updateNotificationScrim(float height) {
@@ -1025,7 +1050,7 @@
boolean onHeader = x >= header.getLeft() && x <= header.getRight()
&& y >= header.getTop() && y <= header.getBottom();
if (mQsExpanded) {
- return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
+ return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
} else {
return onHeader;
}
@@ -1075,8 +1100,9 @@
}
if (!isInSettings()) {
return mNotificationStackScroller.isScrolledToBottom();
+ } else {
+ return mScrollView.isScrolledToBottom();
}
- return super.isScrolledToBottom();
}
@Override
@@ -1342,6 +1368,9 @@
|| mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
mAfforanceHelper.animateHideLeftRightIcon();
}
+ if (mQsExpanded) {
+ mTwoFingerQsExpand = true;
+ }
}
@Override
@@ -1629,4 +1658,12 @@
// Hide "No notifications" in QS.
mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
}
+
+ public void setQsScrimEnabled(boolean qsScrimEnabled) {
+ boolean changed = mQsScrimEnabled != qsScrimEnabled;
+ mQsScrimEnabled = qsScrimEnabled;
+ if (changed) {
+ updateQsState();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
index 5920580..ee6b1a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ObservableScrollView.java
@@ -31,6 +31,8 @@
private int mLastOverscrollAmount;
private boolean mTouchEnabled = true;
private boolean mHandlingTouchEvent;
+ private float mLastX;
+ private float mLastY;
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -65,6 +67,8 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
mHandlingTouchEvent = true;
+ mLastX = ev.getX();
+ mLastY = ev.getY();
boolean result = super.onTouchEvent(ev);
mHandlingTouchEvent = false;
return result;
@@ -73,6 +77,8 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mHandlingTouchEvent = true;
+ mLastX = ev.getX();
+ mLastY = ev.getY();
boolean result = super.onInterceptTouchEvent(ev);
mHandlingTouchEvent = false;
return result;
@@ -107,12 +113,12 @@
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (mListener != null && mLastOverscrollAmount > 0) {
- mListener.onOverscrolled(mLastOverscrollAmount);
+ mListener.onOverscrolled(mLastX, mLastY, mLastOverscrollAmount);
}
}
public interface Listener {
void onScrollChanged();
- void onOverscrolled(int amount);
+ void onOverscrolled(float lastX, float lastY, int amount);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 3c111b6..469a831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -853,6 +853,12 @@
}
}
+ private final Runnable mPostCollapseRunnable = new Runnable() {
+ @Override
+ public void run() {
+ collapse();
+ }
+ };
private boolean onMiddleClicked() {
switch (mStatusBar.getBarState()) {
case StatusBarState.KEYGUARD:
@@ -862,7 +868,10 @@
mStatusBar.goToKeyguard();
return true;
case StatusBarState.SHADE:
- collapse();
+
+ // This gets called in the middle of the touch handling, where the state is still
+ // that we are tracking the panel. Collapse the panel after this is done.
+ post(mPostCollapseRunnable);
return false;
default:
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index e7a9779..750fb39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -650,6 +650,7 @@
if (mSearchPanelView != null) {
mSearchPanelView.setHorizontal(isVertical);
}
+ mNotificationPanel.setQsScrimEnabled(!isVertical);
}
});
mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {
@@ -2129,8 +2130,10 @@
}
if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
- mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
- mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
+ if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
+ mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
+ mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
+ }
}
if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) {
@@ -2744,7 +2747,7 @@
pw.print(" mMediaMetadata=");
pw.print(mMediaMetadata);
if (mMediaMetadata != null) {
- pw.print(" title=" + mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
}
pw.println();
@@ -3751,11 +3754,11 @@
}
@Override
- protected void hideRecents(boolean triggeredFromAltTab) {
+ protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
// Unset the recents visibility flag
mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE;
notifyUiVisibilityChanged(mSystemUiVisibility);
- super.hideRecents(triggeredFromAltTab);
+ super.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 0134fe8..330b599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -63,7 +63,6 @@
// Just an old-fashioned ImageView
performLongClick();
}
- setPressed(false);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
index d405172..cdbe494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
@@ -107,6 +107,19 @@
return info;
}
+ public boolean wouldLaunchResolverActivity(Intent intent) {
+ PackageManager packageManager = mContext.getPackageManager();
+ final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
+ if (appList.size() == 0) {
+ return false;
+ }
+ ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
+ mLockPatternUtils.getCurrentUser());
+ return wouldLaunchResolverActivity(resolved, appList);
+ }
+
private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
// If the list contains the above resolved activity, then it can't be
// ResolverActivity itself.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index fcca5fa..fec5e74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -568,8 +568,11 @@
float childTop = slidingChild.getTranslationY();
float top = childTop + slidingChild.getClipTopAmount();
float bottom = top + slidingChild.getActualHeight();
- int left = slidingChild.getLeft();
- int right = slidingChild.getRight();
+
+ // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
+ // camera affordance).
+ int left = 0;
+ int right = getWidth();
if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
return slidingChild;
@@ -2120,6 +2123,22 @@
return mDismissView.getHeight() + mPaddingBetweenElementsNormal;
}
+ public float getBottomMostNotificationBottom() {
+ final int count = getChildCount();
+ float max = 0;
+ for (int childIdx = 0; childIdx < count; childIdx++) {
+ ExpandableView child = (ExpandableView) getChildAt(childIdx);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ float bottom = child.getTranslationY() + child.getActualHeight();
+ if (bottom > max) {
+ max = bottom;
+ }
+ }
+ return max + getTranslationY();
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 984a5f4..d202036 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -680,15 +680,15 @@
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
sc.seekbarView.setEnabled(!fixedVolume);
- } else if (fixedVolume ||
- (sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
- (sSafetyWarning != null)) {
- sc.seekbarView.setEnabled(false);
} else if (isRinger && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
sc.seekbarView.setEnabled(false);
sc.icon.setEnabled(false);
sc.icon.setAlpha(mDisabledAlpha);
sc.icon.setClickable(false);
+ } else if (fixedVolume ||
+ (sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
+ (sSafetyWarning != null)) {
+ sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
sc.icon.setEnabled(true);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 324f536..14f6c5a 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -279,6 +279,7 @@
int[] mNavigationBarHeightForRotation = new int[4];
int[] mNavigationBarWidthForRotation = new int[4];
+ boolean mBootMessageNeedsHiding;
KeyguardServiceDelegate mKeyguardDelegate;
// The following are only accessed on the mHandler thread.
boolean mKeyguardDrawComplete;
@@ -537,6 +538,7 @@
private static final int MSG_WAKING_UP = 8;
private static final int MSG_DISPATCH_SHOW_RECENTS = 9;
private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
+ private static final int MSG_HIDE_BOOT_MESSAGE = 11;
private class PolicyHandler extends Handler {
@Override
@@ -579,6 +581,9 @@
case MSG_WAKING_UP:
handleWakingUp((ScreenOnListener) msg.obj);
break;
+ case MSG_HIDE_BOOT_MESSAGE:
+ handleHideBootMessage();
+ break;
}
}
}
@@ -1096,9 +1101,7 @@
initializeHdmiState();
// Match current screen state.
- if (mPowerManager.isInteractive()) {
- wakingUp(null);
- } else {
+ if (!mPowerManager.isInteractive()) {
goingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER);
}
}
@@ -2138,7 +2141,7 @@
// Cancel any pending meta actions if we see any other keys being pressed between the down
// of the meta key and its corresponding up.
- if (mPendingMetaAction && keyCode != KeyEvent.KEYCODE_META_LEFT) {
+ if (mPendingMetaAction && KeyEvent.isMetaKey(keyCode)) {
mPendingMetaAction = false;
}
@@ -2343,11 +2346,10 @@
UserHandle.CURRENT_OR_SELF);
}
return -1;
- } else if (keyCode == KeyEvent.KEYCODE_META_LEFT) {
+ } else if (KeyEvent.isMetaKey(keyCode)) {
if (down) {
mPendingMetaAction = true;
} else if (mPendingMetaAction) {
- mPendingMetaAction = false;
launchAssistAction();
}
return -1;
@@ -2426,7 +2428,9 @@
if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
- if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
+ if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)
+ || KeyEvent.metaStateHasModifiers(
+ shiftlessModifiers, KeyEvent.META_META_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
return -1;
@@ -2435,7 +2439,7 @@
} else if (!down && mRecentAppsHeldModifiers != 0
&& (metaState & mRecentAppsHeldModifiers) == 0) {
mRecentAppsHeldModifiers = 0;
- hideRecentApps(true);
+ hideRecentApps(true, false);
}
// Handle keyboard language switching.
@@ -2459,7 +2463,7 @@
}
// Reserve all the META modifier combos for system behavior
- if ((metaState & KeyEvent.META_META_LEFT_ON) != 0) {
+ if ((metaState & KeyEvent.META_META_ON) != 0) {
return -1;
}
@@ -2654,12 +2658,12 @@
}
}
- private void hideRecentApps(boolean triggeredFromAltTab) {
+ private void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHome) {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
try {
IStatusBarService statusbar = getStatusBarService();
if (statusbar != null) {
- statusbar.hideRecentApps(triggeredFromAltTab);
+ statusbar.hideRecentApps(triggeredFromAltTab, triggeredFromHome);
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when closing recent apps", e);
@@ -2701,7 +2705,7 @@
// Hide Recents and notify it to launch Home
awakenDreams();
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- hideRecentApps(false);
+ hideRecentApps(false, true);
} else {
// Otherwise, just launch Home
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
@@ -4649,6 +4653,26 @@
}
setKeyguardDrawn();
+
+ if (mBootMessageNeedsHiding) {
+ handleHideBootMessage();
+ mBootMessageNeedsHiding = false;
+ }
+ }
+
+ private void handleHideBootMessage() {
+ if (mBootMsgDialog == null) {
+ if (DEBUG_WAKEUP) Slog.d(TAG, "handleHideBootMessage: boot message not up");
+ return;
+ }
+ if (!mKeyguardDrawComplete || !mWindowManagerDrawComplete) {
+ if (DEBUG_WAKEUP) Slog.d(TAG, "handleHideBootMessage: deferring until keyguard ready");
+ mBootMessageNeedsHiding = true;
+ return;
+ }
+ if (DEBUG_WAKEUP) Slog.d(TAG, "handleHideBootMessage: dismissing");
+ mBootMsgDialog.dismiss();
+ mBootMsgDialog = null;
}
@Override
@@ -5106,14 +5130,7 @@
/** {@inheritDoc} */
@Override
public void hideBootMessages() {
- mHandler.post(new Runnable() {
- @Override public void run() {
- if (mBootMsgDialog != null) {
- mBootMsgDialog.dismiss();
- mBootMsgDialog = null;
- }
- }
- });
+ mHandler.sendEmptyMessage(MSG_HIDE_BOOT_MESSAGE);
}
/** {@inheritDoc} */
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
index 2fa23c9..3f95427 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetService.java
@@ -16,421 +16,32 @@
package com.android.server.appwidget;
-import android.app.ActivityManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-import com.android.internal.appwidget.IAppWidgetHost;
-import com.android.internal.appwidget.IAppWidgetService;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AppWidgetBackupBridge;
-import com.android.server.WidgetBackupProvider;
import com.android.server.SystemService;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-
/**
* SystemService that publishes an IAppWidgetService.
*/
-public class AppWidgetService extends SystemService implements WidgetBackupProvider {
-
- static final String TAG = "AppWidgetService";
-
- final Context mContext;
- final Handler mSaveStateHandler;
-
- final SparseArray<AppWidgetServiceImpl> mAppWidgetServices;
+public class AppWidgetService extends SystemService {
+ private final AppWidgetServiceImpl mImpl;
public AppWidgetService(Context context) {
super(context);
- mContext = context;
-
- mSaveStateHandler = BackgroundThread.getHandler();
-
- mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
- AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
- mAppWidgetServices.append(0, primary);
+ mImpl = new AppWidgetServiceImpl(context);
}
@Override
public void onStart() {
- publishBinderService(Context.APPWIDGET_SERVICE, mServiceImpl);
- AppWidgetBackupBridge.register(this);
+ publishBinderService(Context.APPWIDGET_SERVICE, mImpl);
+ AppWidgetBackupBridge.register(mImpl);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mServiceImpl.systemRunning(isSafeMode());
+ mImpl.setSafeMode(isSafeMode());
}
}
-
-
- // backup <-> app widget service bridge surface
- @Override
- public List<String> getWidgetParticipants(int userId) {
- return mServiceImpl.getWidgetParticipants(userId);
- }
-
- @Override
- public byte[] getWidgetState(String packageName, int userId) {
- return mServiceImpl.getWidgetState(packageName, userId);
- }
-
- @Override
- public void restoreStarting(int userId) {
- mServiceImpl.restoreStarting(userId);
- }
-
- @Override
- public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
- mServiceImpl.restoreWidgetState(packageName, restoredState, userId);
- }
-
- @Override
- public void restoreFinished(int userId) {
- mServiceImpl.restoreFinished(userId);
- }
-
-
- // implementation entry point and binder service
- private final AppWidgetServiceStub mServiceImpl = new AppWidgetServiceStub();
-
- class AppWidgetServiceStub extends IAppWidgetService.Stub {
-
- private boolean mSafeMode;
-
- public void systemRunning(boolean safeMode) {
- mSafeMode = safeMode;
-
- mAppWidgetServices.get(0).systemReady(safeMode);
-
- // Register for the boot completed broadcast, so we can send the
- // ENABLE broacasts. If we try to send them now, they time out,
- // because the system isn't ready to handle them yet.
- IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- filter, null, null);
-
- // Register for configuration changes so we can update the names
- // of the widgets when the locale changes.
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
-
- // Register for broadcasts about package install, etc., so we can
- // update the provider list.
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- filter, null, null);
- // Register for events related to sdcard installation.
- IntentFilter sdFilter = new IntentFilter();
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
- sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- sdFilter, null, null);
-
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- userFilter.addAction(Intent.ACTION_USER_STOPPING);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
- onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
- } else if (Intent.ACTION_USER_STOPPING.equals(intent.getAction())) {
- onUserStopping(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
- }
- }
- }, userFilter);
- }
-
- @Override
- public int allocateAppWidgetId(String packageName, int hostId, int userId)
- throws RemoteException {
- return getImplForUser(userId).allocateAppWidgetId(packageName, hostId);
- }
-
- @Override
- public int[] getAppWidgetIdsForHost(int hostId, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetIdsForHost(hostId);
- }
-
- @Override
- public void deleteAppWidgetId(int appWidgetId, int userId) throws RemoteException {
- getImplForUser(userId).deleteAppWidgetId(appWidgetId);
- }
-
- @Override
- public void deleteHost(int hostId, int userId) throws RemoteException {
- getImplForUser(userId).deleteHost(hostId);
- }
-
- @Override
- public void deleteAllHosts(int userId) throws RemoteException {
- getImplForUser(userId).deleteAllHosts();
- }
-
- @Override
- public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options,
- int userId) throws RemoteException {
- getImplForUser(userId).bindAppWidgetId(appWidgetId, provider, options);
- }
-
- @Override
- public boolean bindAppWidgetIdIfAllowed(
- String packageName, int appWidgetId, ComponentName provider, Bundle options,
- int userId) throws RemoteException {
- return getImplForUser(userId).bindAppWidgetIdIfAllowed(
- packageName, appWidgetId, provider, options);
- }
-
- @Override
- public boolean hasBindAppWidgetPermission(String packageName, int userId)
- throws RemoteException {
- return getImplForUser(userId).hasBindAppWidgetPermission(packageName);
- }
-
- @Override
- public void setBindAppWidgetPermission(String packageName, boolean permission, int userId)
- throws RemoteException {
- getImplForUser(userId).setBindAppWidgetPermission(packageName, permission);
- }
-
- @Override
- public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
- int userId) throws RemoteException {
- getImplForUser(userId).bindRemoteViewsService(appWidgetId, intent, connection);
- }
-
- @Override
- public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- List<RemoteViews> updatedViews, int userId) throws RemoteException {
- return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
- }
-
- public void onUserRemoved(int userId) {
- if (userId < 1) return;
- synchronized (mAppWidgetServices) {
- AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
- mAppWidgetServices.remove(userId);
-
- if (impl == null) {
- AppWidgetServiceImpl.getSettingsFile(userId).delete();
- } else {
- impl.onUserRemoved();
- }
- }
- }
-
- public void onUserStopping(int userId) {
- if (userId < 1) return;
- synchronized (mAppWidgetServices) {
- AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
- if (impl != null) {
- mAppWidgetServices.remove(userId);
- impl.onUserStopping();
- }
- }
- }
-
-
- // support of the widget/backup bridge
- public List<String> getWidgetParticipants(int userId) {
- return getImplForUser(userId).getWidgetParticipants();
- }
-
- public byte[] getWidgetState(String packageName, int userId) {
- return getImplForUser(userId).getWidgetState(packageName);
- }
-
- public void restoreStarting(int userId) {
- getImplForUser(userId).restoreStarting();
- }
-
- public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
- getImplForUser(userId).restoreWidgetState(packageName, restoredState);
- }
-
- public void restoreFinished(int userId) {
- getImplForUser(userId).restoreFinished();
- }
-
-
- private void checkPermission(int userId) {
- int realUserId = ActivityManager.handleIncomingUser(
- Binder.getCallingPid(),
- Binder.getCallingUid(),
- userId,
- false, /* allowAll */
- true, /* requireFull */
- this.getClass().getSimpleName(),
- this.getClass().getPackage().getName());
- }
-
- private AppWidgetServiceImpl getImplForUser(int userId) {
- checkPermission(userId);
- boolean sendInitial = false;
- AppWidgetServiceImpl service;
- synchronized (mAppWidgetServices) {
- service = mAppWidgetServices.get(userId);
- if (service == null) {
- Slog.i(TAG, "Unable to find AppWidgetServiceImpl for user " + userId
- + ", adding");
- // TODO: Verify that it's a valid user
- service = new AppWidgetServiceImpl(mContext, userId, mSaveStateHandler);
- service.systemReady(mSafeMode);
- // Assume that BOOT_COMPLETED was received, as this is a non-primary user.
- mAppWidgetServices.append(userId, service);
- sendInitial = true;
- }
- }
- if (sendInitial) {
- service.sendInitialBroadcasts();
- }
- return service;
- }
-
- @Override
- public int[] getAppWidgetIds(ComponentName provider, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetIds(provider);
- }
-
- @Override
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId)
- throws RemoteException {
- return getImplForUser(userId).getAppWidgetInfo(appWidgetId);
- }
-
- @Override
- public RemoteViews getAppWidgetViews(int appWidgetId, int userId) throws RemoteException {
- return getImplForUser(userId).getAppWidgetViews(appWidgetId);
- }
-
- @Override
- public void updateAppWidgetOptions(int appWidgetId, Bundle options, int userId) {
- getImplForUser(userId).updateAppWidgetOptions(appWidgetId, options);
- }
-
- @Override
- public Bundle getAppWidgetOptions(int appWidgetId, int userId) {
- return getImplForUser(userId).getAppWidgetOptions(appWidgetId);
- }
-
- @Override
- public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId)
- throws RemoteException {
- return getImplForUser(userId).getInstalledProviders(categoryFilter);
- }
-
- @Override
- public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId, int userId)
- throws RemoteException {
- getImplForUser(userId).notifyAppWidgetViewDataChanged(
- appWidgetIds, viewId);
- }
-
- @Override
- public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).partiallyUpdateAppWidgetIds(
- appWidgetIds, views);
- }
-
- @Override
- public void stopListening(int hostId, int userId) throws RemoteException {
- getImplForUser(userId).stopListening(hostId);
- }
-
- @Override
- public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
- throws RemoteException {
- getImplForUser(userId).unbindRemoteViewsService(
- appWidgetId, intent);
- }
-
- @Override
- public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).updateAppWidgetIds(appWidgetIds, views);
- }
-
- @Override
- public void updateAppWidgetProvider(ComponentName provider, RemoteViews views, int userId)
- throws RemoteException {
- getImplForUser(userId).updateAppWidgetProvider(provider, views);
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- // Dump the state of all the app widget providers
- synchronized (mAppWidgetServices) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- pw.println("User: " + mAppWidgetServices.keyAt(i));
- ipw.increaseIndent();
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.dump(fd, ipw, args);
- ipw.decreaseIndent();
- }
- }
- }
-
- BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- // Slog.d(TAG, "received " + action);
- if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId >= 0) {
- getImplForUser(userId).sendInitialBroadcasts();
- } else {
- Slog.w(TAG, "Incorrect user handle supplied in " + intent);
- }
- } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.onConfigurationChanged();
- }
- } else {
- int sendingUser = getSendingUserId();
- if (sendingUser == UserHandle.USER_ALL) {
- for (int i = 0; i < mAppWidgetServices.size(); i++) {
- AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
- service.onBroadcastReceived(intent);
- }
- } else {
- AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser);
- if (service != null) {
- service.onBroadcastReceived(intent);
- }
- }
- }
- }
- };
- }
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7a67d63..bdaf9ec 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -19,21 +19,25 @@
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.FilterComparison;
+import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -44,16 +48,20 @@
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AttributeSet;
-import android.util.MutableInt;
import android.util.Pair;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.Display;
@@ -61,10 +69,16 @@
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
+import com.android.server.LocalServices;
+import com.android.server.WidgetBackupProvider;
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -79,230 +93,117 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Map.Entry;
+import java.util.Map;
import java.util.Set;
-class AppWidgetServiceImpl {
-
- private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
- private static final int KEYGUARD_HOST_ID = 0x4b455947;
+class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider {
private static final String TAG = "AppWidgetServiceImpl";
- private static final String SETTINGS_FILENAME = "appwidgets.xml";
- private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
- private static final int CURRENT_VERSION = 1; // Bump if the stored widgets need to be upgraded.
- private static final int WIDGET_STATE_VERSION = 1; // version of backed-up widget state
- private static boolean DBG = true;
- private static boolean DEBUG_BACKUP = DBG || true;
+ private static boolean DEBUG = false;
- /*
- * When identifying a Host or Provider based on the calling process, use the uid field. When
- * identifying a Host or Provider based on a package manager broadcast, use the package given.
- */
+ private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
+ private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
+ private static final int KEYGUARD_HOST_ID = 0x4b455947;
- static class Provider {
- int uid;
- AppWidgetProviderInfo info;
- ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
- PendingIntent broadcast;
- boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+ private static final String STATE_FILENAME = "appwidgets.xml";
- int tag; // for use while saving state (the index)
+ private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes
- // is there an instance of this provider hosted by the given app?
- public boolean isHostedBy(String packageName) {
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- if (packageName.equals(id.host.packageName)) {
- return true;
- }
+ private static final int TAG_UNDEFINED = -1;
+
+ private static final int UNKNOWN_UID = -1;
+
+ private static final int LOADED_PROFILE_ID = -1;
+
+ private static final int DISABLED_PROFILE = -1;
+
+ private static final int UNKNOWN_USER_ID = -10;
+
+ // Bump if the stored widgets need to be upgraded.
+ private static final int CURRENT_VERSION = 1;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (DEBUG) {
+ Slog.i(TAG, "Received broadcast: " + action);
}
- return false;
- }
- @Override
- public String toString() {
- return "Provider{" + ((info == null) ? "null" : info.provider)
- + (zombie ? " Z" : "")
- + '}';
- }
- }
-
- static class Host {
- int uid;
- int hostId;
- String packageName;
- ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>();
- IAppWidgetHost callbacks;
- boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
-
- int tag; // for use while saving state (the index)
-
- boolean uidMatches(int callingUid) {
- if (UserHandle.getAppId(callingUid) == Process.myUid()) {
- // For a host that's in the system process, ignore the user id
- return UserHandle.isSameApp(this.uid, callingUid);
+ if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ onConfigurationChanged();
+ } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ onUserStopped(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL));
} else {
- return this.uid == callingUid;
+ onPackageBroadcastReceived(intent, intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
}
}
+ };
- boolean hostsPackage(String pkg) {
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- Provider p = instances.get(i).provider;
- if (p != null && p.info != null && pkg.equals(p.info.provider.getPackageName())) {
- return true;
- }
- }
- return false;
- }
+ // Manages active connections to RemoteViewsServices.
+ private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
+ mBoundRemoteViewsServices = new HashMap<>();
- @Override
- public String toString() {
- return "Host{" + packageName + ":" + hostId + '}';
- }
- }
+ // Manages persistent references to RemoteViewsServices from different App Widgets.
+ private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
+ mRemoteViewsServicesAppWidgets = new HashMap<>();
- static class AppWidgetId {
- int appWidgetId;
- int restoredId; // tracking & remapping any restored state
- Provider provider;
- RemoteViews views;
- Bundle options;
- Host host;
+ private final Object mLock = new Object();
- @Override
- public String toString() {
- return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
- }
- }
+ private final ArrayList<Widget> mWidgets = new ArrayList<>();
+ private final ArrayList<Host> mHosts = new ArrayList<>();
+ private final ArrayList<Provider> mProviders = new ArrayList<>();
- AppWidgetId findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Find restored widget: id=" + restoredId
- + " host=" + host + " provider=" + p);
- }
+ private final ArraySet<Pair<Integer, String>> mPackagesWithBindWidgetPermission =
+ new ArraySet<>();
- if (p == null || host == null) {
- return null;
- }
+ private final SparseIntArray mLoadedUserIds = new SparseIntArray();
- final int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId widget = mAppWidgetIds.get(i);
- if (widget.restoredId == restoredId
- && widget.host.hostId == host.hostId
- && widget.host.packageName.equals(host.packageName)
- && widget.provider.info.provider.equals(p.info.provider)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " Found at " + i + " : " + widget);
- }
- return widget;
- }
- }
- return null;
- }
+ private final BackupRestoreController mBackupRestoreController;
- /**
- * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
- * needs to be a static inner class since a reference to the ServiceConnection is held globally
- * and may lead us to leak AppWidgetService instances (if there were more than one).
- */
- static class ServiceConnectionProxy implements ServiceConnection {
- private final IBinder mConnectionCb;
+ private final Context mContext;
- ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
- mConnectionCb = connectionCb;
- }
+ private final IPackageManager mPackageManager;
+ private final AlarmManager mAlarmManager;
+ private final UserManager mUserManager;
- public void onServiceConnected(ComponentName name, IBinder service) {
- final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub
- .asInterface(mConnectionCb);
- try {
- cb.onServiceConnected(service);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- disconnect();
- }
-
- public void disconnect() {
- final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub
- .asInterface(mConnectionCb);
- try {
- cb.onServiceDisconnected();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- // Manages active connections to RemoteViewsServices
- private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> mBoundRemoteViewsServices = new HashMap<Pair<Integer, FilterComparison>, ServiceConnection>();
- // Manages persistent references to RemoteViewsServices from different App Widgets
- private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>();
-
- final Context mContext;
- final IPackageManager mPm;
- final AlarmManager mAlarmManager;
- final ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
- final int mUserId;
- final boolean mHasFeature;
-
- Locale mLocale;
- int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
- final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
- final ArrayList<Host> mHosts = new ArrayList<Host>();
- // set of package names
- final HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
- boolean mSafeMode;
- boolean mStateLoaded;
- int mMaxWidgetBitmapMemory;
-
- // Map old (restored) widget IDs to new AppWidgetId instances. This object is used
- // as the lock around manipulation of the overall restored-widget state, just as
- // mAppWidgetIds is used as the lock object around all "live" widget state
- // manipulations. Methods that must be called with this lock held are decorated
- // with the suffix "Lr".
- //
- // In cases when both of those locks must be held concurrently, mRestoredWidgetIds
- // must be acquired *first.*
- private final SparseArray<AppWidgetId> mRestoredWidgetIds = new SparseArray<AppWidgetId>();
-
- // We need to make sure to wipe the pre-restore widget state only once for
- // a given package. Keep track of what we've done so far here; the list is
- // cleared at the start of every system restore pass, but preserved through
- // any install-time restore operations.
- HashSet<String> mPrunedApps = new HashSet<String>();
+ private final SecurityPolicy mSecurityPolicy;
private final Handler mSaveStateHandler;
+ private final Handler mCallbackHandler;
- // These are for debugging only -- widgets are going missing in some rare instances
- ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>();
- ArrayList<Host> mDeletedHosts = new ArrayList<Host>();
+ private Locale mLocale;
- AppWidgetServiceImpl(Context context, int userId, Handler saveStateHandler) {
+ private final SparseIntArray mNextAppWidgetIds = new SparseIntArray();
+
+ private boolean mSafeMode;
+ private int mMaxWidgetBitmapMemory;
+
+ AppWidgetServiceImpl(Context context) {
mContext = context;
- mPm = AppGlobals.getPackageManager();
+ mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- mUserId = userId;
- mSaveStateHandler = saveStateHandler;
- mHasFeature = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_APP_WIDGETS);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mSaveStateHandler = BackgroundThread.getHandler();
+ mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
+ mBackupRestoreController = new BackupRestoreController();
+ mSecurityPolicy = new SecurityPolicy();
computeMaximumWidgetBitmapMemory();
+ registerBroadcastReceiver();
}
- void computeMaximumWidgetBitmapMemory() {
+ private void computeMaximumWidgetBitmapMemory() {
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
@@ -312,51 +213,99 @@
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
- public void systemReady(boolean safeMode) {
+ private void registerBroadcastReceiver() {
+ // Register for configuration changes so we can update the names
+ // of the widgets when the locale changes.
+ IntentFilter configFilter = new IntentFilter();
+ configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ configFilter, null, null);
+
+ // Register for broadcasts about package install, etc., so we can
+ // update the provider list.
+ IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ packageFilter, null, null);
+
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ sdFilter, null, null);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_STARTED);
+ userFilter.addAction(Intent.ACTION_USER_STOPPED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+ userFilter, null, null);
+ }
+
+ public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
+ }
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ private void onConfigurationChanged() {
+ if (DEBUG) {
+ Slog.i(TAG, "onConfigurationChanged()");
}
- }
- private void log(String msg) {
- Slog.i(TAG, "u=" + mUserId + ": " + msg);
- }
-
- void onConfigurationChanged() {
- if (DBG) log("Got onConfigurationChanged()");
Locale revised = Locale.getDefault();
- if (revised == null || mLocale == null || !(revised.equals(mLocale))) {
+ if (revised == null || mLocale == null || !revised.equals(mLocale)) {
mLocale = revised;
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ synchronized (mLock) {
+ SparseIntArray changedGroups = null;
+
// Note: updateProvidersForPackageLocked() may remove providers, so we must copy the
// list of installed providers and skip providers that we don't need to update.
// Also note that remove the provider does not clear the Provider component data.
- ArrayList<Provider> installedProviders =
- new ArrayList<Provider>(mInstalledProviders);
- HashSet<ComponentName> removedProviders = new HashSet<ComponentName>();
+ ArrayList<Provider> installedProviders = new ArrayList<>(mProviders);
+ HashSet<ProviderId> removedProviders = new HashSet<>();
+
int N = installedProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = installedProviders.get(i);
- ComponentName cn = p.info.provider;
- if (!removedProviders.contains(cn)) {
- updateProvidersForPackageLocked(cn.getPackageName(), removedProviders);
+ Provider provider = installedProviders.get(i);
+
+ ensureGroupStateLoadedLocked(provider.getUserId());
+
+ if (!removedProviders.contains(provider.id)) {
+ final boolean changed = updateProvidersForPackageLocked(
+ provider.id.componentName.getPackageName(),
+ provider.getUserId(), removedProviders);
+
+ if (changed) {
+ if (changedGroups == null) {
+ changedGroups = new SparseIntArray();
+ }
+ final int groupId = mSecurityPolicy.getGroupParent(
+ provider.getUserId());
+ changedGroups.put(groupId, groupId);
+ }
}
}
- saveStateAsync();
+
+ if (changedGroups != null) {
+ final int groupCount = changedGroups.size();
+ for (int i = 0; i < groupCount; i++) {
+ final int groupId = changedGroups.get(i);
+ saveGroupStateAsync(groupId);
+ }
+ }
}
}
}
- void onBroadcastReceived(Intent intent) {
- if (DBG) log("onBroadcast " + intent);
+ private void onPackageBroadcastReceived(Intent intent, int userId) {
final String action = intent.getAction();
boolean added = false;
boolean changed = false;
- boolean providersModified = false;
+ boolean componentsModified = false;
+
String pkgList[] = null;
if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
@@ -380,555 +329,1177 @@
if (pkgList == null || pkgList.length == 0) {
return;
}
- if (added || changed) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Bundle extras = intent.getExtras();
- if (changed
- || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) {
- for (String pkgName : pkgList) {
- // The package was just upgraded
- providersModified |= updateProvidersForPackageLocked(pkgName, null);
- }
- } else {
- // The package was just added. Fix up the providers...
- for (String pkgName : pkgList) {
- providersModified |= addProvidersForPackageLocked(pkgName);
- }
- // ...and see if these are hosts we've been awaiting
- for (String pkg : pkgList) {
- try {
- int uid = getUidForPackage(pkg);
- resolveHostUidLocked(pkg, uid);
- } catch (NameNotFoundException e) {
- // shouldn't happen; we just installed it!
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ Bundle extras = intent.getExtras();
+
+ if (added || changed) {
+ final boolean newPackageAdded = added && (extras == null
+ || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+
+ for (String pkgName : pkgList) {
+ // Fix up the providers - add/remove/update.
+ componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
+
+ // ... and see if these are hosts we've been awaiting.
+ // NOTE: We are backing up and restoring only the owner.
+ if (newPackageAdded && userId == UserHandle.USER_OWNER) {
+ final int uid = getUidForPackage(pkgName, userId);
+ if (uid >= 0 ) {
+ resolveHostUidLocked(pkgName, uid);
}
}
}
- saveStateAsync();
- }
- } else {
- Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
- // The package is being updated. We'll receive a PACKAGE_ADDED shortly.
} else {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
+ // If the package is being updated, we'll receive a PACKAGE_ADDED
+ // shortly, otherwise it is removed permanently.
+ final boolean packageRemovedPermanently = (extras == null
+ || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+
+ if (packageRemovedPermanently) {
for (String pkgName : pkgList) {
- providersModified |= removeProvidersForPackageLocked(pkgName);
- saveStateAsync();
+ componentsModified |= removeHostsAndProvidersForPackageLocked(
+ pkgName, userId);
}
}
}
- }
- if (providersModified) {
- // If the set of providers has been modified, notify each active AppWidgetHost
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- notifyHostsForProvidersChangedLocked();
+ if (componentsModified) {
+ saveGroupStateAsync(userId);
+
+ // If the set of providers has been modified, notify each active AppWidgetHost
+ scheduleNotifyHostsForProvidersChangedLocked();
}
}
}
- void resolveHostUidLocked(String pkg, int uid) {
+ private void resolveHostUidLocked(String pkg, int uid) {
final int N = mHosts.size();
for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.uid == -1 && pkg.equals(h.packageName)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "host " + pkg + ":" + h.hostId + " resolved to uid " + uid);
+ Host host = mHosts.get(i);
+ if (host.id.uid == UNKNOWN_UID && pkg.equals(host.id.packageName)) {
+ if (DEBUG) {
+ Slog.i(TAG, "host " + host.id + " resolved to uid " + uid);
}
- h.uid = uid;
+ host.id = new HostId(uid, host.id.hostId, host.id.packageName);
+ return;
}
}
}
- private void dumpProvider(Provider p, int index, PrintWriter pw) {
- AppWidgetProviderInfo info = p.info;
- pw.print(" ["); pw.print(index); pw.print("] provider ");
- pw.print(info.provider.flattenToShortString());
- pw.println(':');
- pw.print(" min=("); pw.print(info.minWidth);
- pw.print("x"); pw.print(info.minHeight);
- pw.print(") minResize=("); pw.print(info.minResizeWidth);
- pw.print("x"); pw.print(info.minResizeHeight);
- pw.print(") updatePeriodMillis=");
- pw.print(info.updatePeriodMillis);
- pw.print(" resizeMode=");
- pw.print(info.resizeMode);
- pw.print(info.widgetCategory);
- pw.print(" autoAdvanceViewId=");
- pw.print(info.autoAdvanceViewId);
- pw.print(" initialLayout=#");
- pw.print(Integer.toHexString(info.initialLayout));
- pw.print(" uid="); pw.print(p.uid);
- pw.print(" zombie="); pw.println(p.zombie);
- }
+ private void ensureGroupStateLoadedLocked(int userId) {
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
- private void dumpHost(Host host, int index, PrintWriter pw) {
- pw.print(" ["); pw.print(index); pw.print("] hostId=");
- pw.print(host.hostId); pw.print(' ');
- pw.print(host.packageName); pw.print('/');
- pw.print(host.uid); pw.println(':');
- pw.print(" callbacks="); pw.println(host.callbacks);
- pw.print(" instances.size="); pw.print(host.instances.size());
- pw.print(" zombie="); pw.println(host.zombie);
- }
+ // Careful lad, we may have already loaded the state for some
+ // group members, so check before loading and read only the
+ // state for the new member(s).
+ int newMemberCount = 0;
+ final int profileIdCount = profileIds.length;
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+ if (mLoadedUserIds.indexOfKey(profileId) >= 0) {
+ profileIds[i] = LOADED_PROFILE_ID;
+ } else {
+ newMemberCount++;
+ }
+ }
- private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) {
- pw.print(" ["); pw.print(index); pw.print("] id=");
- pw.println(id.appWidgetId);
- pw.print(" hostId=");
- pw.print(id.host.hostId); pw.print(' ');
- pw.print(id.host.packageName); pw.print('/');
- pw.println(id.host.uid);
- if (id.provider != null) {
- pw.print(" provider=");
- pw.println(id.provider.info.provider.flattenToShortString());
- }
- if (id.host != null) {
- pw.print(" host.callbacks="); pw.println(id.host.callbacks);
- }
- if (id.views != null) {
- pw.print(" views="); pw.println(id.views);
- }
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
+ if (newMemberCount <= 0) {
return;
}
- synchronized (mAppWidgetIds) {
- int N = mInstalledProviders.size();
+ int newMemberIndex = 0;
+ final int[] newProfileIds = new int[newMemberCount];
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+ if (profileId != LOADED_PROFILE_ID) {
+ mLoadedUserIds.put(profileId, profileId);
+ newProfileIds[newMemberIndex] = profileId;
+ newMemberIndex++;
+ }
+ }
+
+ loadGroupWidgetProvidersLocked(newProfileIds);
+ loadGroupStateLocked(newProfileIds);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Permission Denial: can't dump from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+
+ synchronized (mLock) {
+ int N = mProviders.size();
pw.println("Providers:");
- for (int i=0; i<N; i++) {
- dumpProvider(mInstalledProviders.get(i), i, pw);
+ for (int i = 0; i < N; i++) {
+ dumpProvider(mProviders.get(i), i, pw);
}
- N = mAppWidgetIds.size();
+ N = mWidgets.size();
pw.println(" ");
- pw.println("AppWidgetIds:");
- for (int i=0; i<N; i++) {
- dumpAppWidgetId(mAppWidgetIds.get(i), i, pw);
+ pw.println("Widgets:");
+ for (int i = 0; i < N; i++) {
+ dumpWidget(mWidgets.get(i), i, pw);
}
N = mHosts.size();
pw.println(" ");
pw.println("Hosts:");
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
dumpHost(mHosts.get(i), i, pw);
}
- N = mDeletedProviders.size();
- pw.println(" ");
- pw.println("Deleted Providers:");
- for (int i=0; i<N; i++) {
- dumpProvider(mDeletedProviders.get(i), i, pw);
- }
- N = mDeletedHosts.size();
+ N = mPackagesWithBindWidgetPermission.size();
pw.println(" ");
- pw.println("Deleted Hosts:");
- for (int i=0; i<N; i++) {
- dumpHost(mDeletedHosts.get(i), i, pw);
+ pw.println("Grants:");
+ for (int i = 0; i < N; i++) {
+ Pair<Integer, String> grant = mPackagesWithBindWidgetPermission.valueAt(i);
+ dumpGrant(grant, i, pw);
}
}
}
- private void ensureStateLoadedLocked() {
- if (!mStateLoaded) {
- if (!mHasFeature) {
- return;
+ @Override
+ public int[] startListening(IAppWidgetHost callbacks, String callingPackage,
+ int hostId, List<RemoteViews> updatedViews) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "startListening() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupOrAddHostLocked(id);
+
+ host.callbacks = callbacks;
+
+ updatedViews.clear();
+
+ ArrayList<Widget> instances = host.widgets;
+ int N = instances.size();
+ int[] updatedIds = new int[N];
+ for (int i = 0; i < N; i++) {
+ Widget widget = instances.get(i);
+ updatedIds[i] = widget.appWidgetId;
+ updatedViews.add(cloneIfLocalBinder(widget.views));
}
- loadWidgetProviderListLocked();
- loadStateLocked();
- mStateLoaded = true;
+
+ return updatedIds;
}
}
- public int allocateAppWidgetId(String packageName, int hostId) {
- int callingUid = enforceSystemOrCallingUid(packageName);
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return -1;
+ @Override
+ public void stopListening(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "stopListening() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ host.callbacks = null;
+ pruneHostLocked(host);
}
- ensureStateLoadedLocked();
- int appWidgetId = mNextAppWidgetId++;
+ }
+ }
- Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
+ @Override
+ public int allocateAppWidgetId(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
- AppWidgetId id = new AppWidgetId();
- id.appWidgetId = appWidgetId;
- id.host = host;
+ if (DEBUG) {
+ Slog.i(TAG, "allocateAppWidgetId() " + userId);
+ }
- host.instances.add(id);
- mAppWidgetIds.add(id);
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
- saveStateAsync();
- if (DBG) log("Allocating AppWidgetId for " + packageName + " host=" + hostId
- + " id=" + appWidgetId);
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
+ mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
+ }
+
+ final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts it owns.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupOrAddHostLocked(id);
+
+ Widget widget = new Widget();
+ widget.appWidgetId = appWidgetId;
+ widget.host = host;
+
+ host.widgets.add(widget);
+ mWidgets.add(widget);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Allocated widget id " + appWidgetId
+ + " for host " + host.id);
+ }
+
return appWidgetId;
}
}
- public void deleteAppWidgetId(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
+ @Override
+ public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteAppWidgetId() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
return;
}
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null) {
- deleteAppWidgetLocked(id);
- saveStateAsync();
+
+ deleteAppWidgetLocked(widget);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted widget id " + appWidgetId
+ + " for host " + widget.host.id);
}
}
}
- public void deleteHost(int hostId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- Host host = lookupHostLocked(callingUid, hostId);
- if (host != null) {
- deleteHostLocked(host);
- saveStateAsync();
- }
+ @Override
+ public boolean hasBindAppWidgetPermission(String packageName, int grantId) {
+ if (DEBUG) {
+ Slog.i(TAG, "hasBindAppWidgetPermission() " + UserHandle.getCallingUserId());
}
- }
- public void deleteAllHosts() {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- final int N = mHosts.size();
- boolean changed = false;
- for (int i = N - 1; i >= 0; i--) {
- Host host = mHosts.get(i);
- if (host.uidMatches(callingUid)) {
- deleteHostLocked(host);
- changed = true;
- }
- }
- if (changed) {
- saveStateAsync();
- }
- }
- }
+ // A special permission is required for managing white listing.
+ mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
- void deleteHostLocked(Host host) {
- if (DBG) log("Deleting host " + host);
- final int N = host.instances.size();
- for (int i = N - 1; i >= 0; i--) {
- AppWidgetId id = host.instances.get(i);
- deleteAppWidgetLocked(id);
- }
- host.instances.clear();
- mHosts.remove(host);
- mDeletedHosts.add(host);
- // it's gone or going away, abruptly drop the callback connection
- host.callbacks = null;
- }
+ synchronized (mLock) {
+ // The grants are stored in user state wich gets the grant.
+ ensureGroupStateLoadedLocked(grantId);
- void deleteAppWidgetLocked(AppWidgetId id) {
- // We first unbind all services that are bound to this id
- unbindAppWidgetRemoteViewsServicesLocked(id);
-
- Host host = id.host;
- host.instances.remove(id);
- pruneHostLocked(host);
-
- mAppWidgetIds.remove(id);
-
- Provider p = id.provider;
- if (p != null) {
- p.instances.remove(id);
- if (!p.zombie) {
- // send the broacast saying that this appWidgetId has been deleted
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
- intent.setComponent(p.info.provider);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- if (p.instances.size() == 0) {
- // cancel the future updates
- cancelBroadcasts(p);
-
- // send the broacast saying that the provider is not in use any more
- intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
- intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- }
- }
- }
- }
-
- void cancelBroadcasts(Provider p) {
- if (DBG) log("cancelBroadcasts for " + p);
- if (p.broadcast != null) {
- mAlarmManager.cancel(p.broadcast);
- long token = Binder.clearCallingIdentity();
- try {
- p.broadcast.cancel();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- p.broadcast = null;
- }
- }
-
- private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {
- if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId
- + " provider=" + provider);
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- options = cloneIfLocalBinder(options);
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
- }
- if (id.provider != null) {
- throw new IllegalArgumentException("appWidgetId " + appWidgetId
- + " already bound to " + id.provider.info.provider);
- }
- Provider p = lookupProviderLocked(provider);
- if (p == null) {
- throw new IllegalArgumentException("not a appwidget provider: " + provider);
- }
- if (p.zombie) {
- throw new IllegalArgumentException("can't bind to a 3rd party provider in"
- + " safe mode: " + provider);
- }
-
- id.provider = p;
- if (options == null) {
- options = new Bundle();
- }
- id.options = options;
-
- // We need to provide a default value for the widget category if it is not specified
- if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
- }
-
- p.instances.add(id);
- int instancesSize = p.instances.size();
- if (instancesSize == 1) {
- // tell the provider that it's ready
- sendEnableIntentLocked(p);
- }
-
- // send an update now -- We need this update now, and just for this appWidgetId.
- // It's less critical when the next one happens, so when we schedule the next one,
- // we add updatePeriodMillis to its start time. That time will have some slop,
- // but that's okay.
- sendUpdateIntentLocked(p, new int[] { appWidgetId });
-
- // schedule the future updates
- registerForBroadcastsLocked(p, getAppWidgetIds(p));
- saveStateAsync();
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET,
- "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider);
- bindAppWidgetIdImpl(appWidgetId, provider, options);
- }
-
- public boolean bindAppWidgetIdIfAllowed(
- String packageName, int appWidgetId, ComponentName provider, Bundle options) {
- if (!mHasFeature) {
- return false;
- }
- try {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null);
- } catch (SecurityException se) {
- if (!callerHasBindAppWidgetPermission(packageName)) {
+ final int packageUid = getUidForPackage(packageName, grantId);
+ if (packageUid < 0) {
return false;
}
+
+ Pair<Integer, String> packageId = Pair.create(grantId, packageName);
+ return mPackagesWithBindWidgetPermission.contains(packageId);
}
- bindAppWidgetIdImpl(appWidgetId, provider, options);
+ }
+
+ @Override
+ public void setBindAppWidgetPermission(String packageName, int grantId,
+ boolean grantPermission) {
+ if (DEBUG) {
+ Slog.i(TAG, "setBindAppWidgetPermission() " + UserHandle.getCallingUserId());
+ }
+
+ // A special permission is required for managing white listing.
+ mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
+
+ synchronized (mLock) {
+ // The grants are stored in user state wich gets the grant.
+ ensureGroupStateLoadedLocked(grantId);
+
+ final int packageUid = getUidForPackage(packageName, grantId);
+ if (packageUid < 0) {
+ return;
+ }
+
+ Pair<Integer, String> packageId = Pair.create(grantId, packageName);
+ if (grantPermission) {
+ mPackagesWithBindWidgetPermission.add(packageId);
+ } else {
+ mPackagesWithBindWidgetPermission.remove(packageId);
+ }
+
+ saveGroupStateAsync(grantId);
+ }
+ }
+
+ @Override
+ public IntentSender createAppWidgetConfigIntentSender(String callingPackage, Intent intent) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "createAppWidgetConfigIntentSender() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ // The only allowed action is the one to start the configure activity.
+ if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
+ throw new IllegalArgumentException("Only allowed action is "
+ + AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ }
+
+ // Verify that widget id is provided.
+ final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ throw new IllegalArgumentException("Widget id required");
+ }
+
+ // Make sure a component name is provided.
+ ComponentName component = intent.getComponent();
+ if (component == null) {
+ throw new IllegalArgumentException("Component name required");
+ }
+
+ // Verify the user handle.
+ UserHandle userHandle = intent.getParcelableExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
+ if (userHandle != null) {
+ // Remove the profile extra as the receiver already runs under this
+ // user and this information is of no use to this receiver.
+ intent.removeExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
+
+ // If the user handle is not the caller, check if it is an enabled
+ // profile for which the package is white-listed.
+ final int profileId = userHandle.getIdentifier();
+ if (profileId != userId) {
+ // Make sure the passed user handle is a profile in the group.
+ final int[] profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(
+ new int[]{profileId});
+ if (profileIds.length <= 0) {
+ // The profile is not in the group or not enabled, done.
+ return null;
+ }
+
+ // Make sure the provider is white-listed.
+ if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ component.getPackageName(), profileId)) {
+ throw new IllegalArgumentException("Cannot access provider "
+ + component + " in user " + profileIds);
+ }
+ }
+ } else {
+ // If a profile is not specified use the caller user id.
+ userHandle = new UserHandle(userId);
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+ }
+
+ Provider provider = widget.provider;
+ if (provider == null) {
+ throw new IllegalArgumentException("Widget not bound " + appWidgetId);
+ }
+
+ // Make sure the component refers to the provider config activity.
+ if (!component.equals(provider.info.configure)
+ || !provider.info.getProfile().equals(userHandle)) {
+ throw new IllegalArgumentException("No component" + component
+ + " for user " + userHandle.getIdentifier());
+ }
+
+ // All right, create the sender.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle)
+ .getIntentSender();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public boolean bindAppWidgetId(String callingPackage, int appWidgetId,
+ int providerProfileId, ComponentName providerComponent, Bundle options) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "bindAppWidgetId() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ // Check that if a cross-profile binding is attempted, it is allowed.
+ final int[] profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(
+ new int[] {providerProfileId});
+ if (profileIds.length <= 0) {
+ return false;
+ }
+
+ // If the provider is not under the calling user, make sure this
+ // provider is white listed for access from the parent.
+ if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ providerComponent.getPackageName(), providerProfileId)) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // A special permission or white listing is required to bind widgets.
+ if (!mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
+ callingPackage)) {
+ return false;
+ }
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ Slog.e(TAG, "Bad widget id " + appWidgetId);
+ return false;
+ }
+
+ if (widget.provider != null) {
+ Slog.e(TAG, "Widget id " + appWidgetId
+ + " already bound to: " + widget.provider.id);
+ return false;
+ }
+
+ final int providerUid = getUidForPackage(providerComponent.getPackageName(),
+ providerProfileId);
+ if (providerUid < 0) {
+ Slog.e(TAG, "Package " + providerComponent.getPackageName() + " not installed "
+ + " for profile " + providerProfileId);
+ return false;
+ }
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the provider is in the already vetted user profile.
+ ProviderId providerId = new ProviderId(providerUid, providerComponent);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null) {
+ Slog.e(TAG, "No widget provider " + providerComponent + " for profile "
+ + providerProfileId);
+ return false;
+ }
+
+ if (provider.zombie) {
+ Slog.e(TAG, "Can't bind to a 3rd party provider in"
+ + " safe mode " + provider);
+ return false;
+ }
+
+ widget.provider = provider;
+ widget.options = (options != null) ? cloneIfLocalBinder(options) : new Bundle();
+
+ // We need to provide a default value for the widget category if it is not specified
+ if (!widget.options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
+ widget.options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
+ }
+
+ provider.widgets.add(widget);
+
+ final int widgetCount = provider.widgets.size();
+ if (widgetCount == 1) {
+ // Tell the provider that it's ready.
+ sendEnableIntentLocked(provider);
+ }
+
+ // Send an update now -- We need this update now, and just for this appWidgetId.
+ // It's less critical when the next one happens, so when we schedule the next one,
+ // we add updatePeriodMillis to its start time. That time will have some slop,
+ // but that's okay.
+ sendUpdateIntentLocked(provider, new int[] {appWidgetId});
+
+ // Schedule the future updates.
+ registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Bound widget " + appWidgetId + " to provider " + provider.id);
+ }
+ }
+
return true;
}
- private boolean callerHasBindAppWidgetPermission(String packageName) {
- int callingUid = Binder.getCallingUid();
- try {
- if (!UserHandle.isSameApp(callingUid, getUidForPackage(packageName))) {
- return false;
- }
- } catch (Exception e) {
- return false;
+ @Override
+ public int[] getAppWidgetIds(ComponentName componentName) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetIds() " + userId);
}
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- return mPackagesWithBindWidgetPermission.contains(packageName);
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can access only its providers.
+ ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider != null) {
+ return getWidgetIds(provider.widgets);
+ }
+
+ return new int[0];
}
}
- public boolean hasBindAppWidgetPermission(String packageName) {
- if (!mHasFeature) {
- return false;
- }
- mContext.enforceCallingPermission(
- android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
- "hasBindAppWidgetPermission packageName=" + packageName);
+ @Override
+ public int[] getAppWidgetIdsForHost(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- return mPackagesWithBindWidgetPermission.contains(packageName);
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetIdsForHost() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access its hosts.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host != null) {
+ return getWidgetIds(host.widgets);
+ }
+
+ return new int[0];
}
}
- public void setBindAppWidgetPermission(String packageName, boolean permission) {
- if (!mHasFeature) {
- return;
+ @Override
+ public void bindRemoteViewsService(String callingPackage, int appWidgetId,
+ Intent intent, IBinder callbacks) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "bindRemoteViewsService() " + userId);
}
- mContext.enforceCallingPermission(
- android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
- "setBindAppWidgetPermission packageName=" + packageName);
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- if (permission) {
- mPackagesWithBindWidgetPermission.add(packageName);
- } else {
- mPackagesWithBindWidgetPermission.remove(packageName);
- }
- saveStateAsync();
- }
- }
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
- // Binds to a specific RemoteViewsService
- public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
- }
- final ComponentName componentName = intent.getComponent();
- try {
- final ServiceInfo si = mPm.getServiceInfo(componentName,
- PackageManager.GET_PERMISSIONS, mUserId);
- if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
- throw new SecurityException("Selected service does not require "
- + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName);
- }
- } catch (RemoteException e) {
- throw new IllegalArgumentException("Unknown component " + componentName);
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id");
}
- // Ensure that the service specified by the passed intent belongs to the same package
- // as provides the passed widget id.
- String widgetIdPackage = id.provider.info.provider.getPackageName();
+ // Make sure the widget has a provider.
+ if (widget.provider == null) {
+ throw new IllegalArgumentException("No provider for widget "
+ + appWidgetId);
+ }
+
+ ComponentName componentName = intent.getComponent();
+
+ // Ensure that the service belongs to the same package as the provider.
+ // But this is not enough as they may be under different users - see below...
+ String providerPackage = widget.provider.id.componentName.getPackageName();
String servicePackage = componentName.getPackageName();
- if (!servicePackage.equals(widgetIdPackage)) {
- throw new SecurityException("Specified intent doesn't belong to the same package"
- + " as the provided AppWidget id");
+ if (!servicePackage.equals(providerPackage)) {
+ throw new SecurityException("The taget service not in the same package"
+ + " as the widget provider");
}
- // If there is already a connection made for this service intent, then disconnect from
- // that first. (This does not allow multiple connections to the same service under
- // the same key)
- ServiceConnectionProxy conn = null;
+ // Make sure this service exists under the same user as the provider and
+ // requires a permission which allows only the system to bind to it.
+ mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
+ componentName, widget.provider.getUserId());
+
+ // Good to go - the service pakcage is correct, it exists for the correct
+ // user, and requires the bind permission.
+
+ // If there is already a connection made for this service intent, then
+ // disconnect from that first. (This does not allow multiple connections
+ // to the same service under the same key).
+ ServiceConnectionProxy connection = null;
FilterComparison fc = new FilterComparison(intent);
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+
if (mBoundRemoteViewsServices.containsKey(key)) {
- conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
- conn.disconnect();
- mContext.unbindService(conn);
+ connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+ connection.disconnect();
+ unbindService(connection);
mBoundRemoteViewsServices.remove(key);
}
- int userId = UserHandle.getUserId(id.provider.uid);
- if (userId != mUserId) {
- Slog.w(TAG, "AppWidgetServiceImpl of user " + mUserId
- + " binding to provider on user " + userId);
- }
// Bind to the RemoteViewsService (which will trigger a callback to the
// RemoteViewsAdapter.onServiceConnected())
- final long token = Binder.clearCallingIdentity();
- try {
- conn = new ServiceConnectionProxy(key, connection);
- mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
- mBoundRemoteViewsServices.put(key, conn);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ connection = new ServiceConnectionProxy(callbacks);
+ bindService(intent, connection, widget.provider.info.getProfile());
+ mBoundRemoteViewsServices.put(key, connection);
- // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine
- // when we can call back to the RemoteViewsService later to destroy associated
- // factories.
- incrementAppWidgetServiceRefCount(appWidgetId, fc);
+ // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+ // can determine when we can call back to the RemoteViewsService later to
+ // destroy associated factories.
+ Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
+ incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
}
}
- // Unbinds from a specific RemoteViewsService
- public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
+ @Override
+ public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "unbindRemoteViewsService() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
// RemoteViewsAdapter)
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, new FilterComparison(
- intent));
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
+ new FilterComparison(intent));
if (mBoundRemoteViewsServices.containsKey(key)) {
// We don't need to use the appWidgetId until after we are sure there is something
// to unbind. Note that this may mask certain issues with apps calling unbind()
// more than necessary.
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id == null) {
- throw new IllegalArgumentException("bad appWidgetId");
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ throw new IllegalArgumentException("Bad widget id " + appWidgetId);
}
- ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices
- .get(key);
- conn.disconnect();
- mContext.unbindService(conn);
+ ServiceConnectionProxy connection = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
+ connection.disconnect();
+ mContext.unbindService(connection);
mBoundRemoteViewsServices.remove(key);
}
}
}
+ @Override
+ public void deleteHost(String callingPackage, int hostId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteHost() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access hosts in its uid and package.
+ HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
+ Host host = lookupHostLocked(id);
+
+ if (host == null) {
+ return;
+ }
+
+ deleteHostLocked(host);
+
+ saveGroupStateAsync(userId);
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted host " + host.id);
+ }
+ }
+ }
+
+ @Override
+ public void deleteAllHosts() {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "deleteAllHosts() " + userId);
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ boolean changed = false;
+
+ final int N = mHosts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+
+ // Delete only hosts in the calling uid.
+ if (host.id.uid == Binder.getCallingUid()) {
+ deleteHostLocked(host);
+ changed = true;
+
+ if (DEBUG) {
+ Slog.i(TAG, "Deleted host " + host.id);
+ }
+ }
+ }
+
+ if (changed) {
+ saveGroupStateAsync(userId);
+ }
+ }
+ }
+
+ @Override
+ public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetInfo() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null && widget.provider != null && !widget.provider.zombie) {
+ return cloneIfLocalBinder(widget.provider.info);
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetViews() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ return cloneIfLocalBinder(widget.views);
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetOptions() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget == null) {
+ return;
+ }
+
+ // Merge the options.
+ widget.options.putAll(options);
+
+ // Send the broacast to notify the provider that options changed.
+ sendOptionsChangedIntentLocked(widget);
+
+ saveGroupStateAsync(userId);
+ }
+ }
+
+ @Override
+ public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getAppWidgetOptions() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null && widget.options != null) {
+ return cloneIfLocalBinder(widget.options);
+ }
+
+ return Bundle.EMPTY;
+ }
+ }
+
+ @Override
+ public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views) {
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
+ }
+
+ updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
+ }
+
+ @Override
+ public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views) {
+ if (DEBUG) {
+ Slog.i(TAG, "partiallyUpdateAppWidgetIds() " + UserHandle.getCallingUserId());
+ }
+
+ updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
+ }
+
+ @Override
+ public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
+ int viewId) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "notifyAppWidgetViewDataChanged() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ if (appWidgetIds == null || appWidgetIds.length == 0) {
+ return;
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = appWidgetIds.length;
+ for (int i = 0; i < N; i++) {
+ final int appWidgetId = appWidgetIds[i];
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ scheduleNotifyAppWidgetViewDataChanged(widget, viewId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "updateAppWidgetProvider() " + userId);
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can access only its providers.
+ ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null) {
+ Slog.w(TAG, "Provider doesn't exist " + providerId);
+ return;
+ }
+
+ ArrayList<Widget> instances = provider.widgets;
+ final int N = instances.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = instances.get(i);
+ updateAppWidgetInstanceLocked(widget, views, false);
+ }
+ }
+ }
+
+ @Override
+ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int[] profileIds) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (DEBUG) {
+ Slog.i(TAG, "getInstalledProvidersForProfiles() " + userId);
+ }
+
+ if (profileIds != null && profileIds.length > 0) {
+ // Make sure the profile ids are children of the calling user.
+ profileIds = mSecurityPolicy.resolveCallerEnabledGroupProfiles(profileIds);
+ } else {
+ profileIds = new int[] {userId};
+ }
+
+ if (profileIds.length == 0) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ ArrayList<AppWidgetProviderInfo> result = new ArrayList<>();
+
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ AppWidgetProviderInfo info = provider.info;
+
+ // Ignore an invalid provider or one not matching the filter.
+ if (provider.zombie || (info.widgetCategory & categoryFilter) == 0) {
+ continue;
+ }
+
+ // Add providers only for the requested profiles ...
+ final int providerProfileId = info.getProfile().getIdentifier();
+ final int profileCount = profileIds.length;
+ for (int j = 0; j < profileCount; j++) {
+ final int profileId = profileIds[j];
+ if (providerProfileId == profileId) {
+ // ... that are white-listed by the profile manager.
+ if (mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
+ provider.id.componentName.getPackageName(), providerProfileId)) {
+ result.add(cloneIfLocalBinder(info));
+ }
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+ }
+
+ private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
+ RemoteViews views, boolean partially) {
+ final int userId = UserHandle.getCallingUserId();
+
+ if (appWidgetIds == null || appWidgetIds.length == 0) {
+ return;
+ }
+
+ // Make sure the package runs under the caller uid.
+ mSecurityPolicy.enforceCallFromPackage(callingPackage);
+
+ final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;
+ if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
+ throw new IllegalArgumentException("RemoteViews for widget update exceeds"
+ + " maximum bitmap memory usage (used: " + bitmapMemoryUsage
+ + ", max: " + mMaxWidgetBitmapMemory + ")");
+ }
+
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = appWidgetIds.length;
+ for (int i = 0; i < N; i++) {
+ final int appWidgetId = appWidgetIds[i];
+
+ // NOTE: The lookup is enforcing security across users by making
+ // sure the caller can only access widgets it hosts or provides.
+ Widget widget = lookupWidgetLocked(appWidgetId,
+ Binder.getCallingUid(), callingPackage);
+
+ if (widget != null) {
+ updateAppWidgetInstanceLocked(widget, views, partially);
+ }
+ }
+ }
+ }
+
+ private int incrementAndGetAppWidgetIdLocked(int userId) {
+ final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
+ mNextAppWidgetIds.put(userId, appWidgetId);
+ return appWidgetId;
+ }
+
+ private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
+ final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
+ if (nextAppWidgetId < minWidgetId) {
+ mNextAppWidgetIds.put(userId, minWidgetId);
+ }
+ }
+
+ private int peekNextAppWidgetIdLocked(int userId) {
+ if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
+ return AppWidgetManager.INVALID_APPWIDGET_ID + 1;
+ } else {
+ return mNextAppWidgetIds.get(userId);
+ }
+ }
+
+ private Host lookupOrAddHostLocked(HostId id) {
+ Host host = lookupHostLocked(id);
+ if (host != null) {
+ return host;
+ }
+
+ host = new Host();
+ host.id = id;
+ mHosts.add(host);
+
+ return host;
+ }
+
+ private void deleteHostLocked(Host host) {
+ final int N = host.widgets.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Widget widget = host.widgets.remove(i);
+ deleteAppWidgetLocked(widget);
+ }
+ mHosts.remove(host);
+
+ // it's gone or going away, abruptly drop the callback connection
+ host.callbacks = null;
+ }
+
+ private void deleteAppWidgetLocked(Widget widget) {
+ // We first unbind all services that are bound to this id
+ unbindAppWidgetRemoteViewsServicesLocked(widget);
+
+ Host host = widget.host;
+ host.widgets.remove(widget);
+ pruneHostLocked(host);
+
+ mWidgets.remove(widget);
+
+ Provider provider = widget.provider;
+ if (provider != null) {
+ provider.widgets.remove(widget);
+ if (!provider.zombie) {
+ // send the broacast saying that this appWidgetId has been deleted
+ sendDeletedIntentLocked(widget);
+
+ if (provider.widgets.isEmpty()) {
+ // cancel the future updates
+ cancelBroadcasts(provider);
+
+ // send the broacast saying that the provider is not in use any more
+ sendDisabledIntentLocked(provider);
+ }
+ }
+ }
+ }
+
+ private void cancelBroadcasts(Provider provider) {
+ if (DEBUG) {
+ Slog.i(TAG, "cancelBroadcasts() for " + provider);
+ }
+ if (provider.broadcast != null) {
+ mAlarmManager.cancel(provider.broadcast);
+ long token = Binder.clearCallingIdentity();
+ try {
+ provider.broadcast.cancel();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ provider.broadcast = null;
+ }
+ }
+
// Unbinds from a RemoteViewsService when we delete an app widget
- private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
- int appWidgetId = id.appWidgetId;
+ private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
+ int appWidgetId = widget.appWidgetId;
// Unbind all connections to Services bound to this AppWidgetId
Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
.iterator();
while (it.hasNext()) {
final Pair<Integer, Intent.FilterComparison> key = it.next();
- if (key.first.intValue() == appWidgetId) {
- final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices
- .get(key);
+ if (key.first == appWidgetId) {
+ final ServiceConnectionProxy conn = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
conn.disconnect();
mContext.unbindService(conn);
it.remove();
@@ -937,337 +1508,132 @@
// Check if we need to destroy any services (if no other app widgets are
// referencing the same service)
- decrementAppWidgetServiceRefCount(id);
+ decrementAppWidgetServiceRefCount(widget);
}
// Destroys the cached factory on the RemoteViewsService's side related to the specified intent
- private void destroyRemoteViewsService(final Intent intent, AppWidgetId id) {
+ private void destroyRemoteViewsService(final Intent intent, Widget widget) {
final ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service);
try {
cb.onDestroy(intent);
- } catch (RemoteException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error calling remove view factory", re);
}
mContext.unbindService(this);
}
@Override
- public void onServiceDisconnected(android.content.ComponentName name) {
+ public void onServiceDisconnected(ComponentName name) {
// Do nothing
}
};
- int userId = UserHandle.getUserId(id.provider.uid);
// Bind to the service and remove the static intent->factory mapping in the
// RemoteViewsService.
final long token = Binder.clearCallingIdentity();
try {
mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
+ widget.provider.info.getProfile());
} finally {
Binder.restoreCallingIdentity(token);
}
}
// Adds to the ref-count for a given RemoteViewsService intent
- private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) {
+ private void incrementAppWidgetServiceRefCount(int appWidgetId,
+ Pair<Integer, FilterComparison> serviceId) {
HashSet<Integer> appWidgetIds = null;
- if (mRemoteViewsServicesAppWidgets.containsKey(fc)) {
- appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc);
+ if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
+ appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
} else {
- appWidgetIds = new HashSet<Integer>();
- mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds);
+ appWidgetIds = new HashSet<>();
+ mRemoteViewsServicesAppWidgets.put(serviceId, appWidgetIds);
}
appWidgetIds.add(appWidgetId);
}
// Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
// the ref-count reaches zero.
- private void decrementAppWidgetServiceRefCount(AppWidgetId id) {
- Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator();
+ private void decrementAppWidgetServiceRefCount(Widget widget) {
+ Iterator<Pair<Integer, FilterComparison>> it = mRemoteViewsServicesAppWidgets
+ .keySet().iterator();
while (it.hasNext()) {
- final FilterComparison key = it.next();
+ final Pair<Integer, FilterComparison> key = it.next();
final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
- if (ids.remove(id.appWidgetId)) {
+ if (ids.remove(widget.appWidgetId)) {
// If we have removed the last app widget referencing this service, then we
// should destroy it and remove it from this set
if (ids.isEmpty()) {
- destroyRemoteViewsService(key.getIntent(), id);
+ destroyRemoteViewsService(key.second.getIntent(), widget);
it.remove();
}
}
}
}
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return null;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null && id.provider != null && !id.provider.zombie) {
- return cloneIfLocalBinder(id.provider.info);
- }
- return null;
- }
+ private void saveGroupStateAsync(int groupId) {
+ mSaveStateHandler.post(new SaveStateRunnable(groupId));
}
- public RemoteViews getAppWidgetViews(int appWidgetId) {
- if (DBG) log("getAppWidgetViews id=" + appWidgetId);
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return null;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null) {
- return cloneIfLocalBinder(id.views);
- }
- if (DBG) log(" couldn't find appwidgetid");
- return null;
- }
- }
+ private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
+ boolean isPartialUpdate) {
+ if (widget != null && widget.provider != null
+ && !widget.provider.zombie && !widget.host.zombie) {
- public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return new ArrayList<AppWidgetProviderInfo>(0);
- }
- ensureStateLoadedLocked();
- final int N = mInstalledProviders.size();
- ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N);
- for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (!p.zombie && (p.info.widgetCategory & categoryFilter) != 0) {
- result.add(cloneIfLocalBinder(p.info));
- }
- }
- return result;
- }
- }
-
- public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
- if (!mHasFeature) {
- return;
- }
- if (appWidgetIds == null) {
- return;
- }
- if (DBG) log("updateAppWidgetIds views: " + views);
- int bitmapMemoryUsage = 0;
- if (views != null) {
- bitmapMemoryUsage = views.estimateMemoryUsage();
- }
- if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {
- throw new IllegalArgumentException("RemoteViews for widget update exceeds maximum" +
- " bitmap memory usage (used: " + bitmapMemoryUsage + ", max: " +
- mMaxWidgetBitmapMemory + ") The total memory cannot exceed that required to" +
- " fill the device's screen once.");
- }
-
- if (appWidgetIds.length == 0) {
- return;
- }
- final int N = appWidgetIds.length;
-
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- updateAppWidgetInstanceLocked(id, views);
- }
- }
- }
-
- private void saveStateAsync() {
- mSaveStateHandler.post(mSaveStateRunnable);
- }
-
- private final Runnable mSaveStateRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- saveStateLocked();
- }
- }
- };
-
- public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
-
- if (id == null) {
- return;
- }
-
- Provider p = id.provider;
- // Merge the options
- id.options.putAll(cloneIfLocalBinder(options));
-
- // send the broacast saying that this appWidgetId has been deleted
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
- intent.setComponent(p.info.provider);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, id.options);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- saveStateAsync();
- }
- }
-
- public Bundle getAppWidgetOptions(int appWidgetId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return Bundle.EMPTY;
- }
- ensureStateLoadedLocked();
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
- if (id != null && id.options != null) {
- return cloneIfLocalBinder(id.options);
- } else {
- return Bundle.EMPTY;
- }
- }
- }
-
- public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
- if (!mHasFeature) {
- return;
- }
- if (appWidgetIds == null) {
- return;
- }
- if (appWidgetIds.length == 0) {
- return;
- }
- final int N = appWidgetIds.length;
-
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- if (id == null) {
- Slog.w(TAG, "widget id " + appWidgetIds[i] + " not found!");
- } else if (id.views != null) {
- // Only trigger a partial update for a widget if it has received a full update
- updateAppWidgetInstanceLocked(id, views, true);
- }
- }
- }
- }
-
- public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
- if (!mHasFeature) {
- return;
- }
- if (appWidgetIds == null) {
- return;
- }
- if (appWidgetIds.length == 0) {
- return;
- }
- final int N = appWidgetIds.length;
-
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
- notifyAppWidgetViewDataChangedInstanceLocked(id, viewId);
- }
- }
- }
-
- public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
- if (!mHasFeature) {
- return;
- }
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Provider p = lookupProviderLocked(provider);
- if (p == null) {
- Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider);
- return;
- }
- ArrayList<AppWidgetId> instances = p.instances;
- final int callingUid = Binder.getCallingUid();
- final int N = instances.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- if (canAccessAppWidgetId(id, callingUid)) {
- updateAppWidgetInstanceLocked(id, views);
- }
- }
- }
- }
-
- void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
- updateAppWidgetInstanceLocked(id, views, false);
- }
-
- void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) {
- // allow for stale appWidgetIds and other badness
- // lookup also checks that the calling process can access the appWidgetId
- // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
- if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
-
- if (!isPartialUpdate) {
- // For a full update we replace the RemoteViews completely.
- id.views = views;
- } else {
+ if (isPartialUpdate && widget.views != null) {
// For a partial update, we merge the new RemoteViews with the old.
- id.views.mergeRemoteViews(views);
+ widget.views.mergeRemoteViews(views);
+ } else {
+ // For a full update we replace the RemoteViews completely.
+ widget.views = views;
}
- // is anyone listening?
- if (id.host.callbacks != null) {
- try {
- // the lock is held, but this is a oneway call
- id.host.callbacks.updateAppWidget(id.appWidgetId, views, mUserId);
- } catch (RemoteException e) {
- // It failed; remove the callback. No need to prune because
- // we know that this host is still referenced by this instance.
- id.host.callbacks = null;
- }
- }
+ scheduleNotifyUpdateAppWidgetLocked(widget);
}
}
- void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) {
- // allow for stale appWidgetIds and other badness
- // lookup also checks that the calling process can access the appWidgetId
- // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
- if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
- // is anyone listening?
- if (id.host.callbacks != null) {
- try {
- // the lock is held, but this is a oneway call
- id.host.callbacks.viewDataChanged(id.appWidgetId, viewId, mUserId);
- } catch (RemoteException e) {
- // It failed; remove the callback. No need to prune because
- // we know that this host is still referenced by this instance.
- id.host.callbacks = null;
- }
- }
+ private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
+ if (widget == null || widget.host == null || widget.host.zombie
+ || widget.host.callbacks == null || widget.provider == null
+ || widget.provider.zombie) {
+ return;
+ }
- // If the host is unavailable, then we call the associated
- // RemoteViewsFactory.onDataSetChanged() directly
- if (id.host.callbacks == null) {
- Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet();
- for (FilterComparison key : keys) {
- if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) {
- Intent intent = key.getIntent();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.argi1 = widget.appWidgetId;
+ args.argi2 = viewId;
- final ServiceConnection conn = new ServiceConnection() {
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_VIEW_DATA_CHANGED,
+ args).sendToTarget();
+ }
+
+
+ private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, int viewId) {
+ try {
+ callbacks.viewDataChanged(appWidgetId, viewId);
+ } catch (RemoteException re) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this instance.
+ callbacks = null;
+ }
+
+ // If the host is unavailable, then we call the associated
+ // RemoteViewsFactory.onDataSetChanged() directly
+ synchronized (mLock) {
+ if (callbacks == null) {
+ host.callbacks = null;
+
+ Set<Pair<Integer, FilterComparison>> keys = mRemoteViewsServicesAppWidgets.keySet();
+ for (Pair<Integer, FilterComparison> key : keys) {
+ if (mRemoteViewsServicesAppWidgets.get(key).contains(appWidgetId)) {
+ final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
@@ -1275,9 +1641,7 @@
try {
cb.onDataSetChangedAsync();
} catch (RemoteException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
+ Slog.e(TAG, "Error calling onDataSetChangedAsync()", e);
}
mContext.unbindService(this);
}
@@ -1288,40 +1652,124 @@
}
};
- int userId = UserHandle.getUserId(id.provider.uid);
+ final int userId = UserHandle.getUserId(key.first);
+ Intent intent = key.second.getIntent();
+
// Bind to the service and call onDataSetChanged()
- final long token = Binder.clearCallingIdentity();
- try {
- mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
- new UserHandle(userId));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ bindService(intent, connection, new UserHandle(userId));
}
}
}
}
}
- private boolean isLocalBinder() {
+ private void scheduleNotifyUpdateAppWidgetLocked(Widget widget) {
+ if (widget == null || widget.provider == null || widget.provider.zombie
+ || widget.host.callbacks == null || widget.host.zombie) {
+ return;
+ }
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.arg3 = widget.views;
+ args.argi1 = widget.appWidgetId;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
+ args).sendToTarget();
+ }
+
+ private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, RemoteViews views) {
+ try {
+ callbacks.updateAppWidget(appWidgetId, views);
+ } catch (RemoteException re) {
+ synchronized (mLock) {
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
+ }
+ }
+
+ private void scheduleNotifyProviderChangedLocked(Widget widget) {
+ if (widget == null || widget.provider == null || widget.provider.zombie
+ || widget.host.callbacks == null || widget.host.zombie) {
+ return;
+ }
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = widget.host;
+ args.arg2 = widget.host.callbacks;
+ args.arg3 = widget.provider.info;
+ args.argi1 = widget.appWidgetId;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED,
+ args).sendToTarget();
+ }
+
+ private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
+ int appWidgetId, AppWidgetProviderInfo info) {
+ try {
+ callbacks.providerChanged(appWidgetId, info);
+ } catch (RemoteException re) {
+ synchronized (mLock){
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
+ }
+ }
+
+ private void scheduleNotifyHostsForProvidersChangedLocked() {
+ final int N = mHosts.size();
+ for (int i = N - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+
+ if (host == null || host.zombie || host.callbacks == null) {
+ continue;
+ }
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = host;
+ args.arg2 = host.callbacks;
+
+ mCallbackHandler.obtainMessage(
+ CallbackHandler.MSG_NOTIFY_PROVIDERS_CHANGED,
+ args).sendToTarget();
+ }
+ }
+
+ private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
+ try {
+ callbacks.providersChanged();
+ } catch (RemoteException re) {
+ synchronized (mLock) {
+ Slog.e(TAG, "Widget host dead: " + host.id, re);
+ host.callbacks = null;
+ }
+ }
+ }
+
+ private static boolean isLocalBinder() {
return Process.myPid() == Binder.getCallingPid();
}
- private RemoteViews cloneIfLocalBinder(RemoteViews rv) {
+ private static RemoteViews cloneIfLocalBinder(RemoteViews rv) {
if (isLocalBinder() && rv != null) {
return rv.clone();
}
return rv;
}
- private AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
+ private static AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
if (isLocalBinder() && info != null) {
return info.clone();
}
return info;
}
- private Bundle cloneIfLocalBinder(Bundle bundle) {
+ private static Bundle cloneIfLocalBinder(Bundle bundle) {
// Note: this is only a shallow copy. For now this will be fine, but it could be problematic
// if we start adding objects to the options. Further, it would only be an issue if keyguard
// used such options.
@@ -1331,832 +1779,326 @@
return bundle;
}
- public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
- List<RemoteViews> updatedViews) {
- if (!mHasFeature) {
- return new int[0];
- }
- int callingUid = enforceCallingUid(packageName);
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
- host.callbacks = callbacks;
-
- updatedViews.clear();
-
- ArrayList<AppWidgetId> instances = host.instances;
- int N = instances.size();
- int[] updatedIds = new int[N];
- for (int i = 0; i < N; i++) {
- AppWidgetId id = instances.get(i);
- updatedIds[i] = id.appWidgetId;
- updatedViews.add(cloneIfLocalBinder(id.views));
- }
- return updatedIds;
- }
- }
-
- public void stopListening(int hostId) {
- synchronized (mAppWidgetIds) {
- if (!mHasFeature) {
- return;
- }
- ensureStateLoadedLocked();
- Host host = lookupHostLocked(Binder.getCallingUid(), hostId);
- if (host != null) {
- host.callbacks = null;
- pruneHostLocked(host);
- }
- }
- }
-
- boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) {
- if (id.host.uidMatches(callingUid)) {
- // Apps hosting the AppWidget have access to it.
- return true;
- }
- if (id.provider != null && id.provider.uid == callingUid) {
- // Apps providing the AppWidget have access to it (if the appWidgetId has been bound)
- return true;
- }
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) {
- // Apps that can bind have access to all appWidgetIds.
- return true;
- }
- // Nobody else can access it.
- return false;
- }
-
- AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) {
- int callingUid = Binder.getCallingUid();
- final int N = mAppWidgetIds.size();
+ private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
+ final int N = mWidgets.size();
for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) {
- return id;
+ Widget widget = mWidgets.get(i);
+ if (widget.appWidgetId == appWidgetId
+ && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
+ return widget;
}
}
return null;
}
- Provider lookupProviderLocked(ComponentName provider) {
- return lookupProviderLocked(provider, mInstalledProviders);
- }
-
- Provider lookupProviderLocked(ComponentName provider, ArrayList<Provider> installedProviders) {
- final int N = installedProviders.size();
+ private Provider lookupProviderLocked(ProviderId id) {
+ final int N = mProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = installedProviders.get(i);
- if (p.info.provider.equals(provider)) {
- return p;
+ Provider provider = mProviders.get(i);
+ if (provider.id.equals(id)) {
+ return provider;
}
}
return null;
}
- Host lookupHostLocked(int uid, int hostId) {
+ private Host lookupHostLocked(HostId hostId) {
final int N = mHosts.size();
for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.uidMatches(uid) && h.hostId == hostId) {
- return h;
+ Host host = mHosts.get(i);
+ if (host.id.equals(hostId)) {
+ return host;
}
}
return null;
}
- Host lookupOrAddHostLocked(int uid, String packageName, int hostId) {
- final int N = mHosts.size();
- for (int i = 0; i < N; i++) {
- Host h = mHosts.get(i);
- if (h.hostId == hostId && h.packageName.equals(packageName)) {
- return h;
+ private void pruneHostLocked(Host host) {
+ if (host.widgets.size() == 0 && host.callbacks == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Pruning host " + host.id);
}
- }
- Host host = new Host();
- host.packageName = packageName;
- host.uid = uid;
- host.hostId = hostId;
- mHosts.add(host);
- return host;
- }
-
- void pruneHostLocked(Host host) {
- if (host.instances.size() == 0 && host.callbacks == null) {
- if (DBG) log("Pruning host " + host);
mHosts.remove(host);
}
}
- void loadWidgetProviderListLocked() {
+ private void loadGroupWidgetProvidersLocked(int[] profileIds) {
+ List<ResolveInfo> allReceivers = null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- try {
- List<ResolveInfo> broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo ri = broadcastReceivers.get(i);
- addProviderLocked(ri);
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+
+ List<ResolveInfo> receivers = queryIntentReceivers(intent, profileId);
+ if (receivers != null && !receivers.isEmpty()) {
+ if (allReceivers == null) {
+ allReceivers = new ArrayList<>();
+ }
+ allReceivers.addAll(receivers);
}
- } catch (RemoteException re) {
- // Shouldn't happen, local call
+ }
+
+ final int N = (allReceivers == null) ? 0 : allReceivers.size();
+ for (int i = 0; i < N; i++) {
+ ResolveInfo receiver = allReceivers.get(i);
+ addProviderLocked(receiver);
}
}
- boolean addProviderLocked(ResolveInfo ri) {
+ private boolean addProviderLocked(ResolveInfo ri) {
if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
return false;
}
+
if (!ri.activityInfo.isEnabled()) {
return false;
}
- Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
- ri.activityInfo.name), ri);
- if (p != null) {
+
+ ComponentName componentName = new ComponentName(ri.activityInfo.packageName,
+ ri.activityInfo.name);
+ ProviderId providerId = new ProviderId(ri.activityInfo.applicationInfo.uid, componentName);
+
+ Provider provider = parseProviderInfoXml(providerId, ri);
+ if (provider != null) {
// we might have an inactive entry for this provider already due to
// a preceding restore operation. if so, fix it up in place; otherwise
// just add this new one.
- Provider existing = lookupProviderLocked(p.info.provider);
+ Provider existing = lookupProviderLocked(providerId);
+
+ // If the provider was not found it may be because it was restored and
+ // we did not know its UID so let us find if there is such one.
+ if (existing == null) {
+ providerId = new ProviderId(UNKNOWN_UID, componentName);
+ existing = lookupProviderLocked(providerId);
+ }
+
if (existing != null) {
if (existing.zombie && !mSafeMode) {
// it's a placeholder that was set up during an app restore
existing.zombie = false;
- existing.info = p.info; // the real one filled out from the ResolveInfo
- existing.uid = p.uid;
- if (DEBUG_BACKUP) {
+ existing.info = provider.info; // the real one filled out from the ResolveInfo
+ if (DEBUG) {
Slog.i(TAG, "Provider placeholder now reified: " + existing);
}
}
} else {
- mInstalledProviders.add(p);
+ mProviders.add(provider);
}
return true;
- } else {
- return false;
}
+
+ return false;
}
- void removeProviderLocked(int index, Provider p) {
- int N = p.instances.size();
+ private void deleteProviderLocked(Provider provider) {
+ int N = provider.widgets.size();
for (int i = 0; i < N; i++) {
- AppWidgetId id = p.instances.get(i);
+ Widget widget = provider.widgets.remove(i);
// Call back with empty RemoteViews
- updateAppWidgetInstanceLocked(id, null);
- // Stop telling the host about updates for this from now on
- cancelBroadcasts(p);
+ updateAppWidgetInstanceLocked(widget, null, false);
// clear out references to this appWidgetId
- id.host.instances.remove(id);
- mAppWidgetIds.remove(id);
- id.provider = null;
- pruneHostLocked(id.host);
- id.host = null;
+ widget.host.widgets.remove(widget);
+ mWidgets.remove(widget);
+ widget.provider = null;
+ pruneHostLocked(widget.host);
+ widget.host = null;
}
- p.instances.clear();
- mInstalledProviders.remove(index);
- mDeletedProviders.add(p);
+ mProviders.remove(provider);
+
// no need to send the DISABLE broadcast, since the receiver is gone anyway
- cancelBroadcasts(p);
+ cancelBroadcasts(provider);
}
- void sendEnableIntentLocked(Provider p) {
+ private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
+ sendBroadcastAsUser(intent, p.info.getProfile());
}
- void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
- if (appWidgetIds != null && appWidgetIds.length > 0) {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
- intent.setComponent(p.info.provider);
- mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
- }
+ private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, provider.info.getProfile());
}
- void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {
- if (p.info.updatePeriodMillis > 0) {
+ private void sendDeletedIntentLocked(Widget widget) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
+ intent.setComponent(widget.provider.info.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
+ sendBroadcastAsUser(intent, widget.provider.info.getProfile());
+ }
+
+ private void sendDisabledIntentLocked(Provider provider) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, provider.info.getProfile());
+ }
+
+ public void sendOptionsChangedIntentLocked(Widget widget) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
+ intent.setComponent(widget.provider.info.provider);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
+ sendBroadcastAsUser(intent, widget.provider.info.getProfile());
+ }
+
+ private void registerForBroadcastsLocked(Provider provider, int[] appWidgetIds) {
+ if (provider.info.updatePeriodMillis > 0) {
// if this is the first instance, set the alarm. otherwise,
// rely on the fact that we've already set it and that
// PendingIntent.getBroadcast will update the extras.
- boolean alreadyRegistered = p.broadcast != null;
+ boolean alreadyRegistered = provider.broadcast != null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
- intent.setComponent(p.info.provider);
+ intent.setComponent(provider.info.provider);
long token = Binder.clearCallingIdentity();
try {
- p.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
- PendingIntent.FLAG_UPDATE_CURRENT, new UserHandle(mUserId));
+ provider.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT, provider.info.getProfile());
} finally {
Binder.restoreCallingIdentity(token);
}
if (!alreadyRegistered) {
- long period = p.info.updatePeriodMillis;
+ long period = provider.info.updatePeriodMillis;
if (period < MIN_UPDATE_PERIOD) {
period = MIN_UPDATE_PERIOD;
}
- mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock
- .elapsedRealtime()
- + period, period, p.broadcast);
+ mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + period, period, provider.broadcast);
}
}
}
- static int[] getAppWidgetIds(Provider p) {
- int instancesSize = p.instances.size();
+ private static int[] getWidgetIds(ArrayList<Widget> widgets) {
+ int instancesSize = widgets.size();
int appWidgetIds[] = new int[instancesSize];
for (int i = 0; i < instancesSize; i++) {
- appWidgetIds[i] = p.instances.get(i).appWidgetId;
+ appWidgetIds[i] = widgets.get(i).appWidgetId;
}
return appWidgetIds;
}
- public int[] getAppWidgetIds(ComponentName provider) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- Provider p = lookupProviderLocked(provider);
- if (p != null && Binder.getCallingUid() == p.uid) {
- return getAppWidgetIds(p);
- } else {
- return new int[0];
- }
+ private static void dumpProvider(Provider provider, int index, PrintWriter pw) {
+ AppWidgetProviderInfo info = provider.info;
+ pw.print(" ["); pw.print(index); pw.print("] provider ");
+ pw.println(provider.id);
+ pw.print(" min=("); pw.print(info.minWidth);
+ pw.print("x"); pw.print(info.minHeight);
+ pw.print(") minResize=("); pw.print(info.minResizeWidth);
+ pw.print("x"); pw.print(info.minResizeHeight);
+ pw.print(") updatePeriodMillis=");
+ pw.print(info.updatePeriodMillis);
+ pw.print(" resizeMode=");
+ pw.print(info.resizeMode);
+ pw.print(info.widgetCategory);
+ pw.print(" autoAdvanceViewId=");
+ pw.print(info.autoAdvanceViewId);
+ pw.print(" initialLayout=#");
+ pw.print(Integer.toHexString(info.initialLayout));
+ pw.print(" initialKeyguardLayout=#");
+ pw.print(Integer.toHexString(info.initialKeyguardLayout));
+ pw.print(" zombie="); pw.println(provider.zombie);
+ }
+
+ private static void dumpHost(Host host, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print("] hostId=");
+ pw.println(host.id);
+ pw.print(" callbacks="); pw.println(host.callbacks);
+ pw.print(" widgets.size="); pw.print(host.widgets.size());
+ pw.print(" zombie="); pw.println(host.zombie);
+ }
+
+ private static void dumpGrant(Pair<Integer, String> grant, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print(']');
+ pw.print(" user="); pw.print(grant.first);
+ pw.print(" package="); pw.println(grant.second);
+ }
+
+ private static void dumpWidget(Widget widget, int index, PrintWriter pw) {
+ pw.print(" ["); pw.print(index); pw.print("] id=");
+ pw.println(widget.appWidgetId);
+ pw.print(" host=");
+ pw.println(widget.host.id);
+ if (widget.provider != null) {
+ pw.print(" provider="); pw.println(widget.provider.id);
+ }
+ if (widget.host != null) {
+ pw.print(" host.callbacks="); pw.println(widget.host.callbacks);
+ }
+ if (widget.views != null) {
+ pw.print(" views="); pw.println(widget.views);
}
}
- static int[] getAppWidgetIds(Host h) {
- int instancesSize = h.instances.size();
- int appWidgetIds[] = new int[instancesSize];
- for (int i = 0; i < instancesSize; i++) {
- appWidgetIds[i] = h.instances.get(i).appWidgetId;
- }
- return appWidgetIds;
- }
-
- public int[] getAppWidgetIdsForHost(int hostId) {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- int callingUid = Binder.getCallingUid();
- Host host = lookupHostLocked(callingUid, hostId);
- if (host != null) {
- return getAppWidgetIds(host);
- } else {
- return new int[0];
- }
- }
- }
-
- public List<String> getWidgetParticipants() {
- HashSet<String> packages = new HashSet<String>();
- synchronized (mAppWidgetIds) {
- final int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- final AppWidgetId id = mAppWidgetIds.get(i);
- packages.add(id.host.packageName);
- packages.add(id.provider.info.provider.getPackageName());
- }
- }
- return new ArrayList<String>(packages);
- }
-
- private void serializeProvider(XmlSerializer out, Provider p) throws IOException {
+ private static void serializeProvider(XmlSerializer out, Provider p) throws IOException {
out.startTag(null, "p");
out.attribute(null, "pkg", p.info.provider.getPackageName());
out.attribute(null, "cl", p.info.provider.getClassName());
+ out.attribute(null, "tag", Integer.toHexString(p.tag));
out.endTag(null, "p");
}
- private void serializeHost(XmlSerializer out, Host host) throws IOException {
+ private static void serializeHost(XmlSerializer out, Host host) throws IOException {
out.startTag(null, "h");
- out.attribute(null, "pkg", host.packageName);
- out.attribute(null, "id", Integer.toHexString(host.hostId));
+ out.attribute(null, "pkg", host.id.packageName);
+ out.attribute(null, "id", Integer.toHexString(host.id.hostId));
+ out.attribute(null, "tag", Integer.toHexString(host.tag));
out.endTag(null, "h");
}
- private void serializeAppWidgetId(XmlSerializer out, AppWidgetId id) throws IOException {
+ private static void serializeAppWidget(XmlSerializer out, Widget widget) throws IOException {
out.startTag(null, "g");
- out.attribute(null, "id", Integer.toHexString(id.appWidgetId));
- out.attribute(null, "rid", Integer.toHexString(id.restoredId));
- out.attribute(null, "h", Integer.toHexString(id.host.tag));
- if (id.provider != null) {
- out.attribute(null, "p", Integer.toHexString(id.provider.tag));
+ out.attribute(null, "id", Integer.toHexString(widget.appWidgetId));
+ out.attribute(null, "rid", Integer.toHexString(widget.restoredId));
+ out.attribute(null, "h", Integer.toHexString(widget.host.tag));
+ if (widget.provider != null) {
+ out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
}
- if (id.options != null) {
- out.attribute(null, "min_width", Integer.toHexString(id.options.getInt(
+ if (widget.options != null) {
+ out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
- out.attribute(null, "min_height", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
- out.attribute(null, "max_width", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
- out.attribute(null, "max_height", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
- out.attribute(null, "host_category", Integer.toHexString(id.options.getInt(
+ out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
}
out.endTag(null, "g");
}
- private Bundle parseWidgetIdOptions(XmlPullParser parser) {
- Bundle options = new Bundle();
- String minWidthString = parser.getAttributeValue(null, "min_width");
- if (minWidthString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
- Integer.parseInt(minWidthString, 16));
- }
- String minHeightString = parser.getAttributeValue(null, "min_height");
- if (minHeightString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
- Integer.parseInt(minHeightString, 16));
- }
- String maxWidthString = parser.getAttributeValue(null, "max_width");
- if (maxWidthString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
- Integer.parseInt(maxWidthString, 16));
- }
- String maxHeightString = parser.getAttributeValue(null, "max_height");
- if (maxHeightString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
- Integer.parseInt(maxHeightString, 16));
- }
- String categoryString = parser.getAttributeValue(null, "host_category");
- if (categoryString != null) {
- options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
- Integer.parseInt(categoryString, 16));
- }
- return options;
+ @Override
+ public List<String> getWidgetParticipants(int userId) {
+ return mBackupRestoreController.getWidgetParticipants(userId);
}
- // Does this package either host or provide any active widgets?
- private boolean packageNeedsWidgetBackupLocked(String packageName) {
- int N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (packageName.equals(id.host.packageName)) {
- // this package is hosting widgets, so it knows widget IDs
- return true;
- }
- Provider p = id.provider;
- if (p != null && packageName.equals(p.info.provider.getPackageName())) {
- // someone is hosting this app's widgets, so it knows widget IDs
- return true;
- }
- }
- return false;
+ @Override
+ public byte[] getWidgetState(String packageName, int userId) {
+ return mBackupRestoreController.getWidgetState(packageName, userId);
}
- // build the widget-state blob that we save for the app during backup.
- public byte[] getWidgetState(String backupTarget) {
- if (!mHasFeature) {
- return null;
- }
-
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- synchronized (mAppWidgetIds) {
- // Preflight: if this app neither hosts nor provides any live widgets
- // we have no work to do.
- if (!packageNeedsWidgetBackupLocked(backupTarget)) {
- return null;
- }
-
- try {
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(stream, "utf-8");
- out.startDocument(null, true);
- out.startTag(null, "ws"); // widget state
- out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
- out.attribute(null, "pkg", backupTarget);
-
- // Remember all the providers that are currently hosted or published
- // by this package: that is, all of the entities related to this app
- // which will need to be told about id remapping.
- int N = mInstalledProviders.size();
- int index = 0;
- for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- if (backupTarget.equals(p.info.provider.getPackageName())
- || p.isHostedBy(backupTarget)) {
- serializeProvider(out, p);
- p.tag = index++;
- }
- }
- }
-
- N = mHosts.size();
- index = 0;
- for (int i = 0; i < N; i++) {
- Host host = mHosts.get(i);
- if (backupTarget.equals(host.packageName)
- || host.hostsPackage(backupTarget)) {
- serializeHost(out, host);
- host.tag = index++;
- }
- }
-
- // All widget instances involving this package,
- // either as host or as provider
- N = mAppWidgetIds.size();
- for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- if (backupTarget.equals(id.host.packageName)
- || (id.provider != null && backupTarget.equals(
- id.provider.info.provider.getPackageName()))) {
- serializeAppWidgetId(out, id);
- }
- }
-
- out.endTag(null, "ws");
- out.endDocument();
- } catch (IOException e) {
- Slog.w(TAG, "Unable to save widget state for " + backupTarget);
- return null;
- }
-
- }
- return stream.toByteArray();
+ @Override
+ public void restoreStarting(int userId) {
+ mBackupRestoreController.restoreStarting(userId);
}
- public void restoreStarting() {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "restore starting for user " + mUserId);
- }
- synchronized (mRestoredWidgetIds) {
- // We're starting a new "system" restore operation, so any widget restore
- // state that we see from here on is intended to replace the current
- // widget configuration of any/all of the affected apps.
- mPrunedApps.clear();
- mUpdatesByProvider.clear();
- mUpdatesByHost.clear();
- }
+ @Override
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ mBackupRestoreController.restoreWidgetState(packageName, restoredState, userId);
}
- // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
- // instances that are hosted by that app, and (b) all instances in other hosts
- // for which 'pkg' is the provider. We assume that we'll be restoring all of
- // these hosts & providers, so will be reconstructing a correct live state.
- private void pruneWidgetStateLr(String pkg) {
- if (!mPrunedApps.contains(pkg)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "pruning widget state for restoring package " + pkg);
- }
- for (int i = mAppWidgetIds.size() - 1; i >= 0; i--) {
- AppWidgetId id = mAppWidgetIds.get(i);
- Provider p = id.provider;
- if (id.host.packageName.equals(pkg)
- || p.info.provider.getPackageName().equals(pkg)) {
- // 'pkg' is either the host or the provider for this instances,
- // so we tear it down in anticipation of it (possibly) being
- // reconstructed due to the restore
- p.instances.remove(id);
-
- unbindAppWidgetRemoteViewsServicesLocked(id);
- mAppWidgetIds.remove(i);
- }
- }
- mPrunedApps.add(pkg);
- } else {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
- }
- }
+ @Override
+ public void restoreFinished(int userId) {
+ mBackupRestoreController.restoreFinished(userId);
}
- // Accumulate a list of updates that affect the given provider for a final
- // coalesced notification broadcast once restore is over.
- class RestoreUpdateRecord {
- public int oldId;
- public int newId;
- public boolean notified;
-
- public RestoreUpdateRecord(int theOldId, int theNewId) {
- oldId = theOldId;
- newId = theNewId;
- notified = false;
- }
- }
-
- HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider
- = new HashMap<Provider, ArrayList<RestoreUpdateRecord>>();
- HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost
- = new HashMap<Host, ArrayList<RestoreUpdateRecord>>();
-
- private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
- final int oldId, final int newId) {
- final int N = stash.size();
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = stash.get(i);
- if (r.oldId == oldId && r.newId == newId) {
- return true;
- }
- }
- return false;
- }
-
- private void stashProviderRestoreUpdateLr(Provider provider, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
- if (r == null) {
- r = new ArrayList<RestoreUpdateRecord>();
- mUpdatesByProvider.put(provider, r);
- } else {
- // don't duplicate
- if (alreadyStashed(r, oldId, newId)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "ID remap " + oldId + " -> " + newId
- + " already stashed for " + provider);
- }
- return;
- }
- }
- r.add(new RestoreUpdateRecord(oldId, newId));
- }
-
- private void stashHostRestoreUpdateLr(Host host, int oldId, int newId) {
- ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
- if (r == null) {
- r = new ArrayList<RestoreUpdateRecord>();
- mUpdatesByHost.put(host, r);
- } else {
- if (alreadyStashed(r, oldId, newId)) {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "ID remap " + oldId + " -> " + newId
- + " already stashed for " + host);
- }
- return;
- }
- }
- r.add(new RestoreUpdateRecord(oldId, newId));
- }
-
- public void restoreWidgetState(String packageName, byte[] restoredState) {
- if (!mHasFeature) {
- return;
- }
-
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Restoring widget state for " + packageName);
- }
-
- ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
- try {
- // Providers mentioned in the widget dataset by ordinal
- ArrayList<Provider> restoredProviders = new ArrayList<Provider>();
-
- // Hosts mentioned in the widget dataset by ordinal
- ArrayList<Host> restoredHosts = new ArrayList<Host>();
-
- //HashSet<String> toNotify = new HashSet<String>();
-
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, null);
-
- synchronized (mAppWidgetIds) {
- synchronized (mRestoredWidgetIds) {
- int type;
- do {
- type = parser.next();
- if (type == XmlPullParser.START_TAG) {
- final String tag = parser.getName();
- if ("ws".equals(tag)) {
- String v = parser.getAttributeValue(null, "version");
- String pkg = parser.getAttributeValue(null, "pkg");
-
- // TODO: fix up w.r.t. canonical vs current package names
- if (!packageName.equals(pkg)) {
- Slog.w(TAG, "Package mismatch in ws");
- return;
- }
-
- int version = Integer.parseInt(v);
- if (version > WIDGET_STATE_VERSION) {
- Slog.w(TAG, "Unable to process state version " + version);
- return;
- }
- } else if ("p".equals(tag)) {
- String pkg = parser.getAttributeValue(null, "pkg");
- String cl = parser.getAttributeValue(null, "cl");
-
- // hostedProviders index will match 'p' attribute in widget's
- // entry in the xml file being restored
- // If there's no live entry for this provider, add an inactive one
- // so that widget IDs referring to them can be properly allocated
- final ComponentName cn = new ComponentName(pkg, cl);
- Provider p = lookupProviderLocked(cn, mInstalledProviders);
- if (p == null) {
- p = new Provider();
- p.info = new AppWidgetProviderInfo();
- p.info.provider = cn;
- p.zombie = true;
- mInstalledProviders.add(p);
- }
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " provider " + cn);
- }
- restoredProviders.add(p);
- } else if ("h".equals(tag)) {
- // The host app may not yet exist on the device. If it's here we
- // just use the existing Host entry, otherwise we create a
- // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
- String pkg = parser.getAttributeValue(null, "pkg");
- int uid;
- try {
- uid = getUidForPackage(pkg);
- } catch (NameNotFoundException e) {
- uid = -1;
- }
- int hostId = Integer.parseInt(
- parser.getAttributeValue(null, "id"), 16);
- Host h = lookupOrAddHostLocked(uid, pkg, hostId);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " host[" + restoredHosts.size()
- + "]: {" + h.packageName + ":" + h.hostId + "}");
- }
- restoredHosts.add(h);
- } else if ("g".equals(tag)) {
- int restoredId = Integer.parseInt(
- parser.getAttributeValue(null, "id"), 16);
- int hostIndex = Integer.parseInt(
- parser.getAttributeValue(null, "h"), 16);
- Host host = restoredHosts.get(hostIndex);
- Provider p = null;
- String prov = parser.getAttributeValue(null, "p");
- if (prov != null) {
- // could have been null if the app had allocated an id
- // but not yet established a binding under that id
- int which = Integer.parseInt(prov, 16);
- p = restoredProviders.get(which);
- }
-
- // We'll be restoring widget state for both the host and
- // provider sides of this widget ID, so make sure we are
- // beginning from a clean slate on both fronts.
- pruneWidgetStateLr(host.packageName);
- if (p != null) {
- pruneWidgetStateLr(p.info.provider.getPackageName());
- }
-
- // Have we heard about this ancestral widget instance before?
- AppWidgetId id = findRestoredWidgetLocked(restoredId, host, p);
- if (id == null) {
- id = new AppWidgetId();
- id.appWidgetId = mNextAppWidgetId++;
- id.restoredId = restoredId;
- id.options = parseWidgetIdOptions(parser);
- id.host = host;
- id.host.instances.add(id);
- id.provider = p;
- if (id.provider != null) {
- id.provider.instances.add(id);
- }
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "New restored id " + restoredId
- + " now " + id);
- }
- mAppWidgetIds.add(id);
- }
- if (id.provider.info != null) {
- stashProviderRestoreUpdateLr(id.provider,
- restoredId, id.appWidgetId);
- } else {
- Slog.w(TAG, "Missing provider for restored widget " + id);
- }
- stashHostRestoreUpdateLr(id.host, restoredId, id.appWidgetId);
-
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " instance: " + restoredId
- + " -> " + id.appWidgetId
- + " :: p=" + id.provider);
- }
- }
- }
- } while (type != XmlPullParser.END_DOCUMENT);
-
- // We've updated our own bookkeeping. We'll need to notify the hosts and
- // providers about the changes, but we can't do that yet because the restore
- // target is not necessarily fully live at this moment. Set aside the
- // information for now; the backup manager will call us once more at the
- // end of the process when all of the targets are in a known state, and we
- // will update at that point.
- }
- }
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Unable to restore widget state for " + packageName);
- } catch (IOException e) {
- Slog.w(TAG, "Unable to restore widget state for " + packageName);
- } finally {
- saveStateAsync();
- }
- }
-
- // Called once following the conclusion of a restore operation. This is when we
- // send out updates to apps involved in widget-state restore telling them about
- // the new widget ID space.
- public void restoreFinished() {
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "restoreFinished for " + mUserId);
- }
-
- final UserHandle userHandle = new UserHandle(mUserId);
- synchronized (mRestoredWidgetIds) {
- // Build the providers' broadcasts and send them off
- Set<Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
- = mUpdatesByProvider.entrySet();
- for (Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
- // For each provider there's a list of affected IDs
- Provider provider = e.getKey();
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
- final int pending = countPendingUpdates(updates);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Provider " + provider + " pending: " + pending);
- }
- if (pending > 0) {
- int[] oldIds = new int[pending];
- int[] newIds = new int[pending];
- final int N = updates.size();
- int nextPending = 0;
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- r.notified = true;
- oldIds[nextPending] = r.oldId;
- newIds[nextPending] = r.newId;
- nextPending++;
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " " + r.oldId + " => " + r.newId);
- }
- }
- }
- sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_RESTORED,
- provider, null, oldIds, newIds, userHandle);
- }
- }
-
- // same thing per host
- Set<Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
- = mUpdatesByHost.entrySet();
- for (Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
- Host host = e.getKey();
- if (host.uid > 0) {
- ArrayList<RestoreUpdateRecord> updates = e.getValue();
- final int pending = countPendingUpdates(updates);
- if (DEBUG_BACKUP) {
- Slog.i(TAG, "Host " + host + " pending: " + pending);
- }
- if (pending > 0) {
- int[] oldIds = new int[pending];
- int[] newIds = new int[pending];
- final int N = updates.size();
- int nextPending = 0;
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- r.notified = true;
- oldIds[nextPending] = r.oldId;
- newIds[nextPending] = r.newId;
- nextPending++;
- if (DEBUG_BACKUP) {
- Slog.i(TAG, " " + r.oldId + " => " + r.newId);
- }
- }
- }
- sendWidgetRestoreBroadcast(AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
- null, host, oldIds, newIds, userHandle);
- }
- }
- }
- }
- }
-
- private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
- int pending = 0;
- final int N = updates.size();
- for (int i = 0; i < N; i++) {
- RestoreUpdateRecord r = updates.get(i);
- if (!r.notified) {
- pending++;
- }
- }
- return pending;
- }
-
- void sendWidgetRestoreBroadcast(String action, Provider provider, Host host,
- int[] oldIds, int[] newIds, UserHandle userHandle) {
- Intent intent = new Intent(action);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
- if (provider != null) {
- intent.setComponent(provider.info.provider);
- mContext.sendBroadcastAsUser(intent, userHandle);
- }
- if (host != null) {
- intent.setComponent(null);
- intent.setPackage(host.packageName);
- intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.hostId);
- mContext.sendBroadcastAsUser(intent, userHandle);
- }
- }
-
- private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) {
- Provider p = null;
+ @SuppressWarnings("deprecation")
+ private Provider parseProviderInfoXml(ProviderId providerId, ResolveInfo ri) {
+ Provider provider = null;
ActivityInfo activityInfo = ri.activityInfo;
XmlResourceParser parser = null;
@@ -2165,7 +2107,7 @@
AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
if (parser == null) {
Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER
- + " meta-data for " + "AppWidget provider '" + component + '\'');
+ + " meta-data for " + "AppWidget provider '" + providerId + '\'');
return null;
}
@@ -2180,19 +2122,28 @@
String nodeName = parser.getName();
if (!"appwidget-provider".equals(nodeName)) {
Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for"
- + " AppWidget provider '" + component + '\'');
+ + " AppWidget provider " + providerId.componentName
+ + " for user " + providerId.uid);
return null;
}
- p = new Provider();
- AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo();
- info.provider = component;
- p.uid = activityInfo.applicationInfo.uid;
+ provider = new Provider();
+ provider.id = providerId;
+ AppWidgetProviderInfo info = provider.info = new AppWidgetProviderInfo();
+ info.provider = providerId.componentName;
+ info.providerInfo = activityInfo;
- Resources res = mContext.getPackageManager()
- .getResourcesForApplicationAsUser(activityInfo.packageName, mUserId);
+ final Resources resources;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ resources = mContext.getPackageManager()
+ .getResourcesForApplicationAsUser(activityInfo.packageName,
+ UserHandle.getUserId(providerId.uid));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
- TypedArray sa = res.obtainAttributes(attrs,
+ TypedArray sa = resources.obtainAttributes(attrs,
com.android.internal.R.styleable.AppWidgetProviderInfo);
// These dimensions has to be resolved in the application's context.
@@ -2215,10 +2166,12 @@
com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0);
info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable.
AppWidgetProviderInfo_initialKeyguardLayout, 0);
+
String className = sa
.getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure);
if (className != null) {
- info.configure = new ComponentName(component.getPackageName(), className);
+ info.configure = new ComponentName(providerId.componentName.getPackageName(),
+ className);
}
info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString();
info.icon = ri.getIconResource();
@@ -2234,111 +2187,224 @@
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
sa.recycle();
- } catch (Exception e) {
+ } catch (IOException | PackageManager.NameNotFoundException | XmlPullParserException e) {
// Ok to catch Exception here, because anything going wrong because
// of what a client process passes to us should not be fatal for the
// system process.
- Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e);
+ Slog.w(TAG, "XML parsing failed for AppWidget provider "
+ + providerId.componentName + " for user " + providerId.uid, e);
return null;
} finally {
- if (parser != null)
+ if (parser != null) {
parser.close();
+ }
}
- return p;
+ return provider;
}
- int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException {
+ private int getUidForPackage(String packageName, int userId) {
PackageInfo pkgInfo = null;
+
+ final long identity = Binder.clearCallingIdentity();
try {
- pkgInfo = mPm.getPackageInfo(packageName, 0, mUserId);
+ pkgInfo = mPackageManager.getPackageInfo(packageName, 0, userId);
} catch (RemoteException re) {
// Shouldn't happen, local call
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
+
if (pkgInfo == null || pkgInfo.applicationInfo == null) {
- throw new PackageManager.NameNotFoundException();
+ return -1;
}
+
return pkgInfo.applicationInfo.uid;
}
- int enforceSystemOrCallingUid(String packageName) throws IllegalArgumentException {
- int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID || callingUid == 0) {
- return callingUid;
+ private ActivityInfo getProviderInfo(ComponentName componentName, int userId) {
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.setComponent(componentName);
+
+ List<ResolveInfo> receivers = queryIntentReceivers(intent, userId);
+ // We are setting component, so there is only one or none.
+ if (!receivers.isEmpty()) {
+ return receivers.get(0).activityInfo;
}
- return enforceCallingUid(packageName);
+
+ return null;
}
- int enforceCallingUid(String packageName) throws IllegalArgumentException {
- int callingUid = Binder.getCallingUid();
- int packageUid;
+ private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) {
+ final long identity = Binder.clearCallingIdentity();
try {
- packageUid = getUidForPackage(packageName);
- } catch (PackageManager.NameNotFoundException ex) {
- throw new IllegalArgumentException("packageName and uid don't match packageName="
- + packageName);
+ return mPackageManager.queryIntentReceivers(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, userId);
+ } catch (RemoteException re) {
+ return Collections.emptyList();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- if (!UserHandle.isSameApp(callingUid, packageUid)) {
- throw new IllegalArgumentException("packageName and uid don't match packageName="
- + packageName);
- }
- return callingUid;
}
- void sendInitialBroadcasts() {
- synchronized (mAppWidgetIds) {
- ensureStateLoadedLocked();
- final int N = mInstalledProviders.size();
+ private void onUserStarted(int userId) {
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ final int N = mProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- sendEnableIntentLocked(p);
- int[] appWidgetIds = getAppWidgetIds(p);
- sendUpdateIntentLocked(p, appWidgetIds);
- registerForBroadcastsLocked(p, appWidgetIds);
+ Provider provider = mProviders.get(i);
+
+ // Send broadcast only to the providers of the user.
+ if (provider.getUserId() != userId) {
+ continue;
+ }
+
+ if (provider.widgets.size() > 0) {
+ sendEnableIntentLocked(provider);
+ int[] appWidgetIds = getWidgetIds(provider.widgets);
+ sendUpdateIntentLocked(provider, appWidgetIds);
+ registerForBroadcastsLocked(provider, appWidgetIds);
}
}
}
}
// only call from initialization -- it assumes that the data structures are all empty
- void loadStateLocked() {
- AtomicFile file = savedStateFile();
- try {
- FileInputStream stream = file.openRead();
- readStateFromFileLocked(stream);
+ private void loadGroupStateLocked(int[] profileIds) {
+ // We can bind the widgets to host and providers only after
+ // reading the host and providers for all users since a widget
+ // can have a host and a provider in different users.
+ List<LoadedWidgetState> loadedWidgets = new ArrayList<>();
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to close state FileInputStream " + e);
+ int version = 0;
+
+ final int profileIdCount = profileIds.length;
+ for (int i = 0; i < profileIdCount; i++) {
+ final int profileId = profileIds[i];
+
+ // No file written for this user - nothing to do.
+ AtomicFile file = getSavedStateFile(profileId);
+ try {
+ FileInputStream stream = file.openRead();
+ version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
+ IoUtils.closeQuietly(stream);
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Failed to read state: " + e);
+ }
+ }
+
+ if (version >= 0) {
+ // Hooke'm up...
+ bindLoadedWidgets(loadedWidgets);
+
+ // upgrade the database if needed
+ performUpgradeLocked(version);
+ } else {
+ // failed reading, clean up
+ Slog.w(TAG, "Failed to read state, clearing widgets and hosts.");
+ mWidgets.clear();
+ mHosts.clear();
+ final int N = mProviders.size();
+ for (int i = 0; i < N; i++) {
+ mProviders.get(i).widgets.clear();
+ }
+ }
+ }
+
+ private void bindLoadedWidgets(List<LoadedWidgetState> loadedWidgets) {
+ final int loadedWidgetCount = loadedWidgets.size();
+ for (int i = loadedWidgetCount - 1; i >= 0; i--) {
+ LoadedWidgetState loadedWidget = loadedWidgets.remove(i);
+ Widget widget = loadedWidget.widget;
+
+ widget.provider = findProviderByTag(loadedWidget.providerTag);
+ if (widget.provider == null) {
+ // This provider is gone. We just let the host figure out
+ // that this happened when it fails to load it.
+ continue;
+ }
+
+ widget.host = findHostByTag(loadedWidget.hostTag);
+ if (widget.host == null) {
+ // This host is gone.
+ continue;
+ }
+
+ widget.provider.widgets.add(widget);
+ widget.host.widgets.add(widget);
+ mWidgets.add(widget);
+ }
+ }
+
+ private Provider findProviderByTag(int tag) {
+ if (tag < 0) {
+ return null;
+ }
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.tag == tag) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private Host findHostByTag(int tag) {
+ if (tag < 0) {
+ return null;
+ }
+ final int hostCount = mHosts.size();
+ for (int i = 0; i < hostCount; i++) {
+ Host host = mHosts.get(i);
+ if (host.tag == tag) {
+ return host;
+ }
+ }
+ return null;
+ }
+
+ private void saveStateLocked(int userId) {
+ tagProvidersAndHosts();
+
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+
+ AtomicFile file = getSavedStateFile(profileId);
+ FileOutputStream stream;
+ try {
+ stream = file.startWrite();
+ if (writeProfileStateToFileLocked(stream, profileId)) {
+ file.finishWrite(stream);
+ } else {
+ file.failWrite(stream);
+ Slog.w(TAG, "Failed to save state, restoring backup.");
}
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed open state file for write: " + e);
}
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Failed to read state: " + e);
}
}
- void saveStateLocked() {
- if (!mHasFeature) {
- return;
+ private void tagProvidersAndHosts() {
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ provider.tag = i;
}
- AtomicFile file = savedStateFile();
- FileOutputStream stream;
- try {
- stream = file.startWrite();
- if (writeStateToFileLocked(stream)) {
- file.finishWrite(stream);
- } else {
- file.failWrite(stream);
- Slog.w(TAG, "Failed to save state, restoring backup.");
- }
- } catch (IOException e) {
- Slog.w(TAG, "Failed open state file for write: " + e);
+
+ final int hostCount = mHosts.size();
+ for (int i = 0; i < hostCount; i++) {
+ Host host = mHosts.get(i);
+ host.tag = i;
}
}
- boolean writeStateToFileLocked(FileOutputStream stream) {
+ private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
int N;
try {
@@ -2347,39 +2413,52 @@
out.startDocument(null, true);
out.startTag(null, "gs");
out.attribute(null, "version", String.valueOf(CURRENT_VERSION));
- int providerIndex = 0;
- N = mInstalledProviders.size();
+
+ N = mProviders.size();
for (int i = 0; i < N; i++) {
- Provider p = mInstalledProviders.get(i);
- if (p.instances.size() > 0) {
- serializeProvider(out, p);
- p.tag = providerIndex;
- providerIndex++;
+ Provider provider = mProviders.get(i);
+ // Save only providers for the user.
+ if (provider.getUserId() != userId) {
+ continue;
+ }
+ if (provider.widgets.size() > 0) {
+ serializeProvider(out, provider);
}
}
N = mHosts.size();
for (int i = 0; i < N; i++) {
Host host = mHosts.get(i);
+ // Save only hosts for the user.
+ if (host.getUserId() != userId) {
+ continue;
+ }
serializeHost(out, host);
- host.tag = i;
}
- N = mAppWidgetIds.size();
+ N = mWidgets.size();
for (int i = 0; i < N; i++) {
- AppWidgetId id = mAppWidgetIds.get(i);
- serializeAppWidgetId(out, id);
+ Widget widget = mWidgets.get(i);
+ // Save only widgets hosted by the user.
+ if (widget.host.getUserId() != userId) {
+ continue;
+ }
+ serializeAppWidget(out, widget);
}
- Iterator<String> it = mPackagesWithBindWidgetPermission.iterator();
+ Iterator<Pair<Integer, String>> it = mPackagesWithBindWidgetPermission.iterator();
while (it.hasNext()) {
+ Pair<Integer, String> binding = it.next();
+ // Save only white listings for the user.
+ if (binding.first != userId) {
+ continue;
+ }
out.startTag(null, "b");
- out.attribute(null, "packageName", it.next());
+ out.attribute(null, "packageName", binding.second);
out.endTag(null, "b");
}
out.endTag(null, "gs");
-
out.endDocument();
return true;
} catch (IOException e) {
@@ -2388,17 +2467,16 @@
}
}
- @SuppressWarnings("unused")
- void readStateFromFileLocked(FileInputStream stream) {
- boolean success = false;
- int version = 0;
+ private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
+ List<LoadedWidgetState> outLoadedWidgets) {
+ int version = -1;
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, null);
+ int legacyProviderIndex = -1;
+ int legacyHostIndex = -1;
int type;
- int providerIndex = 0;
- HashMap<Integer, Provider> loadedProviders = new HashMap<Integer, Provider>();
do {
type = parser.next();
if (type == XmlPullParser.START_TAG) {
@@ -2411,67 +2489,89 @@
version = 0;
}
} else if ("p".equals(tag)) {
+ legacyProviderIndex++;
// TODO: do we need to check that this package has the same signature
// as before?
String pkg = parser.getAttributeValue(null, "pkg");
String cl = parser.getAttributeValue(null, "cl");
- final IPackageManager packageManager = AppGlobals.getPackageManager();
- try {
- packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0, mUserId);
- } catch (RemoteException e) {
- String[] pkgs = mContext.getPackageManager()
- .currentToCanonicalPackageNames(new String[] { pkg });
- pkg = pkgs[0];
+ pkg = getCanonicalPackageName(pkg, cl, userId);
+ if (pkg == null) {
+ continue;
}
- Provider p = lookupProviderLocked(new ComponentName(pkg, cl));
- if (p == null && mSafeMode) {
+ final int uid = getUidForPackage(pkg, userId);
+ if (uid < 0) {
+ continue;
+ }
+
+ ComponentName componentName = new ComponentName(pkg, cl);
+
+ ActivityInfo providerInfo = getProviderInfo(componentName, userId);
+ if (providerInfo == null) {
+ continue;
+ }
+
+ ProviderId providerId = new ProviderId(uid, componentName);
+ Provider provider = lookupProviderLocked(providerId);
+
+ if (provider == null && mSafeMode) {
// if we're in safe mode, make a temporary one
- p = new Provider();
- p.info = new AppWidgetProviderInfo();
- p.info.provider = new ComponentName(pkg, cl);
- p.zombie = true;
- mInstalledProviders.add(p);
+ provider = new Provider();
+ provider.info = new AppWidgetProviderInfo();
+ provider.info.provider = providerId.componentName;
+ provider.info.providerInfo = providerInfo;
+ provider.zombie = true;
+ provider.id = providerId;
+ mProviders.add(provider);
}
- if (p != null) {
- // if it wasn't uninstalled or something
- loadedProviders.put(providerIndex, p);
- }
- providerIndex++;
- } else if ("h".equals(tag)) {
- Host host = new Host();
+ String tagAttribute = parser.getAttributeValue(null, "tag");
+ final int providerTag = !TextUtils.isEmpty(tagAttribute)
+ ? Integer.parseInt(tagAttribute, 16) : legacyProviderIndex;
+ provider.tag = providerTag;
+ } else if ("h".equals(tag)) {
+ legacyHostIndex++;
+ Host host = new Host();
// TODO: do we need to check that this package has the same signature
// as before?
- host.packageName = parser.getAttributeValue(null, "pkg");
- try {
- host.uid = getUidForPackage(host.packageName);
- } catch (PackageManager.NameNotFoundException ex) {
+ String pkg = parser.getAttributeValue(null, "pkg");
+
+ final int uid = getUidForPackage(pkg, userId);
+ if (uid < 0) {
host.zombie = true;
}
+
if (!host.zombie || mSafeMode) {
// In safe mode, we don't discard the hosts we don't recognize
// so that they're not pruned from our list. Otherwise, we do.
- host.hostId = Integer
- .parseInt(parser.getAttributeValue(null, "id"), 16);
+ final int hostId = Integer.parseInt(parser.getAttributeValue(
+ null, "id"), 16);
+
+ String tagAttribute = parser.getAttributeValue(null, "tag");
+ final int hostTag = !TextUtils.isEmpty(tagAttribute)
+ ? Integer.parseInt(tagAttribute, 16) : legacyHostIndex;
+
+ host.tag = hostTag;
+ host.id = new HostId(uid, hostId, pkg);
mHosts.add(host);
}
} else if ("b".equals(tag)) {
String packageName = parser.getAttributeValue(null, "packageName");
- if (packageName != null) {
- mPackagesWithBindWidgetPermission.add(packageName);
+ final int uid = getUidForPackage(packageName, userId);
+ if (uid >= 0) {
+ Pair<Integer, String> packageId = Pair.create(userId, packageName);
+ mPackagesWithBindWidgetPermission.add(packageId);
}
} else if ("g".equals(tag)) {
- AppWidgetId id = new AppWidgetId();
- id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16);
- if (id.appWidgetId >= mNextAppWidgetId) {
- mNextAppWidgetId = id.appWidgetId + 1;
- }
+ Widget widget = new Widget();
+ widget.appWidgetId = Integer.parseInt(parser.getAttributeValue(
+ null, "id"), 16);
+ setMinAppWidgetIdLocked(userId, widget.appWidgetId + 1);
// restored ID is allowed to be absent
String restoredIdString = parser.getAttributeValue(null, "rid");
- id.restoredId = (restoredIdString == null) ? 0
+ widget.restoredId = (restoredIdString == null) ? 0
: Integer.parseInt(restoredIdString, 16);
Bundle options = new Bundle();
@@ -2500,92 +2600,57 @@
options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
Integer.parseInt(categoryString, 16));
}
- id.options = options;
+ widget.options = options;
+ final int hostTag = Integer.parseInt(parser.getAttributeValue(
+ null, "h"), 16);
String providerString = parser.getAttributeValue(null, "p");
- if (providerString != null) {
- // there's no provider if it hasn't been bound yet.
- // maybe we don't have to save this, but it brings the system
- // to the state it was in.
- int pIndex = Integer.parseInt(providerString, 16);
- id.provider = loadedProviders.get(pIndex);
- if (false) {
- Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider "
- + pIndex + " which is " + id.provider);
- }
- if (id.provider == null) {
- // This provider is gone. We just let the host figure out
- // that this happened when it fails to load it.
- continue;
- }
- }
+ final int providerTag = (providerString != null) ? Integer.parseInt(
+ parser.getAttributeValue(null, "p"), 16) : TAG_UNDEFINED;
- int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16);
- id.host = mHosts.get(hIndex);
- if (id.host == null) {
- // This host is gone.
- continue;
- }
-
- if (id.provider != null) {
- id.provider.instances.add(id);
- }
- id.host.instances.add(id);
- mAppWidgetIds.add(id);
+ // We can match widgets with hosts and providers only after hosts
+ // and providers for all users have been loaded since the widget
+ // host and provider can be in different user profiles.
+ LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget,
+ hostTag, providerTag);
+ outLoadedWidgets.add(loadedWidgets);
}
}
} while (type != XmlPullParser.END_DOCUMENT);
- success = true;
- } catch (NullPointerException e) {
+ } catch (NullPointerException
+ | NumberFormatException
+ | XmlPullParserException
+ | IOException
+ | IndexOutOfBoundsException e) {
Slog.w(TAG, "failed parsing " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (IOException e) {
- Slog.w(TAG, "failed parsing " + e);
- } catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "failed parsing " + e);
+ return -1;
}
- if (success) {
- // delete any hosts that didn't manage to get connected (should happen)
- // if it matters, they'll be reconnected.
- for (int i = mHosts.size() - 1; i >= 0; i--) {
- pruneHostLocked(mHosts.get(i));
- }
- // upgrade the database if needed
- performUpgrade(version);
- } else {
- // failed reading, clean up
- Slog.w(TAG, "Failed to read state, clearing widgets and hosts.");
-
- mAppWidgetIds.clear();
- mHosts.clear();
- final int N = mInstalledProviders.size();
- for (int i = 0; i < N; i++) {
- mInstalledProviders.get(i).instances.clear();
- }
- }
+ return version;
}
- private void performUpgrade(int fromVersion) {
+ private void performUpgradeLocked(int fromVersion) {
if (fromVersion < CURRENT_VERSION) {
- Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " + CURRENT_VERSION
- + " for user " + mUserId);
+ Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to "
+ + CURRENT_VERSION);
}
int version = fromVersion;
// Update 1: keyguard moved from package "android" to "com.android.keyguard"
if (version == 0) {
- for (int i = 0; i < mHosts.size(); i++) {
- Host host = mHosts.get(i);
- if (host != null && "android".equals(host.packageName)
- && host.hostId == KEYGUARD_HOST_ID) {
- host.packageName = KEYGUARD_HOST_PACKAGE;
+ HostId oldHostId = new HostId(Process.myUid(),
+ KEYGUARD_HOST_ID, OLD_KEYGUARD_HOST_PACKAGE);
+
+ Host host = lookupHostLocked(oldHostId);
+ if (host != null) {
+ final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE,
+ UserHandle.USER_OWNER);
+ if (uid >= 0) {
+ host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE);
}
}
+
version = 1;
}
@@ -2594,19 +2659,19 @@
}
}
- static File getSettingsFile(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILENAME);
+ private static File getStateFile(int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME);
}
- AtomicFile savedStateFile() {
- File dir = Environment.getUserSystemDirectory(mUserId);
- File settingsFile = getSettingsFile(mUserId);
- if (!settingsFile.exists() && mUserId == 0) {
+ private static AtomicFile getSavedStateFile(int userId) {
+ File dir = Environment.getUserSystemDirectory(userId);
+ File settingsFile = getStateFile(userId);
+ if (!settingsFile.exists() && userId == UserHandle.USER_OWNER) {
if (!dir.exists()) {
dir.mkdirs();
}
// Migrate old data
- File oldFile = new File("/data/system/" + SETTINGS_FILENAME);
+ File oldFile = new File("/data/system/" + STATE_FILENAME);
// Method doesn't throw an exception on failure. Ignore any errors
// in moving the file (like non-existence)
oldFile.renameTo(settingsFile);
@@ -2614,45 +2679,67 @@
return new AtomicFile(settingsFile);
}
- void onUserStopping() {
- // prune the ones we don't want to keep
- int N = mInstalledProviders.size();
- for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- cancelBroadcasts(p);
- }
- }
+ private void onUserStopped(int userId) {
+ synchronized (mLock) {
+ // Remove widgets that have both host and provider in the user.
+ final int widgetCount = mWidgets.size();
+ for (int i = widgetCount - 1; i >= 0; i--) {
+ Widget widget = mWidgets.get(i);
- void onUserRemoved() {
- getSettingsFile(mUserId).delete();
- }
+ final boolean hostInUser = widget.host.getUserId() == userId;
+ final boolean hasProvider = widget.provider != null;
+ final boolean providerInUser = hasProvider && widget.provider.getUserId() == userId;
- boolean addProvidersForPackageLocked(String pkgName) {
- boolean providersAdded = false;
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.setPackage(pkgName);
- List<ResolveInfo> broadcastReceivers;
- try {
- broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException re) {
- // Shouldn't happen, local call
- return false;
- }
- final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo ri = broadcastReceivers.get(i);
- ActivityInfo ai = ri.activityInfo;
- if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
- continue;
+ // If both host and provider are in the user, just drop the widgets
+ // as we do not want to make host callbacks and provider broadcasts
+ // as the host and the provider will be killed.
+ if (hostInUser && (!hasProvider || providerInUser)) {
+ mWidgets.remove(i);
+ widget.host.widgets.remove(widget);
+ widget.host = null;
+ if (hasProvider) {
+ widget.provider.widgets.remove(widget);
+ widget.provider = null;
+ }
+ }
}
- if (pkgName.equals(ai.packageName)) {
- providersAdded = addProviderLocked(ri);
- }
- }
- return providersAdded;
+ // Remove hosts and notify providers in other profiles.
+ final int hostCount = mHosts.size();
+ for (int i = hostCount - 1; i >= 0; i--) {
+ Host host = mHosts.get(i);
+ if (host.getUserId() == userId) {
+ deleteHostLocked(host);
+ }
+ }
+
+ // Remove the providers and notify hosts in other profiles.
+ final int providerCount = mProviders.size();
+ for (int i = providerCount - 1; i >= 0; i--) {
+ Provider provider = mProviders.get(i);
+ if (provider.getUserId() == userId) {
+ deleteProviderLocked(provider);
+ }
+ }
+
+ // Remove grants for this user.
+ final int grantCount = mPackagesWithBindWidgetPermission.size();
+ for (int i = grantCount - 1; i >= 0; i--) {
+ Pair<Integer, String> packageId = mPackagesWithBindWidgetPermission.valueAt(i);
+ if (packageId.first == userId) {
+ mPackagesWithBindWidgetPermission.removeAt(i);
+ }
+ }
+
+ // Take a note we no longer have state for this user.
+ final int index = mLoadedUserIds.indexOfKey(userId);
+ if (index >= 0) {
+ mLoadedUserIds.removeAt(index);
+ }
+
+ // Remove the widget id counter.
+ mNextAppWidgetIds.removeAt(userId);
+ }
}
/**
@@ -2661,71 +2748,59 @@
*
* @return whether any providers were updated
*/
- boolean updateProvidersForPackageLocked(String pkgName, Set<ComponentName> removedProviders) {
+ private boolean updateProvidersForPackageLocked(String packageName, int userId,
+ Set<ProviderId> removedProviders) {
boolean providersUpdated = false;
- HashSet<String> keep = new HashSet<String>();
+
+ HashSet<ProviderId> keep = new HashSet<>();
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.setPackage(pkgName);
- List<ResolveInfo> broadcastReceivers;
- try {
- broadcastReceivers = mPm.queryIntentReceivers(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException re) {
- // Shouldn't happen, local call
- return false;
- }
+ intent.setPackage(packageName);
+ List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId);
// add the missing ones and collect which ones to keep
int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
for (int i = 0; i < N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
+
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
continue;
}
- if (pkgName.equals(ai.packageName)) {
- ComponentName component = new ComponentName(ai.packageName, ai.name);
- Provider p = lookupProviderLocked(component);
- if (p == null) {
+
+ if (packageName.equals(ai.packageName)) {
+ ProviderId providerId = new ProviderId(ai.applicationInfo.uid,
+ new ComponentName(ai.packageName, ai.name));
+
+ Provider provider = lookupProviderLocked(providerId);
+ if (provider == null) {
if (addProviderLocked(ri)) {
- keep.add(ai.name);
+ keep.add(providerId);
providersUpdated = true;
}
} else {
- Provider parsed = parseProviderInfoXml(component, ri);
+ Provider parsed = parseProviderInfoXml(providerId, ri);
if (parsed != null) {
- keep.add(ai.name);
+ keep.add(providerId);
// Use the new AppWidgetProviderInfo.
- p.info = parsed.info;
+ provider.info = parsed.info;
// If it's enabled
- final int M = p.instances.size();
+ final int M = provider.widgets.size();
if (M > 0) {
- int[] appWidgetIds = getAppWidgetIds(p);
+ int[] appWidgetIds = getWidgetIds(provider.widgets);
// Reschedule for the new updatePeriodMillis (don't worry about handling
// it specially if updatePeriodMillis didn't change because we just sent
// an update, and the next one will be updatePeriodMillis from now).
- cancelBroadcasts(p);
- registerForBroadcastsLocked(p, appWidgetIds);
+ cancelBroadcasts(provider);
+ registerForBroadcastsLocked(provider, appWidgetIds);
// If it's currently showing, call back with the new
// AppWidgetProviderInfo.
for (int j = 0; j < M; j++) {
- AppWidgetId id = p.instances.get(j);
- id.views = null;
- if (id.host != null && id.host.callbacks != null) {
- try {
- id.host.callbacks.providerChanged(id.appWidgetId, p.info,
- mUserId);
- } catch (RemoteException ex) {
- // It failed; remove the callback. No need to prune because
- // we know that this host is still referenced by this
- // instance.
- id.host.callbacks = null;
- }
- }
+ Widget widget = provider.widgets.get(j);
+ widget.views = null;
+ scheduleNotifyProviderChangedLocked(widget);
}
// Now that we've told the host, push out an update.
- sendUpdateIntentLocked(p, appWidgetIds);
+ sendUpdateIntentLocked(provider, appWidgetIds);
providersUpdated = true;
}
}
@@ -2734,15 +2809,16 @@
}
// prune the ones we don't want to keep
- N = mInstalledProviders.size();
+ N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- if (pkgName.equals(p.info.provider.getPackageName())
- && !keep.contains(p.info.provider.getClassName())) {
+ Provider provider = mProviders.get(i);
+ if (packageName.equals(provider.info.provider.getPackageName())
+ && provider.getUserId() == userId
+ && !keep.contains(provider.id)) {
if (removedProviders != null) {
- removedProviders.add(p.info.provider);
+ removedProviders.add(provider.id);
}
- removeProviderLocked(i, p);
+ deleteProviderLocked(provider);
providersUpdated = true;
}
}
@@ -2750,46 +2826,1249 @@
return providersUpdated;
}
- boolean removeProvidersForPackageLocked(String pkgName) {
- boolean providersRemoved = false;
- int N = mInstalledProviders.size();
+ private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
+ boolean removed = false;
+
+ int N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
- Provider p = mInstalledProviders.get(i);
- if (pkgName.equals(p.info.provider.getPackageName())) {
- removeProviderLocked(i, p);
- providersRemoved = true;
+ Provider provider = mProviders.get(i);
+ if (pkgName.equals(provider.info.provider.getPackageName())
+ && provider.getUserId() == userId) {
+ deleteProviderLocked(provider);
+ removed = true;
}
}
// Delete the hosts for this package too
- //
// By now, we have removed any AppWidgets that were in any hosts here,
// so we don't need to worry about sending DISABLE broadcasts to them.
N = mHosts.size();
for (int i = N - 1; i >= 0; i--) {
Host host = mHosts.get(i);
- if (pkgName.equals(host.packageName)) {
+ if (pkgName.equals(host.id.packageName)
+ && host.getUserId() == userId) {
deleteHostLocked(host);
+ removed = true;
}
}
- return providersRemoved;
+ return removed;
}
- void notifyHostsForProvidersChangedLocked() {
- final int N = mHosts.size();
- for (int i = N - 1; i >= 0; i--) {
- Host host = mHosts.get(i);
+ private String getCanonicalPackageName(String packageName, String className, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
try {
- if (host.callbacks != null) {
- host.callbacks.providersChanged(mUserId);
+ AppGlobals.getPackageManager().getReceiverInfo(new ComponentName(packageName,
+ className), 0, userId);
+ return packageName;
+ } catch (RemoteException re) {
+ String[] packageNames = mContext.getPackageManager()
+ .currentToCanonicalPackageNames(new String[]{packageName});
+ if (packageNames != null && packageNames.length > 0) {
+ return packageNames[0];
}
- } catch (RemoteException ex) {
- // It failed; remove the callback. No need to prune because
- // we know that this host is still referenced by this
- // instance.
- host.callbacks = null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return null;
+ }
+
+ private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void bindService(Intent intent, ServiceConnection connection,
+ UserHandle userHandle) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void unbindService(ServiceConnection connection) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mContext.unbindService(connection);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private final class CallbackHandler extends Handler {
+ public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
+ public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
+ public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
+ public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
+
+ public CallbackHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFY_UPDATE_APP_WIDGET: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ RemoteViews views = (RemoteViews) args.arg3;
+ final int appWidgetId = args.argi1;
+ args.recycle();
+
+ handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
+ } break;
+
+ case MSG_NOTIFY_PROVIDER_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
+ final int appWidgetId = args.argi1;
+ args.recycle();
+
+ handleNotifyProviderChanged(host, callbacks, appWidgetId, info);
+ } break;
+
+ case MSG_NOTIFY_PROVIDERS_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ args.recycle();
+
+ handleNotifyProvidersChanged(host, callbacks);
+ } break;
+
+ case MSG_NOTIFY_VIEW_DATA_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Host host = (Host) args.arg1;
+ IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+ final int appWidgetId = args.argi1;
+ final int viewId = args.argi2;
+ args.recycle();
+
+ handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId);
+ } break;
}
}
}
-}
+
+ private final class SecurityPolicy {
+
+ public int[] resolveCallerEnabledGroupProfiles(int[] profileIds) {
+ final int parentId = UserHandle.getCallingUserId();
+
+ int enabledProfileCount = 0;
+ final int profileCount = profileIds.length;
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+ if (!isParentOrProfile(parentId, profileId)) {
+ throw new SecurityException("Not the current user or"
+ + " a child profile: " + profileId);
+ }
+ if (!isProfileEnabled(profileId)) {
+ profileIds[i] = DISABLED_PROFILE;
+ } else {
+ enabledProfileCount++;
+ }
+ }
+
+ int resolvedProfileIndex = 0;
+ final int[] resolvedProfiles = new int[enabledProfileCount];
+ for (int i = 0; i < profileCount; i++) {
+ final int profileId = profileIds[i];
+ if (profileId != DISABLED_PROFILE) {
+ resolvedProfiles[resolvedProfileIndex] = profileId;
+ resolvedProfileIndex++;
+ }
+ }
+
+ return resolvedProfiles;
+ }
+
+ public int[] getEnabledGroupProfileIds(int userId) {
+ final int parentId = getGroupParent(userId);
+
+ final List<UserInfo> profiles;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ profiles = mUserManager.getProfiles(parentId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ int enabledProfileCount = 0;
+ final int profileCount = profiles.size();
+ for (int i = 0; i < profileCount; i++) {
+ if (profiles.get(i).isEnabled()) {
+ enabledProfileCount++;
+ }
+ }
+
+ int enabledProfileIndex = 0;
+ final int[] profileIds = new int[enabledProfileCount];
+ for (int i = 0; i < profileCount; i++) {
+ UserInfo profile = profiles.get(i);
+ if (profile.isEnabled()) {
+ profileIds[enabledProfileIndex] = profile.getUserHandle().getIdentifier();
+ enabledProfileIndex++;
+ }
+ }
+
+ return profileIds;
+ }
+
+ public void enforceServiceExistsAndRequiresBindRemoteViewsPermission(
+ ComponentName componentName, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ServiceInfo serviceInfo = mPackageManager.getServiceInfo(componentName,
+ PackageManager.GET_PERMISSIONS, userId);
+ if (serviceInfo == null) {
+ throw new SecurityException("Service " + componentName
+ + " not installed for user " + userId);
+ }
+ if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(serviceInfo.permission)) {
+ throw new SecurityException("Service " + componentName
+ + " in user " + userId + "does not require "
+ + android.Manifest.permission.BIND_REMOTEVIEWS);
+ }
+ } catch (RemoteException re) {
+ // Local call - shouldn't happen.
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void enforceModifyAppWidgetBindPermissions(String packageName) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
+ "hasBindAppWidgetPermission packageName=" + packageName);
+ }
+
+ public void enforceCallFromPackage(String packageName) {
+ if (!isCallFromPackage(packageName)) {
+ throw new SecurityException("Package " + packageName
+ + " not running under user " + UserHandle.getCallingUserId());
+ }
+ }
+
+ public boolean isCallFromPackage(String packageName) {
+ // System and root call all from anywhere they want.
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID || callingUid == 0 /* root */) {
+ return true;
+ }
+ // Check if the package is present for the given profile.
+ final int packageUid = getUidForPackage(packageName,
+ UserHandle.getUserId(callingUid));
+ if (packageUid < 0) {
+ return false;
+ }
+ // Check if the call for a package is coming from that package.
+ return UserHandle.isSameApp(callingUid, packageUid);
+ }
+
+ public boolean hasCallerBindPermissionOrBindWhiteListedLocked(String packageName) {
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BIND_APPWIDGET, null);
+ } catch (SecurityException se) {
+ if (!isCallerBindAppWidgetWhiteListedLocked(packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isCallerBindAppWidgetWhiteListedLocked(String packageName) {
+ final int userId = UserHandle.getCallingUserId();
+ final int packageUid = getUidForPackage(packageName, userId);
+ if (packageUid < 0) {
+ throw new IllegalArgumentException("No package " + packageName
+ + " for user " + userId);
+ }
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(userId);
+
+ Pair<Integer, String> packageId = Pair.create(userId, packageName);
+ if (mPackagesWithBindWidgetPermission.contains(packageId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+ if (isHostInPackageForUid(widget.host, uid, packageName)) {
+ // Apps hosting the AppWidget have access to it.
+ return true;
+ }
+ if (isProviderInPackageForUid(widget.provider, uid, packageName)) {
+ // Apps providing the AppWidget have access to it.
+ return true;
+ }
+ if (isHostAccessingProvider(widget.host, widget.provider, uid, packageName)) {
+ // Apps hosting the AppWidget get to bind to a remote view service in the provider.
+ return true;
+ }
+ if (mContext.checkCallingPermission(android.Manifest.permission.BIND_APPWIDGET)
+ == PackageManager.PERMISSION_GRANTED) {
+ // Apps that can bind have access to all appWidgetIds.
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isParentOrProfile(int parentId, int profileId) {
+ if (parentId == profileId) {
+ return true;
+ }
+ return getProfileParent(profileId) == parentId;
+ }
+
+ public boolean isProviderInCallerOrInProfileAndWhitelListed(String packageName,
+ int profileId) {
+ final int callerId = UserHandle.getCallingUserId();
+ if (profileId == callerId) {
+ return true;
+ }
+ final int parentId = getProfileParent(profileId);
+ if (parentId != callerId) {
+ return false;
+ }
+ return isProviderWhitelListed(packageName, profileId);
+ }
+
+ public boolean isProviderWhitelListed(String packageName, int profileId) {
+ DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService(
+ DevicePolicyManagerInternal.class);
+
+ // If the policy manager is not available on the device we deny it all.
+ if (devicePolicyManager == null) {
+ return false;
+ }
+
+ List<String> crossProfilePackages = devicePolicyManager
+ .getCrossProfileWidgetProviders(profileId);
+
+ return crossProfilePackages.contains(packageName);
+ }
+
+ public int getProfileParent(int profileId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo parent = mUserManager.getProfileParent(profileId);
+ if (parent != null) {
+ return parent.getUserHandle().getIdentifier();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return UNKNOWN_USER_ID;
+ }
+
+ public int getGroupParent(int profileId) {
+ final int parentId = mSecurityPolicy.getProfileParent(profileId);
+ return (parentId != UNKNOWN_USER_ID) ? parentId : profileId;
+ }
+
+ public boolean isHostInPackageForUid(Host host, int uid, String packageName) {
+ if (UserHandle.getAppId(uid) == Process.myUid()) {
+ // For a host that's in the system process, ignore the user id.
+ return UserHandle.isSameApp(host.id.uid, uid)
+ && host.id.packageName.equals(packageName);
+ } else {
+ return host.id.uid == uid
+ && host.id.packageName.equals(packageName);
+ }
+ }
+
+ public boolean isProviderInPackageForUid(Provider provider, int uid,
+ String packageName) {
+ // Packages providing the AppWidget have access to it.
+ return provider != null && provider.id.uid == uid
+ && provider.id.componentName.getPackageName().equals(packageName);
+ }
+
+ public boolean isHostAccessingProvider(Host host, Provider provider, int uid,
+ String packageName) {
+ // The host creates a package context to bind to remote views service in the provider.
+ return host.id.uid == uid && provider != null
+ && provider.id.componentName.getPackageName().equals(packageName);
+ }
+
+ private boolean isProfileEnabled(int profileId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUserManager.getUserInfo(profileId);
+ if (userInfo == null || !userInfo.isEnabled()) {
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return true;
+ }
+ }
+
+ private static final class Provider {
+ ProviderId id;
+ AppWidgetProviderInfo info;
+ ArrayList<Widget> widgets = new ArrayList<>();
+ PendingIntent broadcast;
+ boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+
+ int tag = TAG_UNDEFINED; // for use while saving state (the index)
+
+ public int getUserId() {
+ return UserHandle.getUserId(id.uid);
+ }
+
+ public boolean isInPackageForUser(String packageName, int userId) {
+ return getUserId() == userId
+ && id.componentName.getPackageName().equals(packageName);
+ }
+
+ // is there an instance of this provider hosted by the given app?
+ public boolean hostedByPackageForUser(String packageName, int userId) {
+ final int N = widgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = widgets.get(i);
+ if (packageName.equals(widget.host.id.packageName)
+ && widget.host.getUserId() == userId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Provider{" + id + (zombie ? " Z" : "") + '}';
+ }
+ }
+
+ private static final class ProviderId {
+ final int uid;
+ final ComponentName componentName;
+
+ private ProviderId(int uid, ComponentName componentName) {
+ this.uid = uid;
+ this.componentName = componentName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ProviderId other = (ProviderId) obj;
+ if (uid != other.uid) {
+ return false;
+ }
+ if (componentName == null) {
+ if (other.componentName != null) {
+ return false;
+ }
+ } else if (!componentName.equals(other.componentName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uid;
+ result = 31 * result + ((componentName != null)
+ ? componentName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ProviderId{user:" + UserHandle.getUserId(uid) + ", app:"
+ + UserHandle.getAppId(uid) + ", cmp:" + componentName + '}';
+ }
+ }
+
+ private static final class Host {
+ HostId id;
+ ArrayList<Widget> widgets = new ArrayList<>();
+ IAppWidgetHost callbacks;
+ boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
+
+ int tag = TAG_UNDEFINED; // for use while saving state (the index)
+
+ public int getUserId() {
+ return UserHandle.getUserId(id.uid);
+ }
+
+ public boolean isInPackageForUser(String packageName, int userId) {
+ return getUserId() == userId && id.packageName.equals(packageName);
+ }
+
+ private boolean hostsPackageForUser(String pkg, int userId) {
+ final int N = widgets.size();
+ for (int i = 0; i < N; i++) {
+ Provider provider = widgets.get(i).provider;
+ if (provider != null && provider.getUserId() == userId && provider.info != null
+ && pkg.equals(provider.info.provider.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Host{" + id + (zombie ? " Z" : "") + '}';
+ }
+ }
+
+ private static final class HostId {
+ final int uid;
+ final int hostId;
+ final String packageName;
+
+ public HostId(int uid, int hostId, String packageName) {
+ this.uid = uid;
+ this.hostId = hostId;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HostId other = (HostId) obj;
+ if (uid != other.uid) {
+ return false;
+ }
+ if (hostId != other.hostId) {
+ return false;
+ }
+ if (packageName == null) {
+ if (other.packageName != null) {
+ return false;
+ }
+ } else if (!packageName.equals(other.packageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uid;
+ result = 31 * result + hostId;
+ result = 31 * result + ((packageName != null)
+ ? packageName.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "HostId{user:" + UserHandle.getUserId(uid) + ", app:"
+ + UserHandle.getAppId(uid) + ", hostId:" + hostId
+ + ", pkg:" + packageName + '}';
+ }
+ }
+
+ private static final class Widget {
+ int appWidgetId;
+ int restoredId; // tracking & remapping any restored state
+ Provider provider;
+ RemoteViews views;
+ Bundle options;
+ Host host;
+
+ @Override
+ public String toString() {
+ return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
+ }
+ }
+
+ /**
+ * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
+ * needs to be a static inner class since a reference to the ServiceConnection is held globally
+ * and may lead us to leak AppWidgetService instances (if there were more than one).
+ */
+ private static final class ServiceConnectionProxy implements ServiceConnection {
+ private final IRemoteViewsAdapterConnection mConnectionCb;
+
+ ServiceConnectionProxy(IBinder connectionCb) {
+ mConnectionCb = IRemoteViewsAdapterConnection.Stub
+ .asInterface(connectionCb);
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mConnectionCb.onServiceConnected(service);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error passing service interface", re);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ disconnect();
+ }
+
+ public void disconnect() {
+ try {
+ mConnectionCb.onServiceDisconnected();
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error clearing service interface", re);
+ }
+ }
+ }
+
+ private class LoadedWidgetState {
+ final Widget widget;
+ final int hostTag;
+ final int providerTag;
+
+ public LoadedWidgetState(Widget widget, int hostTag, int providerTag) {
+ this.widget = widget;
+ this.hostTag = hostTag;
+ this.providerTag = providerTag;
+ }
+ }
+
+ private final class SaveStateRunnable implements Runnable {
+ final int mUserId;
+
+ public SaveStateRunnable(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ ensureGroupStateLoadedLocked(mUserId);
+ saveStateLocked(mUserId);
+ }
+ }
+ }
+
+ /**
+ * This class encapsulates the backup and restore logic for a user group state.
+ */
+ private final class BackupRestoreController {
+ private static final String TAG = "BackupRestoreController";
+
+ private static final boolean DEBUG = true;
+
+ // Version of backed-up widget state.
+ private static final int WIDGET_STATE_VERSION = 2;
+
+ // We need to make sure to wipe the pre-restore widget state only once for
+ // a given package. Keep track of what we've done so far here; the list is
+ // cleared at the start of every system restore pass, but preserved through
+ // any install-time restore operations.
+ private final HashSet<String> mPrunedApps = new HashSet<>();
+
+ private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider =
+ new HashMap<>();
+ private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost =
+ new HashMap<>();
+
+ public List<String> getWidgetParticipants(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Getting widget participants for user: " + userId);
+ }
+
+ HashSet<String> packages = new HashSet<>();
+ synchronized (mLock) {
+ final int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ // Skip cross-user widgets.
+ if (!isProviderAndHostInUser(widget, userId)) {
+ continue;
+ }
+
+ packages.add(widget.host.id.packageName);
+ Provider provider = widget.provider;
+ if (provider != null) {
+ packages.add(provider.id.componentName.getPackageName());
+ }
+ }
+ }
+ return new ArrayList<>(packages);
+ }
+
+ public byte[] getWidgetState(String backedupPackage, int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Getting widget state for user: " + userId);
+ }
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ synchronized (mLock) {
+ // Preflight: if this app neither hosts nor provides any live widgets
+ // we have no work to do.
+ if (!packageNeedsWidgetBackupLocked(backedupPackage, userId)) {
+ return null;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "ws"); // widget state
+ out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
+ out.attribute(null, "pkg", backedupPackage);
+
+ // Remember all the providers that are currently hosted or published
+ // by this package: that is, all of the entities related to this app
+ // which will need to be told about id remapping.
+ int index = 0;
+ int N = mProviders.size();
+ for (int i = 0; i < N; i++) {
+ Provider provider = mProviders.get(i);
+
+ if (!provider.widgets.isEmpty()
+ && (provider.isInPackageForUser(backedupPackage, userId)
+ || provider.hostedByPackageForUser(backedupPackage, userId))) {
+ provider.tag = index;
+ serializeProvider(out, provider);
+ index++;
+ }
+ }
+
+ N = mHosts.size();
+ index = 0;
+ for (int i = 0; i < N; i++) {
+ Host host = mHosts.get(i);
+
+ if (!host.widgets.isEmpty()
+ && (host.isInPackageForUser(backedupPackage, userId)
+ || host.hostsPackageForUser(backedupPackage, userId))) {
+ host.tag = index;
+ serializeHost(out, host);
+ index++;
+ }
+ }
+
+ // All widget instances involving this package,
+ // either as host or as provider
+ N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ Provider provider = widget.provider;
+ if (widget.host.isInPackageForUser(backedupPackage, userId)
+ || (provider != null
+ && provider.isInPackageForUser(backedupPackage, userId))) {
+ serializeAppWidget(out, widget);
+ }
+ }
+
+ out.endTag(null, "ws");
+ out.endDocument();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to save widget state for " + backedupPackage);
+ return null;
+ }
+ }
+
+ return stream.toByteArray();
+ }
+
+ public void restoreStarting(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restore starting for user: " + userId);
+ }
+
+ synchronized (mLock) {
+ // We're starting a new "system" restore operation, so any widget restore
+ // state that we see from here on is intended to replace the current
+ // widget configuration of any/all of the affected apps.
+ mPrunedApps.clear();
+ mUpdatesByProvider.clear();
+ mUpdatesByHost.clear();
+ }
+ }
+
+ public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "Restoring widget state for user:" + userId
+ + " package: " + packageName);
+ }
+
+ ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
+ try {
+ // Providers mentioned in the widget dataset by ordinal
+ ArrayList<Provider> restoredProviders = new ArrayList<>();
+
+ // Hosts mentioned in the widget dataset by ordinal
+ ArrayList<Host> restoredHosts = new ArrayList<>();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+
+ synchronized (mLock) {
+ int type;
+ do {
+ type = parser.next();
+ if (type == XmlPullParser.START_TAG) {
+ final String tag = parser.getName();
+ if ("ws".equals(tag)) {
+ String version = parser.getAttributeValue(null, "version");
+
+ final int versionNumber = Integer.parseInt(version);
+ if (versionNumber > WIDGET_STATE_VERSION) {
+ Slog.w(TAG, "Unable to process state version " + version);
+ return;
+ }
+
+ // TODO: fix up w.r.t. canonical vs current package names
+ String pkg = parser.getAttributeValue(null, "pkg");
+ if (!packageName.equals(pkg)) {
+ Slog.w(TAG, "Package mismatch in ws");
+ return;
+ }
+ } else if ("p".equals(tag)) {
+ String pkg = parser.getAttributeValue(null, "pkg");
+ String cl = parser.getAttributeValue(null, "cl");
+
+ // hostedProviders index will match 'p' attribute in widget's
+ // entry in the xml file being restored
+ // If there's no live entry for this provider, add an inactive one
+ // so that widget IDs referring to them can be properly allocated
+
+ // Backup and resotre only for the parent profile.
+ ComponentName componentName = new ComponentName(pkg, cl);
+
+ Provider p = findProviderLocked(componentName, userId);
+ if (p == null) {
+ p = new Provider();
+ p.id = new ProviderId(UNKNOWN_UID, componentName);
+ p.info = new AppWidgetProviderInfo();
+ p.info.provider = componentName;
+ p.zombie = true;
+ mProviders.add(p);
+ }
+ if (DEBUG) {
+ Slog.i(TAG, " provider " + p.id);
+ }
+ restoredProviders.add(p);
+ } else if ("h".equals(tag)) {
+ // The host app may not yet exist on the device. If it's here we
+ // just use the existing Host entry, otherwise we create a
+ // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
+ String pkg = parser.getAttributeValue(null, "pkg");
+
+ final int uid = getUidForPackage(pkg, userId);
+ final int hostId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+
+ HostId id = new HostId(uid, hostId, pkg);
+ Host h = lookupOrAddHostLocked(id);
+ restoredHosts.add(h);
+
+ if (DEBUG) {
+ Slog.i(TAG, " host[" + restoredHosts.size()
+ + "]: {" + h.id + "}");
+ }
+ } else if ("g".equals(tag)) {
+ int restoredId = Integer.parseInt(
+ parser.getAttributeValue(null, "id"), 16);
+ int hostIndex = Integer.parseInt(
+ parser.getAttributeValue(null, "h"), 16);
+ Host host = restoredHosts.get(hostIndex);
+ Provider p = null;
+ String prov = parser.getAttributeValue(null, "p");
+ if (prov != null) {
+ // could have been null if the app had allocated an id
+ // but not yet established a binding under that id
+ int which = Integer.parseInt(prov, 16);
+ p = restoredProviders.get(which);
+ }
+
+ // We'll be restoring widget state for both the host and
+ // provider sides of this widget ID, so make sure we are
+ // beginning from a clean slate on both fronts.
+ pruneWidgetStateLocked(host.id.packageName, userId);
+ if (p != null) {
+ pruneWidgetStateLocked(p.id.componentName.getPackageName(),
+ userId);
+ }
+
+ // Have we heard about this ancestral widget instance before?
+ Widget id = findRestoredWidgetLocked(restoredId, host, p);
+ if (id == null) {
+ id = new Widget();
+ id.appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
+ id.restoredId = restoredId;
+ id.options = parseWidgetIdOptions(parser);
+ id.host = host;
+ id.host.widgets.add(id);
+ id.provider = p;
+ if (id.provider != null) {
+ id.provider.widgets.add(id);
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "New restored id " + restoredId
+ + " now " + id);
+ }
+ mWidgets.add(id);
+ }
+ if (id.provider.info != null) {
+ stashProviderRestoreUpdateLocked(id.provider,
+ restoredId, id.appWidgetId);
+ } else {
+ Slog.w(TAG, "Missing provider for restored widget " + id);
+ }
+ stashHostRestoreUpdateLocked(id.host, restoredId, id.appWidgetId);
+
+ if (DEBUG) {
+ Slog.i(TAG, " instance: " + restoredId
+ + " -> " + id.appWidgetId
+ + " :: p=" + id.provider);
+ }
+ }
+ }
+ } while (type != XmlPullParser.END_DOCUMENT);
+
+ // We've updated our own bookkeeping. We'll need to notify the hosts and
+ // providers about the changes, but we can't do that yet because the restore
+ // target is not necessarily fully live at this moment. Set aside the
+ // information for now; the backup manager will call us once more at the
+ // end of the process when all of the targets are in a known state, and we
+ // will update at that point.
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(TAG, "Unable to restore widget state for " + packageName);
+ } finally {
+ saveGroupStateAsync(userId);
+ }
+ }
+
+ // Called once following the conclusion of a restore operation. This is when we
+ // send out updates to apps involved in widget-state restore telling them about
+ // the new widget ID space.
+ public void restoreFinished(int userId) {
+ if (DEBUG) {
+ Slog.i(TAG, "restoreFinished for " + userId);
+ }
+
+ final UserHandle userHandle = new UserHandle(userId);
+ synchronized (mLock) {
+ // Build the providers' broadcasts and send them off
+ Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
+ = mUpdatesByProvider.entrySet();
+ for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
+ // For each provider there's a list of affected IDs
+ Provider provider = e.getKey();
+ ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG) {
+ Slog.i(TAG, "Provider " + provider + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcastLocked(
+ AppWidgetManager.ACTION_APPWIDGET_RESTORED,
+ provider, null, oldIds, newIds, userHandle);
+ }
+ }
+
+ // same thing per host
+ Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
+ = mUpdatesByHost.entrySet();
+ for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
+ Host host = e.getKey();
+ if (host.id.uid != UNKNOWN_UID) {
+ ArrayList<RestoreUpdateRecord> updates = e.getValue();
+ final int pending = countPendingUpdates(updates);
+ if (DEBUG) {
+ Slog.i(TAG, "Host " + host + " pending: " + pending);
+ }
+ if (pending > 0) {
+ int[] oldIds = new int[pending];
+ int[] newIds = new int[pending];
+ final int N = updates.size();
+ int nextPending = 0;
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ r.notified = true;
+ oldIds[nextPending] = r.oldId;
+ newIds[nextPending] = r.newId;
+ nextPending++;
+ if (DEBUG) {
+ Slog.i(TAG, " " + r.oldId + " => " + r.newId);
+ }
+ }
+ }
+ sendWidgetRestoreBroadcastLocked(
+ AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
+ null, host, oldIds, newIds, userHandle);
+ }
+ }
+ }
+ }
+ }
+
+ private Provider findProviderLocked(ComponentName componentName, int userId) {
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ Provider provider = mProviders.get(i);
+ if (provider.getUserId() == userId
+ && provider.id.componentName.equals(componentName)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private Widget findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
+ if (DEBUG) {
+ Slog.i(TAG, "Find restored widget: id=" + restoredId
+ + " host=" + host + " provider=" + p);
+ }
+
+ if (p == null || host == null) {
+ return null;
+ }
+
+ final int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+ if (widget.restoredId == restoredId
+ && widget.host.id.equals(host.id)
+ && widget.provider.id.equals(p.id)) {
+ if (DEBUG) {
+ Slog.i(TAG, " Found at " + i + " : " + widget);
+ }
+ return widget;
+ }
+ }
+ return null;
+ }
+
+ private boolean packageNeedsWidgetBackupLocked(String packageName, int userId) {
+ int N = mWidgets.size();
+ for (int i = 0; i < N; i++) {
+ Widget widget = mWidgets.get(i);
+
+ // Skip cross-user widgets.
+ if (!isProviderAndHostInUser(widget, userId)) {
+ continue;
+ }
+
+ if (widget.host.isInPackageForUser(packageName, userId)) {
+ // this package is hosting widgets, so it knows widget IDs.
+ return true;
+ }
+
+ Provider provider = widget.provider;
+ if (provider != null && provider.isInPackageForUser(packageName, userId)) {
+ // someone is hosting this app's widgets, so it knows widget IDs.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) {
+ ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
+ if (r == null) {
+ r = new ArrayList<>();
+ mUpdatesByProvider.put(provider, r);
+ } else {
+ // don't duplicate
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + provider);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
+ final int oldId, final int newId) {
+ final int N = stash.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = stash.get(i);
+ if (r.oldId == oldId && r.newId == newId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) {
+ ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
+ if (r == null) {
+ r = new ArrayList<>();
+ mUpdatesByHost.put(host, r);
+ } else {
+ if (alreadyStashed(r, oldId, newId)) {
+ if (DEBUG) {
+ Slog.i(TAG, "ID remap " + oldId + " -> " + newId
+ + " already stashed for " + host);
+ }
+ return;
+ }
+ }
+ r.add(new RestoreUpdateRecord(oldId, newId));
+ }
+
+ private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
+ Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
+ Intent intent = new Intent(action);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
+ if (provider != null) {
+ intent.setComponent(provider.info.provider);
+ sendBroadcastAsUser(intent, userHandle);
+ }
+ if (host != null) {
+ intent.setComponent(null);
+ intent.setPackage(host.id.packageName);
+ intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
+ sendBroadcastAsUser(intent, userHandle);
+ }
+ }
+
+ // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
+ // instances that are hosted by that app, and (b) all instances in other hosts
+ // for which 'pkg' is the provider. We assume that we'll be restoring all of
+ // these hosts & providers, so will be reconstructing a correct live state.
+ private void pruneWidgetStateLocked(String pkg, int userId) {
+ if (!mPrunedApps.contains(pkg)) {
+ if (DEBUG) {
+ Slog.i(TAG, "pruning widget state for restoring package " + pkg);
+ }
+ for (int i = mWidgets.size() - 1; i >= 0; i--) {
+ Widget widget = mWidgets.get(i);
+
+ Host host = widget.host;
+ Provider provider = widget.provider;
+
+ if (host.hostsPackageForUser(pkg, userId)
+ || (provider != null && provider.isInPackageForUser(pkg, userId))) {
+ // 'pkg' is either the host or the provider for this instances,
+ // so we tear it down in anticipation of it (possibly) being
+ // reconstructed due to the restore
+ host.widgets.remove(widget);
+ provider.widgets.remove(widget);
+ unbindAppWidgetRemoteViewsServicesLocked(widget);
+ mWidgets.remove(i);
+ }
+ }
+ mPrunedApps.add(pkg);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
+ }
+ }
+ }
+
+ private boolean isProviderAndHostInUser(Widget widget, int userId) {
+ // Backup only widgets hosted or provided by the owner profile.
+ return widget.host.getUserId() == userId && (widget.provider == null
+ || widget.provider.getUserId() == userId);
+ }
+
+ private Bundle parseWidgetIdOptions(XmlPullParser parser) {
+ Bundle options = new Bundle();
+ String minWidthString = parser.getAttributeValue(null, "min_width");
+ if (minWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ Integer.parseInt(minWidthString, 16));
+ }
+ String minHeightString = parser.getAttributeValue(null, "min_height");
+ if (minHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ Integer.parseInt(minHeightString, 16));
+ }
+ String maxWidthString = parser.getAttributeValue(null, "max_width");
+ if (maxWidthString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ Integer.parseInt(maxWidthString, 16));
+ }
+ String maxHeightString = parser.getAttributeValue(null, "max_height");
+ if (maxHeightString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ Integer.parseInt(maxHeightString, 16));
+ }
+ String categoryString = parser.getAttributeValue(null, "host_category");
+ if (categoryString != null) {
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ Integer.parseInt(categoryString, 16));
+ }
+ return options;
+ }
+
+ private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
+ int pending = 0;
+ final int N = updates.size();
+ for (int i = 0; i < N; i++) {
+ RestoreUpdateRecord r = updates.get(i);
+ if (!r.notified) {
+ pending++;
+ }
+ }
+ return pending;
+ }
+
+ // Accumulate a list of updates that affect the given provider for a final
+ // coalesced notification broadcast once restore is over.
+ private class RestoreUpdateRecord {
+ public int oldId;
+ public int newId;
+ public boolean notified;
+
+ public RestoreUpdateRecord(int theOldId, int theNewId) {
+ oldId = theOldId;
+ newId = theNewId;
+ notified = false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d434d7a..c77c5b2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1266,10 +1266,13 @@
ArrayList<FullBackupEntry> schedule = null;
synchronized (mQueueLock) {
if (mFullBackupScheduleFile.exists()) {
+ FileInputStream fstream = null;
+ BufferedInputStream bufStream = null;
+ DataInputStream in = null;
try {
- FileInputStream fstream = new FileInputStream(mFullBackupScheduleFile);
- BufferedInputStream bufStream = new BufferedInputStream(fstream);
- DataInputStream in = new DataInputStream(bufStream);
+ fstream = new FileInputStream(mFullBackupScheduleFile);
+ bufStream = new BufferedInputStream(fstream);
+ in = new DataInputStream(bufStream);
int version = in.readInt();
if (version != SCHEDULE_FILE_VERSION) {
@@ -1289,6 +1292,10 @@
Slog.e(TAG, "Unable to read backup schedule", e);
mFullBackupScheduleFile.delete();
schedule = null;
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(bufStream);
+ IoUtils.closeQuietly(fstream);
}
}
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
index d8ee8a1..bffbb4c2 100644
--- a/services/core/java/com/android/server/WiredAccessoryManager.java
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -36,8 +36,10 @@
import com.android.server.input.InputManagerService.WiredAccessoryCallbacks;
import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT;
import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT;
+import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT;
import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT;
import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT;
+import static com.android.server.input.InputManagerService.SW_LINEOUT_INSERT_BIT;
import java.io.File;
import java.io.FileReader;
@@ -60,9 +62,10 @@
private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
private static final int BIT_HDMI_AUDIO = (1 << 4);
+ private static final int BIT_LINEOUT = (1 << 5);
private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
- BIT_HDMI_AUDIO);
+ BIT_HDMI_AUDIO|BIT_LINEOUT);
private static final String NAME_H2W = "h2w";
private static final String NAME_USB_AUDIO = "usb_audio";
@@ -108,8 +111,11 @@
if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) {
switchValues |= SW_MICROPHONE_INSERT_BIT;
}
+ if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_LINEOUT_INSERT) == 1) {
+ switchValues |= SW_LINEOUT_INSERT_BIT;
+ }
notifyWiredAccessoryChanged(0, switchValues,
- SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT);
+ SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT);
}
mObserver.init();
@@ -124,7 +130,8 @@
synchronized (mLock) {
int headset;
mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
- switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) {
+ switch (mSwitchValues &
+ (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) {
case 0:
headset = 0;
break;
@@ -133,6 +140,10 @@
headset = BIT_HEADSET_NO_MIC;
break;
+ case SW_LINEOUT_INSERT_BIT:
+ headset = BIT_LINEOUT;
+ break;
+
case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT:
headset = BIT_HEADSET;
break;
@@ -146,7 +157,8 @@
break;
}
- updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset);
+ updateLocked(NAME_H2W,
+ (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
}
}
@@ -174,7 +186,7 @@
int headsetState = newState & SUPPORTED_HEADSETS;
int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
- int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
+ int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT);
boolean h2wStateChange = true;
boolean usbStateChange = true;
if (LOG) Slog.v(TAG, "newName=" + newName
@@ -190,7 +202,7 @@
// reject all suspect transitions: only accept state changes from:
// - a: 0 headset to 1 headset
// - b: 1 headset to 0 headset
- if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) {
+ if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) {
Log.e(TAG, "Invalid combination, unsetting h2w flag");
h2wStateChange = false;
}
@@ -261,6 +273,8 @@
inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET;
} else if (headset == BIT_HEADSET_NO_MIC){
outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
+ } else if (headset == BIT_LINEOUT){
+ outDevice = AudioManager.DEVICE_OUT_LINE;
} else if (headset == BIT_USB_HEADSET_ANLG) {
outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
} else if (headset == BIT_USB_HEADSET_DGTL) {
@@ -345,7 +359,7 @@
// Monitor h2w
if (!mUseDevInputEventForAudioJack) {
- uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC);
+ uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
@@ -354,7 +368,7 @@
}
// Monitor USB
- uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL);
+ uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
@@ -369,11 +383,11 @@
//
// If the kernel does not have an "hdmi_audio" switch, just fall back on the older
// "hdmi" switch instead.
- uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0);
+ uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
- uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0);
+ uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
@@ -414,11 +428,13 @@
private final String mDevName;
private final int mState1Bits;
private final int mState2Bits;
+ private final int mStateNbits;
- public UEventInfo(String devName, int state1Bits, int state2Bits) {
+ public UEventInfo(String devName, int state1Bits, int state2Bits, int stateNbits) {
mDevName = devName;
mState1Bits = state1Bits;
mState2Bits = state2Bits;
+ mStateNbits = stateNbits;
}
public String getDevName() { return mDevName; }
@@ -437,9 +453,10 @@
}
public int computeNewHeadsetState(int headsetState, int switchState) {
- int preserveMask = ~(mState1Bits | mState2Bits);
+ int preserveMask = ~(mState1Bits | mState2Bits | mStateNbits);
int setBits = ((switchState == 1) ? mState1Bits :
- ((switchState == 2) ? mState2Bits : 0));
+ ((switchState == 2) ? mState2Bits :
+ ((switchState == mStateNbits) ? mStateNbits : 0)));
return ((headsetState & preserveMask) | setBits);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 29fd984..ae2ef06 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13831,6 +13831,10 @@
}
} else if ("system".equals(componentProcessName)) {
result = true;
+ } else if (UserHandle.isSameApp(aInfo.uid, Process.PHONE_UID)
+ && (flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
+ // Phone app is allowed to export singleuser providers.
+ result = true;
} else {
// App with pre-defined UID, check if it's a persistent app
result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b9acea5..7d3738c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -523,6 +523,17 @@
return -1;
}
+ private void resizeVirtualDisplayInternal(IBinder appToken,
+ int width, int height, int densityDpi) {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ return;
+ }
+
+ mVirtualDisplayAdapter.resizeVirtualDisplayLocked(appToken, width, height, densityDpi);
+ }
+ }
+
private void setVirtualDisplaySurfaceInternal(IBinder appToken, Surface surface) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
@@ -1304,6 +1315,17 @@
}
@Override // Binder call
+ public void resizeVirtualDisplay(IVirtualDisplayCallbacks callbacks,
+ int width, int height, int densityDpi) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ resizeVirtualDisplayInternal(callbacks.asBinder(), width, height, densityDpi);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void setVirtualDisplaySurface(IVirtualDisplayCallbacks callbacks, Surface surface) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 1032081..0ebd2de 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -83,6 +83,15 @@
return device;
}
+ public void resizeVirtualDisplayLocked(IBinder appToken,
+ int width, int height, int densityDpi) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+ if (device != null) {
+ device.resizeLocked(width, height, densityDpi);
+ }
+ }
+
+
public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
if (device != null) {
@@ -122,20 +131,24 @@
}
private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
+ private static final int PENDING_SURFACE_CHANGE = 0x01;
+ private static final int PENDING_RESIZE = 0x02;
+
private final IBinder mAppToken;
private final int mOwnerUid;
final String mOwnerPackageName;
final String mName;
- private final int mWidth;
- private final int mHeight;
- private final int mDensityDpi;
private final int mFlags;
private final Callbacks mCallbacks;
+ private int mWidth;
+ private int mHeight;
+ private int mDensityDpi;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
- private int mState;
+ private int mDisplayState;
private boolean mStopped;
+ private int mPendingChanges;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName,
@@ -152,7 +165,8 @@
mSurface = surface;
mFlags = flags;
mCallbacks = callbacks;
- mState = Display.STATE_UNKNOWN;
+ mDisplayState = Display.STATE_UNKNOWN;
+ mPendingChanges |= PENDING_SURFACE_CHANGE;
}
@Override
@@ -175,8 +189,8 @@
@Override
public void requestDisplayStateLocked(int state) {
- if (state != mState) {
- mState = state;
+ if (state != mDisplayState) {
+ mDisplayState = state;
if (state == Display.STATE_OFF) {
mCallbacks.dispatchDisplayPaused();
} else {
@@ -187,7 +201,13 @@
@Override
public void performTraversalInTransactionLocked() {
- setSurfaceInTransactionLocked(mSurface);
+ if ((mPendingChanges & PENDING_RESIZE) != 0) {
+ SurfaceControl.setDisplaySize(getDisplayTokenLocked(), mWidth, mHeight);
+ }
+ if ((mPendingChanges & PENDING_SURFACE_CHANGE) != 0) {
+ setSurfaceInTransactionLocked(mSurface);
+ }
+ mPendingChanges = 0;
}
public void setSurfaceLocked(Surface surface) {
@@ -198,6 +218,19 @@
sendTraversalRequestLocked();
mSurface = surface;
mInfo = null;
+ mPendingChanges |= PENDING_SURFACE_CHANGE;
+ }
+ }
+
+ public void resizeLocked(int width, int height, int densityDpi) {
+ if (mWidth != width || mHeight != height || mDensityDpi != densityDpi) {
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ sendTraversalRequestLocked();
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+ mInfo = null;
+ mPendingChanges |= PENDING_RESIZE;
}
}
@@ -210,7 +243,7 @@
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
pw.println("mFlags=" + mFlags);
- pw.println("mState=" + Display.stateToString(mState));
+ pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
pw.println("mStopped=" + mStopped);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 70d108a..b36c176 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -165,6 +165,11 @@
@ServiceThreadOnly
void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
assertRunOnServiceThread();
+ ActiveSource active = getActiveSource();
+ if (active.isValid() && targetAddress == active.logicalAddress) {
+ invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
+ return;
+ }
if (targetAddress == Constants.ADDR_INTERNAL) {
handleSelectInternalSource();
// Switching to internal source is always successful even when CEC control is disabled.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index fe55b01..edadf6d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -207,10 +207,10 @@
private List<HdmiPortInfo> mPortInfo;
// Map from path(physical address) to port ID.
- private final SparseIntArray mPortIdMap = new SparseIntArray();
+ private UnmodifiableSparseIntArray mPortIdMap;
// Map from port ID to HdmiPortInfo.
- private final SparseArray<HdmiPortInfo> mPortInfoMap = new SparseArray<>();
+ private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
private HdmiCecMessageValidator mMessageValidator;
@@ -360,10 +360,14 @@
return;
}
+ SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
+ SparseIntArray portIdMap = new SparseIntArray();
for (HdmiPortInfo info : cecPortInfo) {
- mPortIdMap.put(info.getAddress(), info.getId());
- mPortInfoMap.put(info.getId(), info);
+ portIdMap.put(info.getAddress(), info.getId());
+ portInfoMap.put(info.getId(), info);
}
+ mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
+ mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
if (mMhlController == null) {
mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
@@ -397,9 +401,7 @@
* @param portId HDMI port id
* @return {@link HdmiPortInfo} for the given port
*/
- @ServiceThreadOnly
HdmiPortInfo getPortInfo(int portId) {
- assertRunOnServiceThread();
return mPortInfoMap.get(portId, null);
}
@@ -407,9 +409,7 @@
* Returns the routing path (physical address) of the HDMI port for the given
* port id.
*/
- @ServiceThreadOnly
int portIdToPath(int portId) {
- assertRunOnServiceThread();
HdmiPortInfo portInfo = getPortInfo(portId);
if (portInfo == null) {
Slog.e(TAG, "Cannot find the port info: " + portId);
@@ -424,16 +424,12 @@
* the port id to be returned is the ID associated with the port address
* 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
*/
- @ServiceThreadOnly
int pathToPortId(int path) {
- assertRunOnServiceThread();
int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
}
- @ServiceThreadOnly
boolean isValidPortId(int portId) {
- assertRunOnServiceThread();
return getPortInfo(portId) != null;
}
@@ -490,9 +486,7 @@
/**
* Whether a device of the specified physical address is connected to ARC enabled port.
*/
- @ServiceThreadOnly
boolean isConnectedToArcPort(int physicalAddress) {
- assertRunOnServiceThread();
int portId = mPortIdMap.get(physicalAddress);
if (portId != Constants.INVALID_PORT_ID) {
return mPortInfoMap.get(portId).isArcSupported();
diff --git a/services/core/java/com/android/server/hdmi/UnmodifiableSparseArray.java b/services/core/java/com/android/server/hdmi/UnmodifiableSparseArray.java
new file mode 100644
index 0000000..5c0a360
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/UnmodifiableSparseArray.java
@@ -0,0 +1,62 @@
+/*
+ * 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.hdmi;
+
+import android.util.SparseArray;
+
+/**
+ * Unmodifiable version of {@link SparseArray}.
+ */
+final class UnmodifiableSparseArray<E> {
+ private static final String TAG = "ImmutableSparseArray";
+
+ private final SparseArray<E> mArray;
+
+ public UnmodifiableSparseArray(SparseArray<E> array) {
+ mArray = array;
+ }
+
+ public int size() {
+ return mArray.size();
+ }
+
+ public E get(int key) {
+ return mArray.get(key);
+ }
+
+ public E get(int key, E valueIfKeyNotFound) {
+ return mArray.get(key, valueIfKeyNotFound);
+ }
+
+ public int keyAt(int index) {
+ return mArray.keyAt(index);
+ }
+
+ public E valueAt(int index) {
+ return mArray.valueAt(index);
+ }
+
+ public int indexOfValue(E value) {
+ return mArray.indexOfValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return mArray.toString();
+ }
+}
+
diff --git a/services/core/java/com/android/server/hdmi/UnmodifiableSparseIntArray.java b/services/core/java/com/android/server/hdmi/UnmodifiableSparseIntArray.java
new file mode 100644
index 0000000..cada855
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/UnmodifiableSparseIntArray.java
@@ -0,0 +1,61 @@
+/*
+ * 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.hdmi;
+
+import android.util.SparseIntArray;
+
+/**
+ * Unmodifiable version of {@link SparseIntArray}.
+ */
+final class UnmodifiableSparseIntArray {
+ private static final String TAG = "ImmutableSparseIntArray";
+
+ private final SparseIntArray mArray;
+
+ public UnmodifiableSparseIntArray(SparseIntArray array) {
+ mArray = array;
+ }
+
+ public int size() {
+ return mArray.size();
+ }
+
+ public int get(int key) {
+ return mArray.get(key);
+ }
+
+ public int get(int key, int valueIfKeyNotFound) {
+ return mArray.get(key, valueIfKeyNotFound);
+ }
+
+ public int keyAt(int index) {
+ return mArray.keyAt(index);
+ }
+
+ public int valueAt(int index) {
+ return mArray.valueAt(index);
+ }
+
+ public int indexOfValue(int value) {
+ return mArray.indexOfValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return mArray.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 0f5805c..7c1681c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -235,6 +235,9 @@
/** Switch code: Microphone. When set, microphone is inserted. */
public static final int SW_MICROPHONE_INSERT = 0x04;
+ /** Switch code: Line out. When set, Line out (hi-Z) is inserted. */
+ public static final int SW_LINEOUT_INSERT = 0x06;
+
/** Switch code: Headphone/Microphone Jack. When set, something is inserted. */
public static final int SW_JACK_PHYSICAL_INSERT = 0x07;
@@ -242,9 +245,10 @@
public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE;
public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT;
public static final int SW_MICROPHONE_INSERT_BIT = 1 << SW_MICROPHONE_INSERT;
+ public static final int SW_LINEOUT_INSERT_BIT = 1 << SW_LINEOUT_INSERT;
public static final int SW_JACK_PHYSICAL_INSERT_BIT = 1 << SW_JACK_PHYSICAL_INSERT;
public static final int SW_JACK_BITS =
- SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT;
+ SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT | SW_LINEOUT_INSERT_BIT;
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
final boolean mUseDevInputEventForAudioJack;
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 495d3a9..530ad4b 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -272,8 +272,7 @@
synchronized (mLocationSinkLock) {
// only one sink is allowed at the moment
if (mLocationSink != null) {
- throw new RuntimeException(
- "IFusedLocationHardware does not support multiple sinks");
+ Log.e(TAG, "Replacing an existing IFusedLocationHardware sink");
}
mLocationSink = eventSink;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 2f1bd60..d71f66a 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -431,9 +431,9 @@
private String getShortMetadataString() {
int fields = mMetadata == null ? 0 : mMetadata.size();
- String title = mMetadata == null ? null : mMetadata
- .getString(MediaMetadata.METADATA_KEY_TITLE);
- return "size=" + fields + ", title=" + title;
+ MediaMetadata.Description description = mMetadata == null ? null : mMetadata
+ .getDescription();
+ return "size=" + fields + ", description=" + description;
}
private void pushPlaybackStateUpdate() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2b55bf5..0b07f0a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11410,7 +11410,7 @@
@Override
public void replacePreferredActivity(IntentFilter filter, int match,
- ComponentName[] set, ComponentName activity) {
+ ComponentName[] set, ComponentName activity, int userId) {
if (filter.countActions() != 1) {
throw new IllegalArgumentException(
"replacePreferredActivity expects filter to have only 1 action.");
@@ -11423,11 +11423,15 @@
"replacePreferredActivity expects filter to have no data authorities, " +
"paths, or types; and at most one scheme.");
}
+
+ final int callingUid = Binder.getCallingUid();
+ enforceCrossUserPermission(callingUid, userId, true, "replace preferred activity");
+ final int callingUserId = UserHandle.getUserId(callingUid);
synchronized (mPackages) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
- if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid())
+ if (getUidTargetSdkVersionLockedLPr(callingUid)
< Build.VERSION_CODES.FROYO) {
Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
+ Binder.getCallingUid());
@@ -11437,7 +11441,6 @@
android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
}
- final int callingUserId = UserHandle.getCallingUserId();
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId);
if (pir != null) {
Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0));
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 263767d..21905f0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -453,10 +453,10 @@
}
@Override
- public void hideRecentApps(boolean triggeredFromAltTab) {
+ public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBar != null) {
try {
- mBar.hideRecentApps(triggeredFromAltTab);
+ mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
} catch (RemoteException ex) {}
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index ba79fed..08ac210 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -930,6 +930,7 @@
if (inputId != null) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(TvContract.buildChannelUriForPassthroughTvInput(inputId));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} else {
Slog.w(TAG, "onChanged: InputId cannot be found for :" + device);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e11b6a7..18446ae 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -479,12 +479,13 @@
// Set up a callback to send the session token.
ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
@Override
- public void onSessionCreated(ITvInputSession session) {
+ public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
if (DEBUG) {
Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
}
synchronized (mLock) {
sessionState.mSession = session;
+ sessionState.mHardwareSessionToken = harewareSessionToken;
if (session == null) {
removeSessionStateLocked(sessionToken, userId);
sendSessionTokenToClientLocked(sessionState.mClient,
@@ -533,37 +534,35 @@
}
@Override
- public void onTrackInfoChanged(List<TvTrackInfo> tracks) {
+ public void onTracksChanged(List<TvTrackInfo> tracks) {
synchronized (mLock) {
if (DEBUG) {
- Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")");
+ Slog.d(TAG, "onTracksChanged(" + tracks + ")");
}
if (sessionState.mSession == null || sessionState.mClient == null) {
return;
}
try {
- sessionState.mClient.onTrackInfoChanged(tracks,
- sessionState.mSeq);
+ sessionState.mClient.onTracksChanged(tracks, sessionState.mSeq);
} catch (RemoteException e) {
- Slog.e(TAG, "error in onTrackInfoChanged");
+ Slog.e(TAG, "error in onTracksChanged");
}
}
}
@Override
- public void onTrackSelectionChanged(List<TvTrackInfo> selectedTracks) {
+ public void onTrackSelected(int type, String trackId) {
synchronized (mLock) {
if (DEBUG) {
- Slog.d(TAG, "onTrackSelectionChanged(" + selectedTracks + ")");
+ Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
}
if (sessionState.mSession == null || sessionState.mClient == null) {
return;
}
try {
- sessionState.mClient.onTrackSelectionChanged(selectedTracks,
- sessionState.mSeq);
+ sessionState.mClient.onTrackSelected(type, trackId, sessionState.mSeq);
} catch (RemoteException e) {
- Slog.e(TAG, "error in onTrackSelectionChanged");
+ Slog.e(TAG, "error in onTrackSelected");
}
}
}
@@ -1109,8 +1108,14 @@
try {
synchronized (mLock) {
try {
- getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
- surface);
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ if (sessionState.mHardwareSessionToken == null) {
+ getSessionLocked(sessionState).setSurface(surface);
+ } else {
+ getSessionLocked(sessionState.mHardwareSessionToken,
+ Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in setSurface", e);
}
@@ -1134,8 +1139,13 @@
try {
synchronized (mLock) {
try {
- getSessionLocked(sessionToken, callingUid, resolvedUserId)
- .dispatchSurfaceChanged(format, width, height);
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, height);
+ if (sessionState.mHardwareSessionToken != null) {
+ getSessionLocked(sessionState.mHardwareSessionToken, Process.SYSTEM_UID,
+ resolvedUserId).dispatchSurfaceChanged(format, width, height);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in dispatchSurfaceChanged", e);
}
@@ -1147,6 +1157,8 @@
@Override
public void setVolume(IBinder sessionToken, float volume, int userId) {
+ final float REMOTE_VOLUME_ON = 1.0f;
+ final float REMOTE_VOLUME_OFF = 0f;
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "setVolume");
@@ -1154,8 +1166,16 @@
try {
synchronized (mLock) {
try {
- getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
- volume);
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).setVolume(volume);
+ if (sessionState.mHardwareSessionToken != null) {
+ // Here, we let the hardware session know only whether volume is on or
+ // off to prevent that the volume is controlled in the both side.
+ getSessionLocked(sessionState.mHardwareSessionToken,
+ Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
+ ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "error in setVolume", e);
}
@@ -1266,7 +1286,7 @@
}
@Override
- public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
+ public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "selectTrack");
@@ -1275,7 +1295,7 @@
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
- track);
+ type, trackId);
} catch (RemoteException e) {
Slog.e(TAG, "error in selectTrack", e);
}
@@ -1286,26 +1306,6 @@
}
@Override
- public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
- final int callingUid = Binder.getCallingUid();
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "unselectTrack");
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- try {
- getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
- track);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in unselectTrack", e);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
int userId) {
final int callingUid = Binder.getCallingUid();
@@ -1479,13 +1479,24 @@
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "captureFrame");
try {
- final String wrappedInputId;
+ String hardwareInputId = null;
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
- wrappedInputId = userState.wrappedInputMap.get(inputId);
+ if (userState.inputMap.get(inputId) == null) {
+ Slog.e(TAG, "Input not found for " + inputId);
+ return false;
+ }
+ for (SessionState sessionState : userState.sessionStateMap.values()) {
+ if (sessionState.mInfo.getId().equals(inputId)
+ && sessionState.mHardwareSessionToken != null) {
+ hardwareInputId = userState.sessionStateMap.get(
+ sessionState.mHardwareSessionToken).mInfo.getId();
+ break;
+ }
+ }
}
return mTvInputHardwareManager.captureFrame(
- (wrappedInputId != null) ? wrappedInputId : inputId,
+ (hardwareInputId != null) ? hardwareInputId : inputId,
surface, config, callingUid, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1603,6 +1614,7 @@
pw.println("mSessionToken: " + session.mSessionToken);
pw.println("mSession: " + session.mSession);
pw.println("mLogUri: " + session.mLogUri);
+ pw.println("mHardwareSessionToken: " + session.mHardwareSessionToken);
pw.decreaseIndent();
}
pw.decreaseIndent();
@@ -1692,9 +1704,6 @@
private final Set<ITvInputManagerCallback> callbackSet =
new HashSet<ITvInputManagerCallback>();
- // A mapping from the TV input id to wrapped input id.
- private final Map<String, String> wrappedInputMap = new HashMap<String, String>();
-
// The token of a "main" TV input session.
private IBinder mainSessionToken = null;
@@ -1769,9 +1778,11 @@
private final IBinder mSessionToken;
private ITvInputSession mSession;
private Uri mLogUri;
+ // Not null if this session represents an external device connected to a hardware TV input.
+ private IBinder mHardwareSessionToken;
- private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
- int callingUid, int userId) {
+ private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
+ int seq, int callingUid, int userId) {
mSessionToken = sessionToken;
mInfo = info;
mClient = client;
@@ -1791,6 +1802,22 @@
Slog.e(TAG, "error in onSessionReleased", e);
}
}
+ // If there are any other sessions based on this session, they should be released.
+ UserState userState = getUserStateLocked(mUserId);
+ for (SessionState sessionState : userState.sessionStateMap.values()) {
+ if (mSession != null && mSession == sessionState.mHardwareSessionToken) {
+ try {
+ sessionState.mSession.release();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in release", e);
+ }
+ try {
+ sessionState.mClient.onSessionReleased(sessionState.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSessionReleased", e);
+ }
+ }
+ }
removeSessionStateLocked(mSessionToken, mUserId);
}
}
@@ -1892,10 +1919,8 @@
for (TvInputState inputState : userState.inputMap.values()) {
if (inputState.mInfo.getComponent().equals(component)) {
- String inputId = inputState.mInfo.getId();
- notifyInputStateChangedLocked(userState, inputId,
+ notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
INPUT_STATE_DISCONNECTED, null);
- userState.wrappedInputMap.remove(inputId);
}
}
updateServiceConnectionLocked(mComponent, mUserId);
@@ -1974,27 +1999,6 @@
}
}
}
-
- @Override
- public void setWrappedInputId(String inputId, String wrappedInputId) {
- synchronized (mLock) {
- if (!hasInputIdLocked(inputId)) {
- return;
- }
- UserState userState = getUserStateLocked(mUserId);
- userState.wrappedInputMap.put(inputId, wrappedInputId);
- }
- }
-
- private boolean hasInputIdLocked(String inputId) {
- ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
- for (TvInputInfo inputInfo : serviceState.mInputList) {
- if (inputInfo.getId().equals(inputId)) {
- return true;
- }
- }
- return false;
- }
}
private final class LogHandler extends Handler {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 80dfb91..cdb5e58 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -11258,7 +11258,7 @@
final WindowList windows = getDefaultWindowListLocked();
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
final WindowState win = windows.get(winNdx);
- if (win.mHasSurface) {
+ if (win.mHasSurface && win.mAppToken != null) {
win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
// Force add to mResizingWindows.
win.mLastContentInsets.set(-1, -1, -1, -1);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c6730bf..899a821 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,14 +18,15 @@
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import android.app.admin.DevicePolicyManagerInternal;
import com.android.internal.R;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.os.storage.ExternalStorageFormatter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.org.conscrypt.TrustedCertificateStore;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import android.app.Activity;
@@ -46,7 +47,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -80,7 +80,6 @@
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
-import android.service.trust.TrustAgentService;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -111,13 +110,11 @@
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -289,6 +286,9 @@
private static final String ATTR_VALUE = "value";
private static final String TAG_PASSWORD_QUALITY = "password-quality";
private static final String TAG_POLICIES = "policies";
+ private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
+ "cross-profile-widget-providers";
+ private static final String TAG_PROVIDER = "provider";
final DeviceAdminInfo info;
@@ -346,6 +346,8 @@
String globalProxyExclusionList = null;
HashMap<String, List<String>> trustAgentFeatures = new HashMap<String, List<String>>();
+ List<String> crossProfileWidgetProviders;
+
ActiveAdmin(DeviceAdminInfo _info) {
info = _info;
}
@@ -490,6 +492,17 @@
}
out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
}
+ if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
+ out.startTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
+ final int providerCount = crossProfileWidgetProviders.size();
+ for (int i = 0; i < providerCount; i++) {
+ String provider = crossProfileWidgetProviders.get(i);
+ out.startTag(null, TAG_PROVIDER);
+ out.attribute(null, ATTR_VALUE, provider);
+ out.endTag(null, TAG_PROVIDER);
+ }
+ out.endTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
+ }
}
void readFromXml(XmlPullParser parser)
@@ -571,6 +584,8 @@
accountTypesWithManagementDisabled = readDisableAccountInfo(parser, tag);
} else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) {
trustAgentFeatures = getAllTrustAgentFeatures(parser, tag);
+ } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) {
+ crossProfileWidgetProviders = getCrossProfileWidgetProviders(parser, tag);
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
}
@@ -640,6 +655,30 @@
return result;
}
+ private List<String> getCrossProfileWidgetProviders(XmlPullParser parser, String tag)
+ throws XmlPullParserException, IOException {
+ int outerDepthDAM = parser.getDepth();
+ int typeDAM;
+ ArrayList<String> result = null;
+ while ((typeDAM=parser.next()) != END_DOCUMENT
+ && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) {
+ if (typeDAM == END_TAG || typeDAM == TEXT) {
+ continue;
+ }
+ String tagDAM = parser.getName();
+ if (TAG_PROVIDER.equals(tagDAM)) {
+ final String provider = parser.getAttributeValue(null, ATTR_VALUE);
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(provider);
+ } else {
+ Slog.w(LOG_TAG, "Unknown tag under " + tag + ": " + tagDAM);
+ }
+ }
+ return result;
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("uid="); pw.println(getUid());
pw.print(prefix); pw.println("policies:");
@@ -695,6 +734,8 @@
pw.println(disableScreenCapture);
pw.print(prefix); pw.print("disabledKeyguardFeatures=");
pw.println(disabledKeyguardFeatures);
+ pw.print(prefix); pw.print("crossProfileWidgetProviders=");
+ pw.println(crossProfileWidgetProviders);
}
}
@@ -752,6 +793,8 @@
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
+
+ LocalServices.addService(DevicePolicyManagerInternal.class, new LocalService());
}
/**
@@ -1836,6 +1879,49 @@
}
}
+ @Override
+ public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (activeAdmin.crossProfileWidgetProviders == null) {
+ activeAdmin.crossProfileWidgetProviders = new ArrayList<>();
+ }
+ if (activeAdmin.crossProfileWidgetProviders.add(packageName)) {
+ saveSettingsLocked(UserHandle.getCallingUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (activeAdmin.crossProfileWidgetProviders == null) {
+ return false;
+ }
+ if (activeAdmin.crossProfileWidgetProviders.remove(packageName)) {
+ saveSettingsLocked(UserHandle.getCallingUserId());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public List<String> getCrossProfileWidgetProviders(ComponentName admin) {
+ ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (activeAdmin.crossProfileWidgetProviders == null
+ || activeAdmin.crossProfileWidgetProviders.isEmpty()) {
+ return null;
+ }
+ if (Binder.getCallingUid() == Process.myUid()) {
+ return new ArrayList<>(activeAdmin.crossProfileWidgetProviders);
+ } else {
+ return activeAdmin.crossProfileWidgetProviders;
+ }
+ }
+
/**
* Return a single admin's expiration date/time, or the min (soonest) for all admins.
* Returns 0 if not configured.
@@ -4486,4 +4572,24 @@
}
}
}
+
+ private final class LocalService extends DevicePolicyManagerInternal {
+ @Override
+ public List<String> getCrossProfileWidgetProviders(int profileId) {
+ ComponentName ownerComponent = mDeviceOwner.getProfileOwnerComponent(profileId);
+ if (ownerComponent == null) {
+ return Collections.emptyList();
+ }
+
+ DevicePolicyData policy = getUserData(profileId);
+ ActiveAdmin admin = policy.mAdminMap.get(ownerComponent);
+
+ if (admin.crossProfileWidgetProviders == null
+ || admin.crossProfileWidgetProviders.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return admin.crossProfileWidgetProviders;
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 8913eb9..b4c221f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -195,7 +195,8 @@
Keyphrase[] keyphrases = new Keyphrase[1];
keyphrases[0] = new Keyphrase(
keyphraseId, recognitionModes, locale, text, users);
- return new KeyphraseSoundModel(UUID.fromString(modelUuid), data, keyphrases);
+ return new KeyphraseSoundModel(UUID.fromString(modelUuid),
+ null /* FIXME use vendor UUID */, data, keyphrases);
}
Slog.w(TAG, "No SoundModel available for the given keyphrase");
} finally {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index f3ede88..fd36bfc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -25,6 +25,7 @@
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.os.RemoteException;
import android.util.Slog;
@@ -330,6 +331,23 @@
}
}
+ public void onSoundModelUpdate(SoundModelEvent event) {
+ if (event == null) {
+ Slog.w(TAG, "Invalid sound model event!");
+ return;
+ }
+
+ if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
+
+ //TODO: implement sound model update
+ }
+
+ public void onServiceStateChange(int state) {
+ if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
+
+ //TODO: implement service state update
+ }
+
@Override
public void onServiceDied() {
synchronized (this) {
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index b55f62a..8845821 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -19,9 +19,6 @@
import android.app.PendingIntent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.HashSet;
@@ -33,32 +30,6 @@
*/
public abstract class Connection {
- private static final int MSG_ADD_CONNECTION_LISTENER = 1;
- private static final int MSG_REMOVE_CONNECTION_LISTENER = 2;
- private static final int MSG_SET_AUDIO_STATE = 3;
- private static final int MSG_SET_PARENT_CONNECTION = 4;
- private static final int MSG_SET_HANDLE = 5;
- private static final int MSG_SET_CALLER_DISPLAY_NAME = 6;
- private static final int MSG_SET_CANCELED = 7;
- private static final int MSG_SET_FAILED = 8;
- private static final int MSG_SET_VIDEO_STATE = 9;
- private static final int MSG_SET_ACTIVE = 10;
- private static final int MSG_SET_RINGING = 11;
- private static final int MSG_SET_INITIALIZING = 12;
- private static final int MSG_SET_INITIALIZED = 13;
- private static final int MSG_SET_DIALING = 14;
- private static final int MSG_SET_ON_HOLD = 15;
- private static final int MSG_SET_VIDEO_CALL_PROVIDER = 16;
- private static final int MSG_SET_DISCONNECTED = 17;
- private static final int MSG_SET_POST_DIAL_WAIT = 18;
- private static final int MSG_SET_REQUESTING_RINGBACK = 19;
- private static final int MSG_SET_CALL_CAPABILITIES = 20;
- private static final int MSG_DESTROY = 21;
- private static final int MSG_SET_SIGNAL = 22;
- private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 23;
- private static final int MSG_SET_STATUS_HINTS = 24;
- private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 25;
-
/** @hide */
public abstract static class Listener {
public void onStateChanged(Connection c, int state) {}
@@ -78,6 +49,7 @@
public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
public void onStartActivityFromInCall(Connection c, PendingIntent intent) {}
+ public void onFailed(Connection c, int code, String msg) {}
}
public final class State {
@@ -115,220 +87,6 @@
private String mFailureMessage;
private boolean mIsCanceled;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ADD_CONNECTION_LISTENER: {
- Listener listener = (Listener) msg.obj;
- mListeners.add(listener);
- }
- break;
- case MSG_REMOVE_CONNECTION_LISTENER: {
- Listener listener = (Listener) msg.obj;
- mListeners.remove(listener);
- }
- break;
- case MSG_SET_AUDIO_STATE: {
- CallAudioState state = (CallAudioState) msg.obj;
- mCallAudioState = state;
- onSetAudioState(state);
- }
- break;
- case MSG_SET_PARENT_CONNECTION: {
- Connection parentConnection = (Connection) msg.obj;
- if (mParentConnection != parentConnection) {
- if (mParentConnection != null) {
- mParentConnection.removeChild(Connection.this);
- }
- mParentConnection = parentConnection;
- if (mParentConnection != null) {
- mParentConnection.addChild(Connection.this);
- // do something if the child connections goes down to ZERO.
- }
- for (Listener l : mListeners) {
- l.onParentConnectionChanged(Connection.this, mParentConnection);
- }
- }
- }
- break;
- case MSG_SET_HANDLE: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- Uri handle = (Uri) args.arg1;
- int presentation = args.argi1;
- mHandle = handle;
- mHandlePresentation = presentation;
- for (Listener l : mListeners) {
- l.onHandleChanged(Connection.this, handle, presentation);
- }
- } finally {
- args.recycle();
- }
- }
- break;
- case MSG_SET_CALLER_DISPLAY_NAME: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String callerDisplayName = (String) args.arg1;
- int presentation = args.argi1;
- mCallerDisplayName = callerDisplayName;
- mCallerDisplayNamePresentation = presentation;
- for (Listener l : mListeners) {
- l.onCallerDisplayNameChanged(Connection.this, callerDisplayName,
- presentation);
- }
- } finally {
- args.recycle();
- }
- }
- break;
- case MSG_SET_CANCELED: {
- setState(State.CANCELED);
- }
- break;
- case MSG_SET_FAILED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- int code = args.argi1;
- String message = (String) args.arg1;
- mFailureCode = code;
- mFailureMessage = message;
- setState(State.FAILED);
- } finally {
- args.recycle();
- }
- }
- break;
- case MSG_SET_VIDEO_STATE: {
- int videoState = ((Integer) msg.obj).intValue();
- mVideoState = videoState;
- for (Listener l : mListeners) {
- l.onVideoStateChanged(Connection.this, mVideoState);
- }
- }
- break;
- case MSG_SET_ACTIVE: {
- setRequestingRingback(false);
- setState(State.ACTIVE);
- }
- break;
- case MSG_SET_RINGING: {
- setState(State.RINGING);
- }
- break;
- case MSG_SET_INITIALIZING: {
- setState(State.INITIALIZING);
- }
- break;
- case MSG_SET_INITIALIZED: {
- setState(State.NEW);
- }
- break;
- case MSG_SET_DIALING: {
- setState(State.DIALING);
- }
- break;
- case MSG_SET_ON_HOLD: {
- setState(State.HOLDING);
- }
- break;
- case MSG_SET_VIDEO_CALL_PROVIDER: {
- ConnectionService.VideoCallProvider videoCallProvider =
- (ConnectionService.VideoCallProvider) msg.obj;
- mVideoCallProvider = videoCallProvider;
- for (Listener l : mListeners) {
- l.onVideoCallProviderChanged(Connection.this, videoCallProvider);
- }
- }
- break;
- case MSG_SET_DISCONNECTED: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- int cause = args.argi1;
- String message = (String) args.arg1;
- setState(State.DISCONNECTED);
- Log.d(this, "Disconnected with cause %d message %s", cause, message);
- for (Listener l : mListeners) {
- l.onDisconnected(Connection.this, cause, message);
- }
- } finally {
- args.recycle();
- }
- }
- break;
- case MSG_SET_POST_DIAL_WAIT: {
- String remaining = (String) msg.obj;
- for (Listener l : mListeners) {
- l.onPostDialWait(Connection.this, remaining);
- }
- }
- break;
- case MSG_SET_REQUESTING_RINGBACK: {
- boolean ringback = ((Boolean) msg.obj).booleanValue();
- if (mRequestingRingback != ringback) {
- mRequestingRingback = ringback;
- for (Listener l : mListeners) {
- l.onRequestingRingback(Connection.this, ringback);
- }
- }
- } break;
- case MSG_SET_CALL_CAPABILITIES: {
- int callCapabilities = ((Integer) msg.obj).intValue();
- if (mCallCapabilities != callCapabilities) {
- mCallCapabilities = callCapabilities;
- for (Listener l : mListeners) {
- l.onCallCapabilitiesChanged(Connection.this, mCallCapabilities);
- }
- }
- }
- break;
- case MSG_DESTROY: {
- // TODO: Is this still relevant because everything is on the main thread now.
- // It is possible that onDestroy() will trigger the listener to remove itself
- // which will result in a concurrent modification exception. To counteract
- // this we make a copy of the listeners and iterate on that.
- for (Listener l : new ArrayList<>(mListeners)) {
- if (mListeners.contains(l)) {
- l.onDestroyed(Connection.this);
- }
- }
- }
- break;
- case MSG_SET_SIGNAL: {
- Bundle details = (Bundle) msg.obj;
- for (Listener l : mListeners) {
- l.onSignalChanged(Connection.this, details);
- }
- }
- break;
- case MSG_SET_AUDIO_MODE_IS_VOIP: {
- boolean isVoip = ((Boolean) msg.obj).booleanValue();
- mAudioModeIsVoip = isVoip;
- for (Listener l : mListeners) {
- l.onAudioModeIsVoipChanged(Connection.this, isVoip);
- }
- }
- break;
- case MSG_SET_STATUS_HINTS: {
- StatusHints statusHints = (StatusHints) msg.obj;
- mStatusHints = statusHints;
- for (Listener l : mListeners) {
- l.onStatusHintsChanged(Connection.this, statusHints);
- }
- }
- break;
- case MSG_START_ACTIVITY_FROM_IN_CALL: {
- PendingIntent intent = (PendingIntent) msg.obj;
- for (Listener l : mListeners) {
- l.onStartActivityFromInCall(Connection.this, intent);
- }
- }
- break;
- }
- }
- };
-
/**
* Create a new Connection.
*/
@@ -430,7 +188,7 @@
* @hide
*/
public final Connection addConnectionListener(Listener l) {
- mHandler.obtainMessage(MSG_ADD_CONNECTION_LISTENER, l).sendToTarget();
+ mListeners.add(l);
return this;
}
@@ -443,7 +201,7 @@
* @hide
*/
public final Connection removeConnectionListener(Listener l) {
- mHandler.obtainMessage(MSG_REMOVE_CONNECTION_LISTENER, l).sendToTarget();
+ mListeners.remove(l);
return this;
}
@@ -469,7 +227,8 @@
*/
final void setAudioState(CallAudioState state) {
Log.d(this, "setAudioState %s", state);
- mHandler.obtainMessage(MSG_SET_AUDIO_STATE, state).sendToTarget();
+ mCallAudioState = state;
+ onSetAudioState(state);
}
/**
@@ -507,7 +266,19 @@
*/
public final void setParentConnection(Connection parentConnection) {
Log.d(this, "parenting %s to %s", this, parentConnection);
- mHandler.obtainMessage(MSG_SET_PARENT_CONNECTION, parentConnection).sendToTarget();
+ if (mParentConnection != parentConnection) {
+ if (mParentConnection != null) {
+ mParentConnection.removeChild(this);
+ }
+ mParentConnection = parentConnection;
+ if (mParentConnection != null) {
+ mParentConnection.addChild(this);
+ // do something if the child connections goes down to ZERO.
+ }
+ for (Listener l : mListeners) {
+ l.onParentConnectionChanged(this, mParentConnection);
+ }
+ }
}
public final Connection getParentConnection() {
@@ -534,10 +305,11 @@
*/
public final void setHandle(Uri handle, int presentation) {
Log.d(this, "setHandle %s", handle);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = handle;
- args.argi1 = presentation;
- mHandler.obtainMessage(MSG_SET_HANDLE, args).sendToTarget();
+ mHandle = handle;
+ mHandlePresentation = presentation;
+ for (Listener l : mListeners) {
+ l.onHandleChanged(this, handle, presentation);
+ }
}
/**
@@ -549,10 +321,11 @@
*/
public final void setCallerDisplayName(String callerDisplayName, int presentation) {
Log.d(this, "setCallerDisplayName %s", callerDisplayName);
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = callerDisplayName;
- args.argi1 = presentation;
- mHandler.obtainMessage(MSG_SET_CALLER_DISPLAY_NAME, args).sendToTarget();
+ mCallerDisplayName = callerDisplayName;
+ mCallerDisplayNamePresentation = presentation;
+ for (Listener l : mListeners) {
+ l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+ }
}
/**
@@ -561,7 +334,7 @@
*/
public final void setCanceled() {
Log.d(this, "setCanceled");
- mHandler.obtainMessage(MSG_SET_CANCELED).sendToTarget();
+ setState(State.CANCELED);
}
/**
@@ -577,10 +350,9 @@
*/
public final void setFailed(int code, String message) {
Log.d(this, "setFailed (%d: %s)", code, message);
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = code;
- args.arg1 = message;
- mHandler.obtainMessage(MSG_SET_FAILED, args).sendToTarget();
+ mFailureCode = code;
+ mFailureMessage = message;
+ setState(State.FAILED);
}
/**
@@ -594,7 +366,10 @@
*/
public final void setVideoState(int videoState) {
Log.d(this, "setVideoState %d", videoState);
- mHandler.obtainMessage(MSG_SET_VIDEO_STATE, Integer.valueOf(videoState)).sendToTarget();
+ mVideoState = videoState;
+ for (Listener l : mListeners) {
+ l.onVideoStateChanged(this, mVideoState);
+ }
}
/**
@@ -602,28 +377,28 @@
* communicate).
*/
public final void setActive() {
- mHandler.obtainMessage(MSG_SET_ACTIVE).sendToTarget();
+ setRequestingRingback(false);
+ setState(State.ACTIVE);
}
/**
* Sets state to ringing (e.g., an inbound ringing call).
*/
public final void setRinging() {
- mHandler.obtainMessage(MSG_SET_RINGING).sendToTarget();
+ setState(State.RINGING);
}
/**
* Sets state to initializing (this Connection is not yet ready to be used).
*/
public final void setInitializing() {
- mHandler.obtainMessage(MSG_SET_INITIALIZING).sendToTarget();
+ setState(State.INITIALIZING);
}
/**
* Sets state to initialized (the Connection has been set up and is now ready to be used).
*/
public final void setInitialized() {
- mHandler.obtainMessage(MSG_SET_INITIALIZED).sendToTarget();
setState(State.NEW);
}
@@ -631,14 +406,14 @@
* Sets state to dialing (e.g., dialing an outbound call).
*/
public final void setDialing() {
- mHandler.obtainMessage(MSG_SET_DIALING).sendToTarget();
+ setState(State.DIALING);
}
/**
* Sets state to be on hold.
*/
public final void setOnHold() {
- mHandler.obtainMessage(MSG_SET_ON_HOLD).sendToTarget();
+ setState(State.HOLDING);
}
/**
@@ -646,7 +421,10 @@
* @param videoCallProvider The video call provider.
*/
public final void setVideoCallProvider(ConnectionService.VideoCallProvider videoCallProvider) {
- mHandler.obtainMessage(MSG_SET_VIDEO_CALL_PROVIDER, videoCallProvider).sendToTarget();
+ mVideoCallProvider = videoCallProvider;
+ for (Listener l : mListeners) {
+ l.onVideoCallProviderChanged(this, videoCallProvider);
+ }
}
public final ConnectionService.VideoCallProvider getVideoCallProvider() {
@@ -661,17 +439,20 @@
* @param message Optional call-service-provided message about the disconnect.
*/
public final void setDisconnected(int cause, String message) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = cause;
- args.arg1 = message;
- mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();
+ setState(State.DISCONNECTED);
+ Log.d(this, "Disconnected with cause %d message %s", cause, message);
+ for (Listener l : mListeners) {
+ l.onDisconnected(this, cause, message);
+ }
}
/**
* TODO(santoscordon): Needs documentation.
*/
public final void setPostDialWait(String remaining) {
- mHandler.obtainMessage(MSG_SET_POST_DIAL_WAIT, remaining).sendToTarget();
+ for (Listener l : mListeners) {
+ l.onPostDialWait(this, remaining);
+ }
}
/**
@@ -681,8 +462,12 @@
* @param ringback Whether the ringback tone is to be played.
*/
public final void setRequestingRingback(boolean ringback) {
- mHandler.obtainMessage(MSG_SET_REQUESTING_RINGBACK, Boolean.valueOf(ringback))
- .sendToTarget();
+ if (mRequestingRingback != ringback) {
+ mRequestingRingback = ringback;
+ for (Listener l : mListeners) {
+ l.onRequestingRingback(this, ringback);
+ }
+ }
}
/**
@@ -691,15 +476,26 @@
* @param callCapabilities The new call capabilities.
*/
public final void setCallCapabilities(int callCapabilities) {
- mHandler.obtainMessage(MSG_SET_CALL_CAPABILITIES, Integer.valueOf(callCapabilities))
- .sendToTarget();
+ if (mCallCapabilities != callCapabilities) {
+ mCallCapabilities = callCapabilities;
+ for (Listener l : mListeners) {
+ l.onCallCapabilitiesChanged(this, mCallCapabilities);
+ }
+ }
}
/**
* TODO(santoscordon): Needs documentation.
*/
public final void destroy() {
- mHandler.obtainMessage(MSG_DESTROY).sendToTarget();
+ // It is possible that onDestroy() will trigger the listener to remove itself which will
+ // result in a concurrent modification exception. To counteract this we make a copy of the
+ // listeners and iterate on that.
+ for (Listener l : new ArrayList<>(mListeners)) {
+ if (mListeners.contains(l)) {
+ l.onDestroyed(this);
+ }
+ }
}
/**
@@ -708,7 +504,9 @@
* @param details A {@link android.os.Bundle} containing details of the current level.
*/
public final void setSignal(Bundle details) {
- mHandler.obtainMessage(MSG_SET_SIGNAL, details).sendToTarget();
+ for (Listener l : mListeners) {
+ l.onSignalChanged(this, details);
+ }
}
/**
@@ -717,7 +515,10 @@
* @param isVoip True if the audio mode is VOIP.
*/
public final void setAudioModeIsVoip(boolean isVoip) {
- mHandler.obtainMessage(MSG_SET_AUDIO_MODE_IS_VOIP, Boolean.valueOf(isVoip)).sendToTarget();
+ mAudioModeIsVoip = isVoip;
+ for (Listener l : mListeners) {
+ l.onAudioModeIsVoipChanged(this, isVoip);
+ }
}
/**
@@ -726,7 +527,10 @@
* @param statusHints The status label and icon to set.
*/
public final void setStatusHints(StatusHints statusHints) {
- mHandler.obtainMessage(MSG_SET_STATUS_HINTS, statusHints).sendToTarget();
+ mStatusHints = statusHints;
+ for (Listener l : mListeners) {
+ l.onStatusHintsChanged(this, statusHints);
+ }
}
/**
@@ -738,13 +542,13 @@
if (!intent.isActivity()) {
throw new IllegalArgumentException("Activity intent required.");
}
- mHandler.obtainMessage(MSG_START_ACTIVITY_FROM_IN_CALL, intent).sendToTarget();
+ for (Listener l : mListeners) {
+ l.onStartActivityFromInCall(this, intent);
+ }
}
/**
* Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
- * <p>
- * This callback will happen on the main thread.
*
* @param state The new call audio state.
*/
@@ -753,8 +557,6 @@
/**
* Notifies this Connection of an internal state change. This method is called after the
* state is changed.
- * <p>
- * This callback will happen on the main thread.
*
* @param state The new state, a {@link Connection.State} member.
*/
@@ -762,8 +564,6 @@
/**
* Notifies this Connection of a request to play a DTMF tone.
- * <p>
- * This callback will happen on the main thread.
*
* @param c A DTMF character.
*/
@@ -771,81 +571,61 @@
/**
* Notifies this Connection of a request to stop any currently playing DTMF tones.
- * <p>
- * This callback will happen on the main thread.
*/
public void onStopDtmfTone() {}
/**
* Notifies this Connection of a request to disconnect.
- * <p>
- * This callback will happen on the main thread.
*/
public void onDisconnect() {}
/**
* Notifies this Connection of a request to disconnect.
- * <p>
- * This callback will happen on the main thread.
*/
public void onSeparate() {}
/**
* Notifies this Connection of a request to abort.
- * <p>
- * This callback will happen on the main thread.
*/
public void onAbort() {}
/**
* Notifies this Connection of a request to hold.
- * <p>
- * This callback will happen on the main thread.
*/
public void onHold() {}
/**
* Notifies this Connection of a request to exit a hold state.
- * <p>
- * This callback will happen on the main thread.
*/
public void onUnhold() {}
/**
- * Notifies this Connection, which is in {@link State#RINGING}, of a request to accept.
- * <p>
- * This callback will happen on the main thread.
+ * Notifies this Connection, which is in {@link State#RINGING}, of
+ * a request to accept.
*
* @param videoState The video state in which to answer the call.
*/
public void onAnswer(int videoState) {}
/**
- * Notifies this Connection, which is in {@link State#RINGING}, of a request to reject.
- * <p>
- * This callback will happen on the main thread.
+ * Notifies this Connection, which is in {@link State#RINGING}, of
+ * a request to reject.
*/
public void onReject() {}
/**
* Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
- * <p>
- * This callback will happen on the main thread.
*/
public void onPostDialContinue(boolean proceed) {}
/**
* Swap this call with a background call. This is used for calls that don't support hold,
* e.g. CDMA.
- * <p>
- * This callback will happen on the main thread.
*/
public void onSwapWithBackgroundCall() {}
/**
* TODO(santoscordon): Needs documentation.
- * <p>
- * This callback will happen on the main thread.
*/
public void onChildrenChanged(List<Connection> children) {}
@@ -854,14 +634,12 @@
*/
public void onPhoneAccountClicked() {}
- /** This must be called from the main thread. */
private void addChild(Connection connection) {
Log.d(this, "adding child %s", connection);
mChildConnections.add(connection);
onChildrenChanged(mChildConnections);
}
- /** This must be called from the main thread. */
private void removeChild(Connection connection) {
Log.d(this, "removing child %s", connection);
mChildConnections.remove(connection);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0772687..c50110a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2401,7 +2401,7 @@
* is sent to the SIM.
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
- * the end. If an error occurs, an empty string is returned.
+ * the end.
*/
public String iccTransmitApduLogicalChannel(int channel, int cla,
int instruction, int p1, int p2, int p3, String data) {
@@ -2431,7 +2431,7 @@
* is sent to the SIM.
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
- * the end. If an error occurs, an empty string is returned.
+ * the end.
*/
public String iccTransmitApduBasicChannel(int cla,
int instruction, int p1, int p2, int p3, String data) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 72b04cf..d256f9d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -530,7 +530,7 @@
* is sent to the SIM.
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
- * the end. If an error occurs, an empty string is returned.
+ * the end.
*/
String iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
int p1, int p2, int p3, String data);
@@ -548,7 +548,7 @@
* is sent to the SIM.
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
- * the end. If an error occurs, an empty string is returned.
+ * the end.
*/
String iccTransmitApduBasicChannel(int cla, int instruction,
int p1, int p2, int p3, String data);
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 8a2732d..46c81b6 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -572,6 +572,13 @@
/** {@hide} */
@Override
+ public Context createApplicationContext(ApplicationInfo application, int flags)
+ throws PackageManager.NameNotFoundException {
+ return null;
+ }
+
+ /** {@hide} */
+ @Override
public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
throws PackageManager.NameNotFoundException {
throw new UnsupportedOperationException();
@@ -595,7 +602,7 @@
@Override
public boolean isRestricted() {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException();
}
/** @hide */
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 3686899..c0799fc 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -193,8 +193,7 @@
if (metadata == null) {
return;
}
- Log.d(TAG, "Received metadata change, title is "
- + metadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ Log.d(TAG, "Received metadata change, " + metadata.getDescription());
}
}
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
index 4372ff9..65a3d8a 100644
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
+++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
@@ -23,6 +23,7 @@
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.media.AudioFormat;
import android.os.Parcel;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -97,7 +98,8 @@
Keyphrase[] keyphrases = new Keyphrase[2];
keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
- KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), null, keyphrases);
+ KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
+ null, keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -119,8 +121,8 @@
Keyphrase[] keyphrases = new Keyphrase[2];
keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
- KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), new byte[0],
- keyphrases);
+ KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
+ new byte[0], keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -141,7 +143,8 @@
public void testKeyphraseSoundModelParcelUnparcel_noKeyphrases() throws Exception {
byte[] data = new byte[10];
mRandom.nextBytes(data);
- KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, null);
+ KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
+ data, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -162,8 +165,8 @@
public void testKeyphraseSoundModelParcelUnparcel_zeroKeyphrases() throws Exception {
byte[] data = new byte[10];
mRandom.nextBytes(data);
- KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data,
- new Keyphrase[0]);
+ KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
+ data, new Keyphrase[0]);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -187,7 +190,8 @@
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
byte[] data = new byte[200 * 1024];
mRandom.nextBytes(data);
- KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, keyphrases);
+ KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
+ data, keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -207,7 +211,7 @@
@SmallTest
public void testRecognitionEventParcelUnparcel_noData() throws Exception {
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1,
- true, 2, 3, 4, null);
+ true, 2, 3, 4, false, null, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -224,7 +228,7 @@
@SmallTest
public void testRecognitionEventParcelUnparcel_zeroData() throws Exception {
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_FAILURE, 1,
- true, 2, 3, 4, new byte[1]);
+ true, 2, 3, 4, false, null, new byte[1]);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -243,7 +247,32 @@
byte[] data = new byte[200 * 1024];
mRandom.nextBytes(data);
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1,
- false, 2, 3, 4, data);
+ false, 2, 3, 4, false, null, data);
+
+ // Write to a parcel
+ Parcel parcel = Parcel.obtain();
+ re.writeToParcel(parcel, 0);
+
+ // Read from it
+ parcel.setDataPosition(0);
+ RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel);
+
+ // Verify that they are the same
+ assertEquals(re, unparceled);
+ }
+
+ @SmallTest
+ public void testRecognitionEventParcelUnparcel_largeAudioData() throws Exception {
+ byte[] data = new byte[200 * 1024];
+ mRandom.nextBytes(data);
+ RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1,
+ false, 2, 3, 4, true,
+ (new AudioFormat.Builder())
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(16000)
+ .build(),
+ data);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -260,7 +289,7 @@
@SmallTest
public void testKeyphraseRecognitionEventParcelUnparcel_noKeyphrases() throws Exception {
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
- SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, true, 2, 3, 4, null, false, null);
+ SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, true, 2, 3, 4, false, null, null, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -279,8 +308,8 @@
public void testKeyphraseRecognitionEventParcelUnparcel_zeroData() throws Exception {
KeyphraseRecognitionExtra[] kpExtra = new KeyphraseRecognitionExtra[0];
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
- SoundTrigger.RECOGNITION_STATUS_FAILURE, 2, true, 2, 3, 4, new byte[1],
- true, kpExtra);
+ SoundTrigger.RECOGNITION_STATUS_FAILURE, 2, true, 2, 3, 4, false, null, new byte[1],
+ kpExtra);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@@ -303,20 +332,20 @@
ConfidenceLevel cl1 = new ConfidenceLevel(1, 90);
ConfidenceLevel cl2 = new ConfidenceLevel(2, 30);
kpExtra[0] = new KeyphraseRecognitionExtra(1,
- SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION,
+ SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION, 0,
new ConfidenceLevel[] {cl1, cl2});
kpExtra[1] = new KeyphraseRecognitionExtra(1,
- SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER,
+ SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0,
new ConfidenceLevel[] {cl2});
kpExtra[2] = new KeyphraseRecognitionExtra(1,
- SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, null);
+ SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0, null);
kpExtra[3] = new KeyphraseRecognitionExtra(1,
- SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER,
+ SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0,
new ConfidenceLevel[0]);
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
- SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, true, 2, 3, 4, data,
- false, kpExtra);
+ SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, true, 2, 3, 4, false, null, data,
+ kpExtra);
// Write to a parcel
Parcel parcel = Parcel.obtain();
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
index 88ebd1f..154851b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -16,6 +16,9 @@
package android.graphics;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Toolkit;
@@ -58,6 +61,8 @@
private final Graphics2D mGraphics;
private final Paint_Delegate mPaint;
private char[] mText;
+ // This List can contain nulls. A null font implies that the we weren't able to load the font
+ // properly. So, if we encounter a situation where we try to use that font, log a warning.
private List<Font> mFonts;
// Bounds of the text drawn so far.
private RectF mBounds;
@@ -169,6 +174,10 @@
// fonts to check which one can draw it.
int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
for (Font font : mFonts) {
+ if (font == null) {
+ logFontWarning();
+ continue;
+ }
canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount);
if (canDisplayUpTo == -1) {
render(start, start+charCount, font, flag, advances, advancesIndex, draw);
@@ -191,6 +200,12 @@
}
}
+ private static void logFontWarning() {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+ "Some fonts could not be loaded. The rendering may not be perfect. " +
+ "Try running the IDE with JRE 7.", null, null);
+ }
+
/**
* Renders the text to the right of the bounds with the given font.
* @param font The font to render the text with.
@@ -266,6 +281,10 @@
private static void setScriptFont(char[] text, ScriptRun run,
List<Font> fonts) {
for (Font font : fonts) {
+ if (font == null) {
+ logFontWarning();
+ continue;
+ }
if (font.canDisplayUpTo(text, run.start, run.limit) == -1) {
run.font = font;
return;
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index 30b0ce5..de3307f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -24,6 +24,7 @@
import android.content.res.AssetManager;
import java.awt.Font;
+import java.awt.FontFormatException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -152,14 +153,20 @@
try {
return Font.createFont(Font.TRUETYPE_FONT, f);
} catch (Exception e) {
+ if (path.endsWith(".otf") && e instanceof FontFormatException) {
+ // If we aren't able to load an Open Type font, don't log a warning just yet.
+ // We wait for a case where font is being used. Only then we try to log the
+ // warning.
+ return null;
+ }
Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
String.format("Unable to load font %1$s", relativePath),
- e /*throwable*/, null /*data*/);
+ e, null);
}
} else {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.",
- null /*throwable*/, null /*data*/);
+ null, null);
}
return null;
@@ -206,7 +213,7 @@
@LayoutlibDelegate
/*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "FontFamily.addFontFromAsset is not supported.", null /*throwable*/, null /*data*/);
+ "FontFamily.addFontFromAsset is not supported.", null, null);
return false;
}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
index 1e7564e..af0c456 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
@@ -48,4 +48,58 @@
return def;
}
+ @LayoutlibDelegate
+ /*package*/ static int native_get_int(String key, int def) {
+ Map<String, String> properties = Bridge.getPlatformProperties();
+ String value = properties.get(key);
+ if (value != null) {
+ return Integer.decode(value);
+ }
+
+ return def;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long native_get_long(String key, long def) {
+ Map<String, String> properties = Bridge.getPlatformProperties();
+ String value = properties.get(key);
+ if (value != null) {
+ return Long.decode(value);
+ }
+
+ return def;
+ }
+
+ /**
+ * Values 'n', 'no', '0', 'false' or 'off' are considered false.
+ * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
+ */
+ @LayoutlibDelegate
+ /*package*/ static boolean native_get_boolean(String key, boolean def) {
+ Map<String, String> properties = Bridge.getPlatformProperties();
+ String value = properties.get(key);
+
+ if ("n".equals(value) || "no".equals(value) || "0".equals(value) || "false".equals(value)
+ || "off".equals(value)) {
+ return false;
+ }
+ //noinspection SimplifiableIfStatement
+ if ("y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value)
+ || "on".equals(value)) {
+ return true;
+ }
+
+ return def;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_set(String key, String def) {
+ Map<String, String> properties = Bridge.getPlatformProperties();
+ properties.put(key, def);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void native_add_change_callback() {
+ // pass.
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
new file mode 100644
index 0000000..5a467b2
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java
@@ -0,0 +1,55 @@
+package android.text;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.text.CharacterIterator;
+import java.util.Arrays;
+import java.util.Locale;
+
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.text.BreakIterator;
+import com.ibm.icu.util.ULocale;
+import javax.swing.text.Segment;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class StaticLayout_Delegate {
+
+ /**
+ * Fills the recycle array with positions that are suitable to break the text at. The array
+ * must be terminated by '-1'.
+ */
+ @LayoutlibDelegate
+ /*package*/ static int[] nLineBreakOpportunities(String locale, char[] text, int length,
+ int[] recycle) {
+ BreakIterator iterator = BreakIterator.getLineInstance(new ULocale(locale));
+ Segment segment = new Segment(text, 0, length);
+ iterator.setText(segment);
+ if (recycle == null) {
+ // Because 42 is the answer to everything.
+ recycle = new int[42];
+ }
+ int breakOpp = iterator.first();
+ recycle[0] = breakOpp;
+ //noinspection ConstantConditions
+ assert BreakIterator.DONE == -1;
+ for (int i = 1; breakOpp != BreakIterator.DONE; ++i) {
+ if (i >= recycle.length) {
+ recycle = doubleSize(recycle);
+ }
+ assert (i < recycle.length);
+ breakOpp = iterator.next();
+ recycle[i] = breakOpp;
+ }
+ return recycle;
+ }
+
+ private static int[] doubleSize(int[] array) {
+ return Arrays.copyOf(array, array.length * 2);
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 03b5211..04a52ea 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -967,6 +967,12 @@
}
@Override
+ public Context createApplicationContext(ApplicationInfo application, int flags)
+ throws PackageManager.NameNotFoundException {
+ return null;
+ }
+
+ @Override
public boolean deleteDatabase(String arg0) {
// pass
return false;
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 4af07dd..31b3e25 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -116,17 +116,16 @@
}
File[] possibleSdks = sdkDir.listFiles(new FileFilter() {
@Override
- public boolean accept(File pathname) {
- return pathname.isDirectory() && pathname.getAbsolutePath().contains("android-sdk");
+ public boolean accept(File path) {
+ return path.isDirectory() && path.getAbsolutePath().contains("android-sdk");
}
});
for (File possibleSdk : possibleSdks) {
File platformsDir = new File(possibleSdk, "platforms");
File[] platforms = platformsDir.listFiles(new FileFilter() {
@Override
- public boolean accept(File pathname) {
- return pathname.isDirectory()
- && pathname.toPath().getFileName().toString().startsWith("android-");
+ public boolean accept(File path) {
+ return path.isDirectory() && path.getName().startsWith("android-");
}
});
if (platforms == null || platforms.length == 0) {
@@ -137,10 +136,8 @@
@Override
public int compare(File o1, File o2) {
final int MAX_VALUE = 1000;
- String suffix1 = o1.toPath().getFileName().toString()
- .substring("android-".length());
- String suffix2 = o2.toPath().getFileName().toString()
- .substring("android-".length());
+ String suffix1 = o1.getName().substring("android-".length());
+ String suffix2 = o2.getName().substring("android-".length());
int suff1, suff2;
try {
suff1 = Integer.parseInt(suffix1);
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 8fb8928..89cbaeb 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -150,7 +150,6 @@
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
"com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
- "android.os.SystemProperties#native_get",
"dalvik.system.VMRuntime#newUnpaddedArray"
};
@@ -198,7 +197,9 @@
"android.graphics.Typeface",
"android.graphics.Xfermode",
"android.os.SystemClock",
+ "android.os.SystemProperties",
"android.text.AndroidBidi",
+ "android.text.StaticLayout",
"android.text.format.Time",
"android.util.FloatMath",
"android.view.Display",
diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk
index 99a5671..9ff56d6 100644
--- a/tools/obbtool/Android.mk
+++ b/tools/obbtool/Android.mk
@@ -13,7 +13,7 @@
LOCAL_SRC_FILES := \
Main.cpp
-LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
+LOCAL_CFLAGS := -Wall -Werror
#LOCAL_C_INCLUDES +=
@@ -36,7 +36,7 @@
LOCAL_MODULE := pbkdf2gen
LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags
+LOCAL_CFLAGS := -Wall -Werror
LOCAL_SRC_FILES := pbkdf2gen.cpp
LOCAL_LDLIBS += -ldl
LOCAL_C_INCLUDES := external/openssl/include $(LOCAL_C_INCLUDES)