diff options
Diffstat (limited to 'core')
1001 files changed, 18192 insertions, 11632 deletions
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 3c3df01..d4c4318 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ActionMode; import android.view.Gravity; +import android.view.KeyEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -1013,6 +1014,26 @@ public abstract class ActionBar { return null; } + /** @hide */ + public boolean openOptionsMenu() { + return false; + } + + /** @hide */ + public boolean invalidateOptionsMenu() { + return false; + } + + /** @hide */ + public boolean onMenuKeyEvent(KeyEvent event) { + return false; + } + + /** @hide */ + public boolean collapseActionView() { + return false; + } + /** * Listener interface for ActionBar navigation events. * @@ -1291,6 +1312,7 @@ public abstract class ActionBar { public LayoutParams(int width, int height) { super(width, height); + this.gravity = Gravity.CENTER_VERTICAL | Gravity.START; } public LayoutParams(int width, int height, int gravity) { @@ -1305,6 +1327,7 @@ public abstract class ActionBar { public LayoutParams(LayoutParams source) { super(source); + this.gravity = source.gravity; } public LayoutParams(ViewGroup.LayoutParams source) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 07de85c..23b5f29 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -716,6 +716,7 @@ public class Activity extends ContextThemeWrapper HashMap<String, Object> children; ArrayList<Fragment> fragments; ArrayMap<String, LoaderManagerImpl> loaders; + VoiceInteractor voiceInteractor; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -920,6 +921,9 @@ public class Activity extends ContextThemeWrapper } mFragments.dispatchCreate(); getApplication().dispatchActivityCreated(this, savedInstanceState); + if (mVoiceInteractor != null) { + mVoiceInteractor.attachActivity(this); + } mCalled = true; } @@ -1830,7 +1834,8 @@ public class Activity extends ContextThemeWrapper } } } - if (activity == null && children == null && fragments == null && !retainLoaders) { + if (activity == null && children == null && fragments == null && !retainLoaders + && mVoiceInteractor == null) { return null; } @@ -1839,6 +1844,7 @@ public class Activity extends ContextThemeWrapper nci.children = children; nci.fragments = fragments; nci.loaders = mAllLoaderManagers; + nci.voiceInteractor = mVoiceInteractor; return nci; } @@ -2069,15 +2075,16 @@ public class Activity extends ContextThemeWrapper * <p>In order to use a Toolbar within the Activity's window content the application * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p> * - * @param actionBar Toolbar to set as the Activity's action bar + * @param toolbar Toolbar to set as the Activity's action bar */ - public void setActionBar(@Nullable Toolbar actionBar) { + public void setActionBar(@Nullable Toolbar toolbar) { if (getActionBar() instanceof WindowDecorActionBar) { throw new IllegalStateException("This Activity already has an action bar supplied " + "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + "android:windowActionBar to false in your theme to use a Toolbar instead."); } - mActionBar = new ToolbarActionBar(actionBar); + mActionBar = new ToolbarActionBar(toolbar, getTitle(), this); + mActionBar.invalidateOptionsMenu(); } /** @@ -2443,6 +2450,10 @@ public class Activity extends ContextThemeWrapper * but you can override this to do whatever you want. */ public void onBackPressed() { + if (mActionBar != null && mActionBar.collapseActionView()) { + return; + } + if (!mFragments.popBackStackImmediate()) { finishAfterTransition(); } @@ -2654,6 +2665,14 @@ public class Activity extends ContextThemeWrapper */ public boolean dispatchKeyEvent(KeyEvent event) { onUserInteraction(); + + // Let action bars open menus in response to the menu key prioritized over + // the window handling it + if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && + mActionBar != null && mActionBar.onMenuKeyEvent(event)) { + return true; + } + Window win = getWindow(); if (win.superDispatchKeyEvent(event)) { return true; @@ -2901,7 +2920,9 @@ public class Activity extends ContextThemeWrapper * time it needs to be displayed. */ public void invalidateOptionsMenu() { - mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) { + mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } } /** @@ -3111,7 +3132,9 @@ public class Activity extends ContextThemeWrapper * open, this method does nothing. */ public void openOptionsMenu() { - mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + if (mActionBar == null || !mActionBar.openOptionsMenu()) { + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + } } /** @@ -4194,7 +4217,11 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode) { - startActivityFromFragment(fragment, intent, requestCode, null); + Bundle options = null; + if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) { + options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle(); + } + startActivityFromFragment(fragment, intent, requestCode, options); } /** @@ -4219,6 +4246,9 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) { + if (options != null) { + mActivityTransitionState.startExitOutTransition(this, options); + } Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, fragment, @@ -5625,8 +5655,14 @@ public class Activity extends ContextThemeWrapper mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; - mVoiceInteractor = voiceInteractor != null - ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null; + if (voiceInteractor != null) { + if (lastNonConfigurationInstances != null) { + mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; + } else { + mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, + Looper.myLooper()); + } + } mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), @@ -5835,6 +5871,9 @@ public class Activity extends ContextThemeWrapper if (mLoaderManager != null) { mLoaderManager.doDestroy(); } + if (mVoiceInteractor != null) { + mVoiceInteractor.detachActivity(); + } } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index abcb0d0..788ac56 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -738,7 +738,7 @@ public class ActivityManager { public static final int RECENT_INCLUDE_PROFILES = 0x0004; /** - * Return a list of the tasks that the user has recently launched, with + * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * * <p><b>Note: this method is only intended for debugging and presenting @@ -750,6 +750,15 @@ public class ActivityManager { * same time, assumptions made about the meaning of the data here for * purposes of control flow will be incorrect.</p> * + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method is + * no longer available to third party applications: as the introduction of + * document-centric recents means + * it can leak personal information to the caller. For backwards compatibility, + * it will still return a small subset of its data: at least the caller's + * own tasks (though see {@link #getAppTasks()} for the correct supported + * way to retrieve that information), and possibly some other tasks + * such as home that are known to not be sensitive. + * * @param maxNum The maximum number of entries to return in the list. The * actual number returned may be smaller, depending on how many tasks the * user has started and the maximum number the system can remember. @@ -762,6 +771,7 @@ public class ActivityManager { * @throws SecurityException Throws SecurityException if the caller does * not hold the {@link android.Manifest.permission#GET_TASKS} permission. */ + @Deprecated public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException { try { @@ -946,6 +956,14 @@ public class ActivityManager { * same time, assumptions made about the meaning of the data here for * purposes of control flow will be incorrect.</p> * + * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method + * is no longer available to third party + * applications: the introduction of document-centric recents means + * it can leak person information to the caller. For backwards compatibility, + * it will still retu rn a small subset of its data: at least the caller's + * own tasks, and possibly some other tasks + * such as home that are known to not be sensitive. + * * @param maxNum The maximum number of entries to return in the list. The * actual number returned may be smaller, depending on how many tasks the * user has started. @@ -956,6 +974,7 @@ public class ActivityManager { * @throws SecurityException Throws SecurityException if the caller does * not hold the {@link android.Manifest.permission#GET_TASKS} permission. */ + @Deprecated public List<RunningTaskInfo> getRunningTasks(int maxNum) throws SecurityException { try { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 0f65454..56462ae 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -943,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM b = data.readStrongBinder(); IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b); int userId = data.readInt(); - boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId); + String abiOverride = data.readString(); + boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId, + abiOverride); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; @@ -3339,7 +3341,8 @@ class ActivityManagerProxy implements IActivityManager public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException { + IUiAutomationConnection connection, int userId, String instructionSet) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3350,6 +3353,7 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); data.writeStrongBinder(connection != null ? connection.asBinder() : null); data.writeInt(userId); + data.writeString(instructionSet); mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index d08978b..b739387 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -15,14 +15,23 @@ */ package android.app; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.transition.Transition; import android.transition.TransitionSet; import android.util.ArrayMap; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.Window; import android.widget.ImageView; @@ -120,8 +129,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; - // The background fade in/out duration. 150ms is pretty quick, but not abrupt. - public static final int FADE_BACKGROUND_DURATION_MS = 150; + // The background fade in/out duration. TODO: Enable tuning this. + public static final int FADE_BACKGROUND_DURATION_MS = 300; protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); @@ -181,6 +190,16 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { */ public static final int MSG_CANCEL = 106; + /** + * When returning, this is the destination location for the shared element. + */ + public static final int MSG_SHARED_ELEMENT_DESTINATION = 107; + + /** + * Send the shared element positions. + */ + public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108; + final private Window mWindow; final protected ArrayList<String> mAllSharedElementNames; final protected ArrayList<View> mSharedElements = new ArrayList<View>(); @@ -334,6 +353,210 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected abstract Transition getViewsTransition(); + private static void setSharedElementState(View view, String name, Bundle transitionArgs, + int[] parentLoc) { + Bundle sharedElementBundle = transitionArgs.getBundle(name); + if (sharedElementBundle == null) { + return; + } + + if (view instanceof ImageView) { + int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt >= 0) { + ImageView imageView = (ImageView) view; + ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; + imageView.setScaleType(scaleType); + if (scaleType == ImageView.ScaleType.MATRIX) { + float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); + Matrix matrix = new Matrix(); + matrix.setValues(matrixValues); + imageView.setImageMatrix(matrix); + } + } + } + + float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); + view.setTranslationZ(z); + + int x = sharedElementBundle.getInt(KEY_SCREEN_X); + int y = sharedElementBundle.getInt(KEY_SCREEN_Y); + int width = sharedElementBundle.getInt(KEY_WIDTH); + int height = sharedElementBundle.getInt(KEY_HEIGHT); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + view.measure(widthSpec, heightSpec); + + int left = x - parentLoc[0]; + int top = y - parentLoc[1]; + int right = left + width; + int bottom = top + height; + view.layout(left, top, right, bottom); + } + + protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( + Bundle sharedElementState, final ArrayList<View> snapshots) { + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = + new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); + if (sharedElementState != null) { + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, + name, sharedElementState); + if (originalState != null) { + originalImageState.put((ImageView) sharedElement, originalState); + } + View parent = (View) sharedElement.getParent(); + parent.getLocationOnScreen(tempLoc); + setSharedElementState(sharedElement, name, sharedElementState, tempLoc); + } + } + mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); + + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, + snapshots); + return true; + } + } + ); + return originalImageState; + } + + private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, + Bundle transitionArgs) { + if (!(view instanceof ImageView)) { + return null; + } + Bundle bundle = transitionArgs.getBundle(name); + if (bundle == null) { + return null; + } + int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); + if (scaleTypeInt < 0) { + return null; + } + + ImageView imageView = (ImageView) view; + ImageView.ScaleType originalScaleType = imageView.getScaleType(); + + Matrix originalMatrix = null; + if (originalScaleType == ImageView.ScaleType.MATRIX) { + originalMatrix = new Matrix(imageView.getImageMatrix()); + } + + return Pair.create(originalScaleType, originalMatrix); + } + + protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { + int numSharedElements = names.size(); + if (numSharedElements == 0) { + return null; + } + ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); + Context context = getWindow().getContext(); + int[] parentLoc = new int[2]; + getDecor().getLocationOnScreen(parentLoc); + for (String name: names) { + Bundle sharedElementBundle = state.getBundle(name); + if (sharedElementBundle != null) { + Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); + View snapshot = new View(context); + Resources resources = getWindow().getContext().getResources(); + if (bitmap != null) { + snapshot.setBackground(new BitmapDrawable(resources, bitmap)); + } + snapshot.setViewName(name); + setSharedElementState(snapshot, name, state, parentLoc); + snapshots.add(snapshot); + } + } + return snapshots; + } + + protected static void setOriginalImageViewState( + ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { + for (int i = 0; i < originalState.size(); i++) { + ImageView imageView = originalState.keyAt(i); + Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); + imageView.setScaleType(state.first); + imageView.setImageMatrix(state.second); + } + } + + protected Bundle captureSharedElementState() { + Bundle bundle = new Bundle(); + int[] tempLoc = new int[2]; + for (int i = 0; i < mSharedElementNames.size(); i++) { + View sharedElement = mSharedElements.get(i); + String name = mSharedElementNames.get(i); + captureSharedElementState(sharedElement, name, bundle, tempLoc); + } + return bundle; + } + + /** + * Captures placement information for Views with a shared element name for + * Activity Transitions. + * + * @param view The View to capture the placement information for. + * @param name The shared element name in the target Activity to apply the placement + * information for. + * @param transitionArgs Bundle to store shared element placement information. + * @param tempLoc A temporary int[2] for capturing the current location of views. + */ + private static void captureSharedElementState(View view, String name, Bundle transitionArgs, + int[] tempLoc) { + Bundle sharedElementBundle = new Bundle(); + view.getLocationOnScreen(tempLoc); + float scaleX = view.getScaleX(); + sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); + int width = Math.round(view.getWidth() * scaleX); + sharedElementBundle.putInt(KEY_WIDTH, width); + + float scaleY = view.getScaleY(); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); + int height = Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_HEIGHT, height); + + sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); + + if (width > 0 && height > 0) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); + } + + if (view instanceof ImageView) { + ImageView imageView = (ImageView) view; + int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); + sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); + if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { + float[] matrix = new float[9]; + imageView.getImageMatrix().getValues(matrix); + sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); + } + } + + transitionArgs.putBundle(name, sharedElementBundle); + } + + private static int scaleTypeToInt(ImageView.ScaleType scaleType) { + for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { + if (scaleType == SCALE_TYPE_VALUES[i]) { + return i; + } + } + return -1; + } + private static class FixedEpicenterCallback extends Transition.EpicenterCallback { private Rect mEpicenter; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2f35160..84673d9 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1455,10 +1455,10 @@ final class ApplicationPackageManager extends PackageManager { * @hide */ @Override - public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig, - int userIdDest) { + public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable, + int sourceUserId, int targetUserId) { try { - mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest); + mPM.addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId); } catch (RemoteException e) { // Should never happen! } @@ -1468,14 +1468,31 @@ final class ApplicationPackageManager extends PackageManager { * @hide */ @Override - public void clearForwardingIntentFilters(int userIdOrig) { + public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int sourceUserId, + int targetUserId) { + addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId); + } + + /** + * @hide + */ + @Override + public void clearCrossProfileIntentFilters(int sourceUserId) { try { - mPM.clearForwardingIntentFilters(userIdOrig); + mPM.clearCrossProfileIntentFilters(sourceUserId); } catch (RemoteException e) { // Should never happen! } } + /** + * @hide + */ + @Override + public void clearForwardingIntentFilters(int sourceUserId) { + clearCrossProfileIntentFilters(sourceUserId); + } + private final ContextImpl mContext; private final IPackageManager mPM; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 1634d11..e03224c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -35,7 +35,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IntentSender; +import android.content.IRestrictionsManager; import android.content.ReceiverCallNotAllowedException; +import android.content.RestrictionsManager; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; @@ -58,7 +60,9 @@ import android.hardware.ISerialManager; import android.hardware.SerialManager; import android.hardware.SystemSensorManager; import android.hardware.hdmi.HdmiCecManager; +import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiCecService; +import android.hardware.hdmi.IHdmiControlService; import android.hardware.camera2.CameraManager; import android.hardware.display.DisplayManager; import android.hardware.input.InputManager; @@ -71,6 +75,8 @@ import android.location.LocationManager; import android.media.AudioManager; import android.media.MediaRouter; import android.media.session.MediaSessionManager; +import android.media.tv.ITvInputManager; +import android.media.tv.TvInputManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.EthernetManager; @@ -115,8 +121,6 @@ import android.service.fingerprint.FingerprintManager; import android.service.fingerprint.FingerprintManagerReceiver; import android.service.fingerprint.FingerprintService; import android.telephony.TelephonyManager; -import android.tv.ITvInputManager; -import android.tv.TvInputManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; import android.util.ArrayMap; @@ -133,10 +137,12 @@ import android.view.textservice.TextServicesManager; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.admin.DevicePolicyManager; +import android.app.task.ITaskManager; import android.app.trust.TrustManager; 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 java.io.File; @@ -247,6 +253,8 @@ class ContextImpl extends Context { private File[] mExternalFilesDirs; @GuardedBy("mSync") private File[] mExternalCacheDirs; + @GuardedBy("mSync") + private File[] mExternalMediaDirs; private static final String[] EMPTY_FILE_LIST = {}; @@ -384,6 +392,11 @@ class ContextImpl extends Context { return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b)); }}); + registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() { + public Object createStaticService() { + IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE); + return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b)); + }}); registerService(CLIPBOARD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { @@ -651,6 +664,13 @@ class ContextImpl extends Context { } }); + registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE); + IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b); + return new RestrictionsManager(ctx, service); + } + }); registerService(PRINT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE); @@ -693,6 +713,12 @@ class ContextImpl extends Context { public Object createService(ContextImpl ctx) { return new UsageStatsManager(ctx.getOuterContext()); }}); + + registerService(TASK_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(TASK_SERVICE); + return new TaskManagerImpl(ITaskManager.Stub.asInterface(b)); + }}); } static ContextImpl getImpl(Context context) { @@ -1024,6 +1050,18 @@ class ContextImpl extends Context { } @Override + public File[] getExternalMediaDirs() { + synchronized (mSync) { + if (mExternalMediaDirs == null) { + mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); + } + + // Create dirs if needed + return ensureDirsExistOrFilter(mExternalMediaDirs); + } + } + + @Override public File getFileStreamPath(String name) { return makeFilename(getFilesDir(), name); } @@ -1726,7 +1764,8 @@ class ContextImpl extends Context { arguments.setAllowFds(false); } return ActivityManagerNative.getDefault().startInstrumentation( - className, profileFile, 0, arguments, null, null, getUserId()); + className, profileFile, 0, arguments, null, null, getUserId(), + null /* ABI override */); } catch (RemoteException e) { // System has crashed, nothing we can do. } diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index bc97852..4b052e7 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -18,11 +18,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.Matrix; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -33,12 +29,12 @@ import android.transition.TransitionManager; import android.util.ArrayMap; import android.util.Pair; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import android.widget.ImageView; import java.util.ArrayList; -import java.util.Collection; /** * This ActivityTransitionCoordinator is created by the Activity to manage @@ -56,6 +52,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { private Handler mHandler; private boolean mIsCanceled; private ObjectAnimator mBackgroundAnimator; + private boolean mIsExitTransitionComplete; public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver, ArrayList<String> sharedElementNames, @@ -76,6 +73,24 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } }; mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS); + send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null); + } + } + + private void sendSharedElementDestination() { + ViewGroup decor = getDecor(); + if (!decor.isLayoutRequested()) { + Bundle state = captureSharedElementState(); + mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state); + } else { + getDecor().getViewTreeObserver() + .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + return true; + } + }); } } @@ -98,9 +113,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { break; case MSG_EXIT_TRANSITION_COMPLETE: if (!mIsCanceled) { - if (!mSharedElementTransitionStarted) { - send(resultCode, resultData); - } else { + mIsExitTransitionComplete = true; + if (mSharedElementTransitionStarted) { onRemoteExitTransitionComplete(); } } @@ -108,6 +122,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { case MSG_CANCEL: cancel(); break; + case MSG_SEND_SHARED_ELEMENT_DESTINATION: + sendSharedElementDestination(); + break; } } @@ -183,6 +200,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { setViewVisibility(mSharedElements, View.VISIBLE); ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState = setSharedElementState(sharedElementState, sharedElementSnapshots); + requestLayoutForSharedElements(); boolean startEnterTransition = allowOverlappingTransitions(); boolean startSharedElementTransition = true; @@ -200,6 +218,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { mResultReceiver = null; // all done sending messages. } + private void requestLayoutForSharedElements() { + int numSharedElements = mSharedElements.size(); + for (int i = 0; i < numSharedElements; i++) { + mSharedElements.get(i).requestLayout(); + } + } + private Transition beginTransition(boolean startEnterTransition, boolean startSharedElementTransition) { Transition sharedElementTransition = null; @@ -213,6 +238,19 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { } Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); + if (startSharedElementTransition) { + if (transition == null) { + sharedElementTransitionStarted(); + } else { + transition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionStart(Transition transition) { + transition.removeListener(this); + sharedElementTransitionStarted(); + } + }); + } + } if (transition != null) { TransitionManager.beginDelayedTransition(getDecor(), transition); if (startSharedElementTransition && !mSharedElementNames.isEmpty()) { @@ -224,6 +262,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { return transition; } + private void sharedElementTransitionStarted() { + mSharedElementTransitionStarted = true; + if (mIsExitTransitionComplete) { + send(MSG_EXIT_TRANSITION_COMPLETE, null); + } + } + private void startEnterTransition(Transition transition) { setViewVisibility(mTransitioningViews, View.VISIBLE); if (!mIsReturning) { @@ -310,142 +355,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { startEnterTransition(transition); } } - - private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) { - int numSharedElements = names.size(); - if (numSharedElements == 0) { - return null; - } - ArrayList<View> snapshots = new ArrayList<View>(numSharedElements); - Context context = getWindow().getContext(); - int[] parentLoc = new int[2]; - getDecor().getLocationOnScreen(parentLoc); - for (String name: names) { - Bundle sharedElementBundle = state.getBundle(name); - if (sharedElementBundle != null) { - Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP); - View snapshot = new View(context); - Resources resources = getWindow().getContext().getResources(); - snapshot.setBackground(new BitmapDrawable(resources, bitmap)); - snapshot.setViewName(name); - setSharedElementState(snapshot, name, state, parentLoc); - snapshots.add(snapshot); - } - } - return snapshots; - } - - private static void setSharedElementState(View view, String name, Bundle transitionArgs, - int[] parentLoc) { - Bundle sharedElementBundle = transitionArgs.getBundle(name); - if (sharedElementBundle == null) { - return; - } - - if (view instanceof ImageView) { - int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt >= 0) { - ImageView imageView = (ImageView) view; - ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt]; - imageView.setScaleType(scaleType); - if (scaleType == ImageView.ScaleType.MATRIX) { - float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX); - Matrix matrix = new Matrix(); - matrix.setValues(matrixValues); - imageView.setImageMatrix(matrix); - } - } - } - - float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z); - view.setTranslationZ(z); - - int x = sharedElementBundle.getInt(KEY_SCREEN_X); - int y = sharedElementBundle.getInt(KEY_SCREEN_Y); - int width = sharedElementBundle.getInt(KEY_WIDTH); - int height = sharedElementBundle.getInt(KEY_HEIGHT); - - int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - view.measure(widthSpec, heightSpec); - - int left = x - parentLoc[0]; - int top = y - parentLoc[1]; - int right = left + width; - int bottom = top + height; - view.layout(left, top, right, bottom); - } - - private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState( - Bundle sharedElementState, final ArrayList<View> snapshots) { - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState = - new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>(); - if (sharedElementState != null) { - int[] tempLoc = new int[2]; - for (int i = 0; i < mSharedElementNames.size(); i++) { - View sharedElement = mSharedElements.get(i); - String name = mSharedElementNames.get(i); - Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement, - name, sharedElementState); - if (originalState != null) { - originalImageState.put((ImageView) sharedElement, originalState); - } - View parent = (View) sharedElement.getParent(); - parent.getLocationOnScreen(tempLoc); - setSharedElementState(sharedElement, name, sharedElementState, tempLoc); - sharedElement.requestLayout(); - } - } - mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots); - - getDecor().getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, - snapshots); - mSharedElementTransitionStarted = true; - return true; - } - } - ); - return originalImageState; - } - - private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name, - Bundle transitionArgs) { - if (!(view instanceof ImageView)) { - return null; - } - Bundle bundle = transitionArgs.getBundle(name); - if (bundle == null) { - return null; - } - int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1); - if (scaleTypeInt < 0) { - return null; - } - - ImageView imageView = (ImageView) view; - ImageView.ScaleType originalScaleType = imageView.getScaleType(); - - Matrix originalMatrix = null; - if (originalScaleType == ImageView.ScaleType.MATRIX) { - originalMatrix = new Matrix(imageView.getImageMatrix()); - } - - return Pair.create(originalScaleType, originalMatrix); - } - - private static void setOriginalImageViewState( - ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) { - for (int i = 0; i < originalState.size(); i++) { - ImageView imageView = originalState.keyAt(i); - Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i); - imageView.setScaleType(state.first); - imageView.setImageMatrix(state.second); - } - } - } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 93eb53e..ba1638f 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -19,8 +19,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -29,7 +27,7 @@ import android.os.Message; import android.transition.Transition; import android.transition.TransitionManager; import android.view.View; -import android.widget.ImageView; +import android.view.ViewTreeObserver; import java.util.ArrayList; @@ -62,6 +60,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { private boolean mIsHidden; + private boolean mExitTransitionStarted; + + private Bundle mExitSharedElementBundle; + public ExitTransitionCoordinator(Activity activity, ArrayList<String> names, ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) { super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning), @@ -102,15 +104,32 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { setViewVisibility(mSharedElements, View.VISIBLE); mIsHidden = true; break; + case MSG_SHARED_ELEMENT_DESTINATION: + mExitSharedElementBundle = resultData; + if (mExitTransitionStarted) { + startSharedElementExit(); + } + break; + } + } + + private void startSharedElementExit() { + if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) { + Transition transition = getSharedElementExitTransition(); + TransitionManager.beginDelayedTransition(getDecor(), transition); + ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle, + mSharedElementNames); + setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots); } } private void hideSharedElements() { setViewVisibility(mSharedElements, View.INVISIBLE); + finishIfNecessary(); } public void startExit() { - beginTransition(); + beginTransitions(); setViewVisibility(mTransitioningViews, View.INVISIBLE); } @@ -140,7 +159,30 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }, options); - startExit(); + Transition sharedElementTransition = mSharedElements.isEmpty() + ? null : getSharedElementTransition(); + if (sharedElementTransition == null) { + sharedElementTransitionComplete(); + } + Transition transition = mergeTransitions(sharedElementTransition, getExitTransition()); + if (transition == null) { + mExitTransitionStarted = true; + } else { + TransitionManager.beginDelayedTransition(getDecor(), transition); + setViewVisibility(mTransitioningViews, View.INVISIBLE); + getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + mExitTransitionStarted = true; + if (mExitSharedElementBundle != null) { + startSharedElementExit(); + } + notifyComplete(); + return true; + } + }); + } } private void fadeOutBackground() { @@ -162,40 +204,62 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } - private void beginTransition() { - Transition sharedElementTransition = configureTransition(getSharedElementTransition()); - Transition viewsTransition = configureTransition(getViewsTransition()); - viewsTransition = addTargets(viewsTransition, mTransitioningViews); - if (sharedElementTransition == null || mSharedElements.isEmpty()) { - sharedElementTransitionComplete(); - sharedElementTransition = null; + private Transition getExitTransition() { + Transition viewsTransition = null; + if (!mTransitioningViews.isEmpty()) { + viewsTransition = configureTransition(getViewsTransition()); + } + if (viewsTransition == null) { + exitTransitionComplete(); } else { - sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { + viewsTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { - sharedElementTransitionComplete(); + exitTransitionComplete(); + if (mIsHidden) { + setViewVisibility(mTransitioningViews, View.VISIBLE); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + super.onTransitionCancel(transition); } }); } - if (viewsTransition == null || mTransitioningViews.isEmpty()) { - exitTransitionComplete(); - viewsTransition = null; + return viewsTransition; + } + + private Transition getSharedElementExitTransition() { + Transition sharedElementTransition = null; + if (!mSharedElements.isEmpty()) { + sharedElementTransition = configureTransition(getSharedElementTransition()); + } + if (sharedElementTransition == null) { + sharedElementTransitionComplete(); } else { - viewsTransition.addListener(new Transition.TransitionListenerAdapter() { + sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { - exitTransitionComplete(); + sharedElementTransitionComplete(); if (mIsHidden) { - setViewVisibility(mTransitioningViews, View.VISIBLE); + setViewVisibility(mSharedElements, View.VISIBLE); } } }); + mSharedElements.get(0).invalidate(); } + return sharedElementTransition; + } + + private void beginTransitions() { + Transition sharedElementTransition = getSharedElementExitTransition(); + Transition viewsTransition = getExitTransition(); Transition transition = mergeTransitions(sharedElementTransition, viewsTransition); - TransitionManager.beginDelayedTransition(getDecor(), transition); - if (viewsTransition == null && sharedElementTransition != null) { - mSharedElements.get(0).requestLayout(); + mExitTransitionStarted = true; + if (transition != null) { + TransitionManager.beginDelayedTransition(getDecor(), transition); } } @@ -205,18 +269,12 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } protected boolean isReadyToNotify() { - return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady; + return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady + && mExitTransitionStarted; } private void sharedElementTransitionComplete() { - Bundle bundle = new Bundle(); - int[] tempLoc = new int[2]; - for (int i = 0; i < mSharedElementNames.size(); i++) { - View sharedElement = mSharedElements.get(i); - String name = mSharedElementNames.get(i); - captureSharedElementState(sharedElement, name, bundle, tempLoc); - } - mSharedElementBundle = bundle; + mSharedElementBundle = captureSharedElementState(); notifyComplete(); } @@ -230,15 +288,23 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mExitNotified = true; mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null); mResultReceiver = null; // done talking - if (mIsReturning) { - mActivity.finish(); - mActivity.overridePendingTransition(0, 0); - } - mActivity = null; + finishIfNecessary(); } } } + private void finishIfNecessary() { + if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() + || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) { + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + mActivity = null; + } + if (!mIsReturning && mExitNotified) { + mActivity = null; // don't need it anymore + } + } + @Override protected Transition getViewsTransition() { if (mIsReturning) { @@ -255,58 +321,4 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { return getWindow().getSharedElementExitTransition(); } } - - /** - * Captures placement information for Views with a shared element name for - * Activity Transitions. - * - * @param view The View to capture the placement information for. - * @param name The shared element name in the target Activity to apply the placement - * information for. - * @param transitionArgs Bundle to store shared element placement information. - * @param tempLoc A temporary int[2] for capturing the current location of views. - */ - private static void captureSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { - Bundle sharedElementBundle = new Bundle(); - view.getLocationOnScreen(tempLoc); - float scaleX = view.getScaleX(); - sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); - int width = Math.round(view.getWidth() * scaleX); - sharedElementBundle.putInt(KEY_WIDTH, width); - - float scaleY = view.getScaleY(); - sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); - int height = Math.round(view.getHeight() * scaleY); - sharedElementBundle.putInt(KEY_HEIGHT, height); - - sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.draw(canvas); - sharedElementBundle.putParcelable(KEY_BITMAP, bitmap); - - if (view instanceof ImageView) { - ImageView imageView = (ImageView) view; - int scaleTypeInt = scaleTypeToInt(imageView.getScaleType()); - sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt); - if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) { - float[] matrix = new float[9]; - imageView.getImageMatrix().getValues(matrix); - sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix); - } - } - - transitionArgs.putBundle(name, sharedElementBundle); - } - - private static int scaleTypeToInt(ImageView.ScaleType scaleType) { - for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) { - if (scaleType == SCALE_TYPE_VALUES[i]) { - return i; - } - } - return -1; - } } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 8434c2a..bf2d7e5 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -176,7 +176,8 @@ public interface IActivityManager extends IInterface { public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, - IUiAutomationConnection connection, int userId) throws RemoteException; + IUiAutomationConnection connection, int userId, + String abiOverride) throws RemoteException; public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) throws RemoteException; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6e23b11..8dba1dc 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -989,10 +989,9 @@ public class Notification implements Parcelable * <pre class="prettyprint"> * Notification.Action action = new Notification.Action.Builder( * R.drawable.archive_all, "Archive all", actionIntent) - * .apply(new Notification.Action.WearableExtender() + * .extend(new Notification.Action.WearableExtender() * .setAvailableOffline(false)) - * .build(); - * </pre> + * .build();</pre> */ public static final class WearableExtender implements Extender { /** Notification action extra which contains wearable extensions */ @@ -1672,7 +1671,6 @@ public class Notification implements Parcelable private Notification mPublicVersion = null; private final NotificationColorUtil mColorUtil; private ArrayList<String> mPeople; - private boolean mPreQuantum; private int mColor = COLOR_DEFAULT; /** @@ -1695,6 +1693,15 @@ public class Notification implements Parcelable * object. */ public Builder(Context context) { + /* + * Important compatibility note! + * Some apps out in the wild create a Notification.Builder in their Activity subclass + * constructor for later use. At this point Activities - themselves subclasses of + * ContextWrapper - do not have their inner Context populated yet. This means that + * any calls to Context methods from within this constructor can cause NPEs in existing + * apps. Any data populated from mContext should therefore be populated lazily to + * preserve compatibility. + */ mContext = context; // Set defaults to match the defaults of a Notification @@ -1703,7 +1710,6 @@ public class Notification implements Parcelable mPriority = PRIORITY_DEFAULT; mPeople = new ArrayList<String>(); - mPreQuantum = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L; mColorUtil = NotificationColorUtil.getInstance(); } @@ -3307,8 +3313,7 @@ public class Notification implements Parcelable * <pre class="prettyprint"> * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( * notification); - * List<Notification> pages = wearableExtender.getPages(); - * </pre> + * List<Notification> pages = wearableExtender.getPages();</pre> */ public static final class WearableExtender implements Extender { /** @@ -3357,6 +3362,14 @@ public class Notification implements Parcelable */ public static final int SIZE_LARGE = 4; + /** + * Size value for use with {@link #setCustomSizePreset} to show this notification + * full screen. + * <p>This value is only applicable for custom display notifications created using + * {@link #setDisplayIntent}. + */ + public static final int SIZE_FULL_SCREEN = 5; + /** Notification extra which contains wearable extensions */ private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; @@ -3553,7 +3566,27 @@ public class Notification implements Parcelable /** * Set an intent to launch inside of an activity view when displaying - * this notification. This {@link PendingIntent} should be for an activity. + * this notification. The {@link PendingIntent} provided should be for an activity. + * + * <pre class="prettyprint"> + * Intent displayIntent = new Intent(context, MyDisplayActivity.class); + * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, + * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); + * Notification notif = new Notification.Builder(context) + * .extend(new Notification.WearableExtender() + * .setDisplayIntent(displayPendingIntent) + * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) + * .build();</pre> + * + * <p>The activity to launch needs to allow embedding, must be exported, and + * should have an empty task affinity. + * + * <p>Example AndroidManifest.xml entry: + * <pre class="prettyprint"> + * <activity android:name="com.example.MyDisplayActivity" + * android:exported="true" + * android:allowEmbedded="true" + * android:taskAffinity="" /></pre> * * @param intent the {@link PendingIntent} for an activity * @return this object for method chaining @@ -3687,12 +3720,17 @@ public class Notification implements Parcelable /** * Set an action from this notification's actions to be clickable with the content of - * this notification page. This action will no longer display separately from the - * notification content. This action's icon will display with optional subtext provided - * by the action's title. - * @param actionIndex The index of the action to hoist on the current notification page. - * If wearable actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. + * this notification. This action will no longer display separately from the + * notification's content. + * + * <p>For notifications with multiple pages, child pages can also have content actions + * set, although the list of available actions comes from the main notification and not + * from the child page's notification. + * + * @param actionIndex The index of the action to hoist onto the current notification page. + * If wearable actions were added to the main notification, this index + * will apply to that list, otherwise it will apply to the regular + * actions list. */ public WearableExtender setContentAction(int actionIndex) { mContentActionIndex = actionIndex; @@ -3700,14 +3738,18 @@ public class Notification implements Parcelable } /** - * Get the action index of an action from this notification to show as clickable with - * the content of this notification page. When the user clicks this notification page, - * this action will trigger. This action will no longer display separately from the - * notification content. The action's icon will display with optional subtext provided - * by the action's title. + * Get the index of the notification action, if any, that was specified as being clickable + * with the content of this notification. This action will no longer display separately + * from the notification's content. + * + * <p>For notifications with multiple pages, child pages can also have content actions + * set, although the list of available actions comes from the main notification and not + * from the child page's notification. + * + * <p>If wearable specific actions were added to the main notification, this index will + * apply to that list, otherwise it will apply to the regular actions list. * - * <p>If wearable specific actions are present, this index will apply to that list, - * otherwise it will apply to the main notification's actions list. + * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. */ public int getContentAction() { return mContentActionIndex; diff --git a/core/java/android/app/TaskManagerImpl.java b/core/java/android/app/TaskManagerImpl.java new file mode 100644 index 0000000..f42839e --- /dev/null +++ b/core/java/android/app/TaskManagerImpl.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. + */ + +// in android.app so ContextImpl has package access +package android.app; + +import android.app.task.ITaskManager; +import android.app.task.Task; +import android.app.task.TaskManager; + +import java.util.List; + + +/** + * Concrete implementation of the TaskManager interface + * @hide + */ +public class TaskManagerImpl extends TaskManager { + ITaskManager mBinder; + + /* package */ TaskManagerImpl(ITaskManager binder) { + mBinder = binder; + } + + @Override + public int schedule(Task task) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void cancel(int taskId) { + // TODO Auto-generated method stub + + } + + @Override + public void cancelAll() { + // TODO Auto-generated method stub + + } + + @Override + public List<Task> getAllPendingTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 6dc48b0..85e970c 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -30,18 +30,39 @@ import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; -import java.util.WeakHashMap; +import java.util.ArrayList; /** - * Interface for an {@link Activity} to interact with the user through voice. + * Interface for an {@link Activity} to interact with the user through voice. Use + * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor} + * to retrieve the interface, if the activity is currently involved in a voice interaction. + * + * <p>The voice interactor revolves around submitting voice interaction requests to the + * back-end voice interaction service that is working with the user. These requests are + * submitted with {@link #submitRequest}, providing a new instance of a + * {@link Request} subclass describing the type of operation to perform -- currently the + * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}. + * + * <p>Once a request is submitted, the voice system will process it and eventually deliver + * the result to the request object. The application can cancel a pending request at any + * time. + * + * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that + * if an activity is being restarted with retained state, it will retain the current + * VoiceInteractor and any outstanding requests. Because of this, you should always use + * {@link Request#getActivity() Request.getActivity} to get back to the activity of a + * request, rather than holding on to the activity instance yourself, either explicitly + * or implicitly through a non-static inner class. */ public class VoiceInteractor { static final String TAG = "VoiceInteractor"; static final boolean DEBUG = true; - final Context mContext; - final Activity mActivity; final IVoiceInteractor mInteractor; + + Context mContext; + Activity mActivity; + final HandlerCaller mHandlerCaller; final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() { @Override @@ -60,6 +81,16 @@ public class VoiceInteractor { request.clear(); } break; + case MSG_ABORT_VOICE_RESULT: + request = pullRequest((IVoiceInteractorRequest)args.arg1, true); + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + + " result=" + args.arg1); + if (request != null) { + ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); + request.clear(); + } + break; case MSG_COMMAND_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); if (DEBUG) Log.d(TAG, "onCommandResult: req=" @@ -94,6 +125,12 @@ public class VoiceInteractor { } @Override + public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO( + MSG_ABORT_VOICE_RESULT, request, result)); + } + + @Override public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, Bundle result) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( @@ -110,8 +147,9 @@ public class VoiceInteractor { final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); static final int MSG_CONFIRMATION_RESULT = 1; - static final int MSG_COMMAND_RESULT = 2; - static final int MSG_CANCEL_RESULT = 3; + static final int MSG_ABORT_VOICE_RESULT = 2; + static final int MSG_COMMAND_RESULT = 3; + static final int MSG_CANCEL_RESULT = 4; public static abstract class Request { IVoiceInteractorRequest mRequestInterface; @@ -140,6 +178,12 @@ public class VoiceInteractor { public void onCancel() { } + public void onAttached(Activity activity) { + } + + public void onDetached() { + } + void clear() { mRequestInterface = null; mContext = null; @@ -180,9 +224,42 @@ public class VoiceInteractor { IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, IVoiceInteractorCallback callback) throws RemoteException { - return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras); + return interactor.startConfirmation(packageName, callback, mPrompt, mExtras); + } + } + + public static class AbortVoiceRequest extends Request { + final CharSequence mMessage; + final Bundle mExtras; + + /** + * Reports that the current interaction can not be complete with voice, so the + * application will need to switch to a traditional input UI. Applications should + * only use this when they need to completely bail out of the voice interaction + * and switch to a traditional UI. When the response comes back, the voice + * system has handled the request and is ready to switch; at that point the application + * can start a new non-voice activity. Be sure when starting the new activity + * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice + * interaction task. + * + * @param message Optional message to tell user about not being able to complete + * the interaction with voice. + * @param extras Additional optional information. + */ + public AbortVoiceRequest(CharSequence message, Bundle extras) { + mMessage = message; + mExtras = extras; } - } + + public void onAbortResult(Bundle result) { + } + + IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, + IVoiceInteractorCallback callback) throws RemoteException { + return interactor.startAbortVoice(packageName, callback, mMessage, mExtras); + } + } public static class CommandRequest extends Request { final String mCommand; @@ -220,11 +297,11 @@ public class VoiceInteractor { } } - VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor, + VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity, Looper looper) { + mInteractor = interactor; mContext = context; mActivity = activity; - mInteractor = interactor; mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true); } @@ -238,6 +315,49 @@ public class VoiceInteractor { } } + private ArrayList<Request> makeRequestList() { + final int N = mActiveRequests.size(); + if (N < 1) { + return null; + } + ArrayList<Request> list = new ArrayList<Request>(N); + for (int i=0; i<N; i++) { + list.add(mActiveRequests.valueAt(i)); + } + return list; + } + + void attachActivity(Activity activity) { + if (mActivity == activity) { + return; + } + mContext = activity; + mActivity = activity; + ArrayList<Request> reqs = makeRequestList(); + if (reqs != null) { + for (int i=0; i<reqs.size(); i++) { + Request req = reqs.get(i); + req.mContext = activity; + req.mActivity = activity; + req.onAttached(activity); + } + } + } + + void detachActivity() { + ArrayList<Request> reqs = makeRequestList(); + if (reqs != null) { + for (int i=0; i<reqs.size(); i++) { + Request req = reqs.get(i); + req.onDetached(); + req.mActivity = null; + req.mContext = null; + } + } + mContext = null; + mActivity = null; + } + public boolean submitRequest(Request request) { try { IVoiceInteractorRequest ireq = request.submit(mInteractor, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 77b1acf..afe2981 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.RestrictionsManager; import android.os.Bundle; import android.os.Handler; import android.os.Process; @@ -103,6 +104,17 @@ public class DevicePolicyManager { = "android.app.action.ACTION_PROVISION_MANAGED_PROFILE"; /** + * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the + * user has already consented to the creation of the managed profile. + * The intent must contain the extras + * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and + * {@link #EXTRA_PROVISIONING_TOKEN} + * @hide + */ + public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED + = "android.app.action.USER_HAS_CONSENTED"; + + /** * A String extra holding the name of the package of the mobile device management application * that starts the managed provisioning flow. This package will be set as the profile owner. * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}. @@ -111,6 +123,13 @@ public class DevicePolicyManager { = "deviceAdminPackageName"; /** + * An int extra used to identify the consent of the user to create the managed profile. + * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} + */ + public static final String EXTRA_PROVISIONING_TOKEN + = "android.app.extra.token"; + + /** * A String extra holding the default name of the profile that is created during managed profile * provisioning. * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE} @@ -176,15 +195,16 @@ public class DevicePolicyManager { public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD"; /** - * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user. + * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a + * managed profile to its parent. */ - public static int FLAG_TO_PRIMARY_USER = 0x0001; + public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001; /** - * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed - * profile. + * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the + * parent to its managed profile. */ - public static int FLAG_TO_MANAGED_PROFILE = 0x0002; + public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002; /** * Return true if the given administrator component is currently @@ -1951,17 +1971,16 @@ public class DevicePolicyManager { } /** - * Called by a profile owner to forward intents sent from the managed profile to the owner, or - * from the owner to the managed profile. - * If an intent matches this intent filter, then activities belonging to the other user can - * respond to this intent. + * Called by the profile owner so that some intents sent in the managed profile can also be + * resolved in the parent, or vice versa. * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param filter if an intent matches this IntentFilter, then it can be forwarded. + * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the + * other profile */ - public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) { + public void addCrossProfileIntentFilter(ComponentName admin, IntentFilter filter, int flags) { if (mService != null) { try { - mService.addForwardingIntentFilter(admin, filter, flags); + mService.addCrossProfileIntentFilter(admin, filter, flags); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1969,14 +1988,14 @@ public class DevicePolicyManager { } /** - * Called by a profile owner to remove the forwarding intent filters from the current user - * and from the owner. + * Called by a profile owner to remove the cross-profile intent filters from the managed profile + * and from the parent. * @param admin Which {@link DeviceAdminReceiver} this request is associated with. */ - public void clearForwardingIntentFilters(ComponentName admin) { + public void clearCrossProfileIntentFilters(ComponentName admin) { if (mService != null) { try { - mService.clearForwardingIntentFilters(admin); + mService.clearCrossProfileIntentFilters(admin); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2320,4 +2339,23 @@ public class DevicePolicyManager { } } + /** + * Designates a specific broadcast receiver component as the provider for + * making permission requests of a local or remote administrator of the user. + * <p/> + * Only a profile owner can designate the restrictions provider. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param receiver The component name of a BroadcastReceiver that handles the + * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null, + * it removes the restrictions provider previously assigned. + */ + public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) { + if (mService != null) { + try { + mService.setRestrictionsProvider(admin, receiver); + } catch (RemoteException re) { + Log.w(TAG, "Failed to set permission provider on device policy service"); + } + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3c08c14..7f754dc 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -121,9 +121,12 @@ interface IDevicePolicyManager { void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); Bundle getApplicationRestrictions(in ComponentName who, in String packageName); + void setRestrictionsProvider(in ComponentName who, in ComponentName provider); + ComponentName getRestrictionsProvider(int userHandle); + void setUserRestriction(in ComponentName who, in String key, boolean enable); - void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); - void clearForwardingIntentFilters(in ComponentName admin); + void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags); + void clearCrossProfileIntentFilters(in ComponentName admin); boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked); int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java new file mode 100644 index 0000000..46f082e --- /dev/null +++ b/core/java/android/app/backup/BackupTransport.java @@ -0,0 +1,415 @@ +/* + * 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.backup; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.backup.IBackupTransport; + +/** + * Concrete class that provides a stable-API bridge between IBackupTransport + * and its implementations. + * + * @hide + */ +public class BackupTransport { + public static final int TRANSPORT_OK = 0; + public static final int TRANSPORT_ERROR = 1; + public static final int TRANSPORT_NOT_INITIALIZED = 2; + public static final int TRANSPORT_PACKAGE_REJECTED = 3; + public static final int AGENT_ERROR = 4; + public static final int AGENT_UNKNOWN = 5; + + IBackupTransport mBinderImpl = new TransportImpl(); + /** @hide */ + public IBinder getBinder() { + return mBinderImpl.asBinder(); + } + + // ------------------------------------------------------------------------------------ + // Transport self-description and general configuration interfaces + // + + /** + * Ask the transport for the name under which it should be registered. This will + * typically be its host service's component name, but need not be. + */ + public String name() { + throw new UnsupportedOperationException("Transport name() not implemented"); + } + + /** + * Ask the transport for an Intent that can be used to launch any internal + * configuration Activity that it wishes to present. For example, the transport + * may offer a UI for allowing the user to supply login credentials for the + * transport's off-device backend. + * + * If the transport does not supply any user-facing configuration UI, it should + * return null from this method. + * + * @return An Intent that can be passed to Context.startActivity() in order to + * launch the transport's configuration UI. This method will return null + * if the transport does not offer any user-facing configuration UI. + */ + public Intent configurationIntent() { + return null; + } + + /** + * On demand, supply a one-line string that can be shown to the user that + * describes the current backend destination. For example, a transport that + * can potentially associate backup data with arbitrary user accounts should + * include the name of the currently-active account here. + * + * @return A string describing the destination to which the transport is currently + * sending data. This method should not return null. + */ + public String currentDestinationString() { + throw new UnsupportedOperationException( + "Transport currentDestinationString() not implemented"); + } + + /** + * Ask the transport where, on local device storage, to keep backup state blobs. + * This is per-transport so that mock transports used for testing can coexist with + * "live" backup services without interfering with the live bookkeeping. The + * returned string should be a name that is expected to be unambiguous among all + * available backup transports; the name of the class implementing the transport + * is a good choice. + * + * @return A unique name, suitable for use as a file or directory name, that the + * Backup Manager could use to disambiguate state files associated with + * different backup transports. + */ + public String transportDirName() { + throw new UnsupportedOperationException( + "Transport transportDirName() not implemented"); + } + + // ------------------------------------------------------------------------------------ + // Device-level operations common to both key/value and full-data storage + + /** + * Initialize the server side storage for this device, erasing all stored data. + * The transport may send the request immediately, or may buffer it. After + * this is called, {@link #finishBackup} will be called to ensure the request + * is sent and received successfully. + * + * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure). + */ + public int initializeDevice() { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Erase the given application's data from the backup destination. This clears + * out the given package's data from the current backup set, making it as though + * the app had never yet been backed up. After this is called, {@link finishBackup} + * must be called to ensure that the operation is recorded successfully. + * + * @return the same error codes as {@link #performBackup}. + */ + public int clearBackupData(PackageInfo packageInfo) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Finish sending application data to the backup destination. This must be + * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData} + * to ensure that all data is sent and the operation properly finalized. Only when this + * method returns true can a backup be assumed to have succeeded. + * + * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}. + */ + public int finishBackup() { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Key/value incremental backup support interfaces + + /** + * Verify that this is a suitable time for a key/value backup pass. This should return zero + * if a backup is reasonable right now, some positive value otherwise. This method + * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair. + * + * <p>If this is not a suitable time for a backup, the transport should return a + * backoff delay, in milliseconds, after which the Backup Manager should try again. + * + * @return Zero if this is a suitable time for a backup pass, or a positive time delay + * in milliseconds to suggest deferring the backup pass for a while. + */ + public long requestBackupTime() { + return 0; + } + + /** + * Send one application's key/value data update to the backup destination. The + * transport may send the data immediately, or may buffer it. After this is called, + * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully. + * + * @param packageInfo The identity of the application whose data is being backed up. + * This specifically includes the signature list for the package. + * @param data The data stream that resulted from invoking the application's + * BackupService.doBackup() method. This may be a pipe rather than a file on + * persistent media, so it may not be seekable. + * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account + * must be erased prior to the storage of the data provided here. The purpose of this + * is to provide a guarantee that no stale data exists in the restore set when the + * device begins providing incremental backups. + * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), + * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or + * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has + * become lost due to inactivity purge or some other reason and needs re-initializing) + */ + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) { + return BackupTransport.TRANSPORT_ERROR; + } + + // ------------------------------------------------------------------------------------ + // Key/value dataset restore interfaces + + /** + * Get the set of all backups currently available over this transport. + * + * @return Descriptions of the set of restore images available for this device, + * or null if an error occurred (the attempt should be rescheduled). + **/ + public RestoreSet[] getAvailableRestoreSets() { + return null; + } + + /** + * Get the identifying token of the backup set currently being stored from + * this device. This is used in the case of applications wishing to restore + * their last-known-good data. + * + * @return A token that can be passed to {@link #startRestore}, or 0 if there + * is no backup set available corresponding to the current device state. + */ + public long getCurrentRestoreSet() { + return 0; + } + + /** + * Start restoring application data from backup. After calling this function, + * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData} + * to walk through the actual application data. + * + * @param token A backup token as returned by {@link #getAvailableRestoreSets} + * or {@link #getCurrentRestoreSet}. + * @param packages List of applications to restore (if data is available). + * Application data will be restored in the order given. + * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call + * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR} + * (an error occurred, the restore should be aborted and rescheduled). + */ + public int startRestore(long token, PackageInfo[] packages) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Get the package name of the next application with data in the backup store. + * + * @return The name of one of the packages supplied to {@link #startRestore}, + * or "" (the empty string) if no more backup data is available, + * or null if an error occurred (the restore should be aborted and rescheduled). + */ + public String nextRestorePackage() { + return null; + } + + /** + * Get the data for the application returned by {@link #nextRestorePackage}. + * @param data An open, writable file into which the backup data should be stored. + * @return the same error codes as {@link #startRestore}. + */ + public int getRestoreData(ParcelFileDescriptor outFd) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * End a restore session (aborting any in-process data transfer as necessary), + * freeing any resources and connections used during the restore process. + */ + public void finishRestore() { + throw new UnsupportedOperationException( + "Transport finishRestore() not implemented"); + } + + // ------------------------------------------------------------------------------------ + // Full backup interfaces + + /** + * Verify that this is a suitable time for a full-data backup pass. This should return zero + * if a backup is reasonable right now, some positive value otherwise. This method + * will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair. + * + * <p>If this is not a suitable time for a backup, the transport should return a + * backoff delay, in milliseconds, after which the Backup Manager should try again. + * + * @return Zero if this is a suitable time for a backup pass, or a positive time delay + * in milliseconds to suggest deferring the backup pass for a while. + * + * @see #requestBackupTime() + */ + public long requestFullBackupTime() { + return 0; + } + + /** + * Begin the process of sending an application's full-data archive to the backend. + * The description of the package whose data will be delivered is provided, as well as + * the socket file descriptor on which the transport will receive the data itself. + * + * <p>If the package is not eligible for backup, the transport should return + * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will + * simply proceed with the next candidate if any, or finish the full backup operation + * if all apps have been processed. + * + * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this + * method, the OS will proceed to call {@link #sendBackupData()} one or more times + * to deliver the application's data as a streamed tarball. The transport should not + * read() from the socket except as instructed to via the {@link #sendBackupData(int)} + * method. + * + * <p>After all data has been delivered to the transport, the system will call + * {@link #finishBackup()}. At this point the transport should commit the data to + * its datastore, if appropriate, and close the socket that had been provided in + * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}. + * + * @param targetPackage The package whose data is to follow. + * @param socket The socket file descriptor through which the data will be provided. + * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still + * close this file descriptor now; otherwise it should be cached for use during + * succeeding calls to {@link #sendBackupData(int)}, and closed in response to + * {@link #finishBackup()}. + * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not + * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering + * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes + * performing a backup at this time. + */ + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) { + return BackupTransport.TRANSPORT_PACKAGE_REJECTED; + } + + /** + * Tells the transport to read {@code numBytes} bytes of data from the socket file + * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} + * call, and deliver those bytes to the datastore. + * + * @param numBytes The number of bytes of tarball data available to be read from the + * socket. + * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to + * indicate a fatal error situation. If an error is returned, the system will + * call finishBackup() and stop attempting backups until after a backoff and retry + * interval. + */ + public int sendBackupData(int numBytes) { + return BackupTransport.TRANSPORT_ERROR; + } + + /** + * Bridge between the actual IBackupTransport implementation and the stable API. If the + * binder interface needs to change, we use this layer to translate so that we can + * (if appropriate) decouple those framework-side changes from the BackupTransport + * implementations. + */ + class TransportImpl extends IBackupTransport.Stub { + + @Override + public String name() throws RemoteException { + return BackupTransport.this.name(); + } + + @Override + public Intent configurationIntent() throws RemoteException { + return BackupTransport.this.configurationIntent(); + } + + @Override + public String currentDestinationString() throws RemoteException { + return BackupTransport.this.currentDestinationString(); + } + + @Override + public String transportDirName() throws RemoteException { + return BackupTransport.this.transportDirName(); + } + + @Override + public long requestBackupTime() throws RemoteException { + return BackupTransport.this.requestBackupTime(); + } + + @Override + public int initializeDevice() throws RemoteException { + return BackupTransport.this.initializeDevice(); + } + + @Override + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) + throws RemoteException { + return BackupTransport.this.performBackup(packageInfo, inFd); + } + + @Override + public int clearBackupData(PackageInfo packageInfo) throws RemoteException { + return BackupTransport.this.clearBackupData(packageInfo); + } + + @Override + public int finishBackup() throws RemoteException { + return BackupTransport.this.finishBackup(); + } + + @Override + public RestoreSet[] getAvailableRestoreSets() throws RemoteException { + return BackupTransport.this.getAvailableRestoreSets(); + } + + @Override + public long getCurrentRestoreSet() throws RemoteException { + return BackupTransport.this.getCurrentRestoreSet(); + } + + @Override + public int startRestore(long token, PackageInfo[] packages) throws RemoteException { + return BackupTransport.this.startRestore(token, packages); + } + + @Override + public String nextRestorePackage() throws RemoteException { + return BackupTransport.this.nextRestorePackage(); + } + + @Override + public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { + return BackupTransport.this.getRestoreData(outFd); + } + + @Override + public void finishRestore() throws RemoteException { + BackupTransport.this.finishRestore(); + } + } +} diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/app/task/ITaskManager.aidl index a2bd0d7..b56c78a 100644 --- a/core/java/android/tv/ITvInputSessionCallback.aidl +++ b/core/java/android/app/task/ITaskManager.aidl @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,15 +14,17 @@ * limitations under the License. */ -package android.tv; +package android.app.task; -import android.tv.ITvInputSession; +import android.app.task.Task; -/** - * Helper interface for ITvInputSession to allow the TV input to notify the system service when a - * new session has been created. - * @hide - */ -oneway interface ITvInputSessionCallback { - void onSessionCreated(ITvInputSession session); + /** + * IPC interface that supports the app-facing {@link #TaskManager} api. + * {@hide} + */ +interface ITaskManager { + int schedule(in Task task); + void cancel(int taskId); + void cancelAll(); + List<Task> getAllPendingTasks(); } diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/app/task/Task.aidl index abc4b47..1f25439 100644 --- a/core/java/android/tv/TvInputInfo.aidl +++ b/core/java/android/app/task/Task.aidl @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,7 @@ * limitations under the License. */ -package android.tv; +package android.app.task; -parcelable TvInputInfo; +parcelable Task; +
\ No newline at end of file diff --git a/core/java/android/content/Task.java b/core/java/android/app/task/Task.java index 407880f..ca4aeb2 100644 --- a/core/java/android/content/Task.java +++ b/core/java/android/app/task/Task.java @@ -14,23 +14,26 @@ * limitations under the License */ -package android.content; +package android.app.task; -import android.app.task.TaskService; +import android.content.ComponentName; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; /** - * Container of data passed to the {@link android.content.TaskManager} fully encapsulating the + * Container of data passed to the {@link android.app.task.TaskManager} fully encapsulating the * parameters required to schedule work against the calling application. These are constructed * using the {@link Task.Builder}. */ public class Task implements Parcelable { - public interface NetworkType { - public final int ANY = 0; - public final int UNMETERED = 1; + /** Default. */ + public final int NONE = 0; + /** This task requires network connectivity. */ + public final int ANY = 1; + /** This task requires network connectivity that is unmetered. */ + public final int UNMETERED = 2; } /** @@ -48,6 +51,8 @@ public class Task implements Parcelable { private final ComponentName service; private final boolean requireCharging; private final boolean requireDeviceIdle; + private final boolean hasEarlyConstraint; + private final boolean hasLateConstraint; private final int networkCapabilities; private final long minLatencyMillis; private final long maxExecutionDelayMillis; @@ -59,7 +64,7 @@ public class Task implements Parcelable { /** * Unique task id associated with this class. This is assigned to your task by the scheduler. */ - public int getTaskId() { + public int getId() { return taskId; } @@ -92,7 +97,7 @@ public class Task implements Parcelable { } /** - * See {@link android.content.Task.NetworkType} for a description of this value. + * See {@link android.app.task.Task.NetworkType} for a description of this value. */ public int getNetworkCapabilities() { return networkCapabilities; @@ -139,13 +144,31 @@ public class Task implements Parcelable { } /** - * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field + * See {@link android.app.task.Task.BackoffPolicy} for an explanation of the values this field * can take. This defaults to exponential. */ public int getBackoffPolicy() { return backoffPolicy; } + /** + * User can specify an early constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasEarlyConstraint() { + return hasEarlyConstraint; + } + + /** + * User can specify a late constraint of 0L, which is valid, so we keep track of whether the + * function was called at all. + * @hide + */ + public boolean hasLateConstraint() { + return hasLateConstraint; + } + private Task(Parcel in) { taskId = in.readInt(); extras = in.readBundle(); @@ -159,6 +182,8 @@ public class Task implements Parcelable { intervalMillis = in.readLong(); initialBackoffMillis = in.readLong(); backoffPolicy = in.readInt(); + hasEarlyConstraint = in.readInt() == 1; + hasLateConstraint = in.readInt() == 1; } private Task(Task.Builder b) { @@ -174,6 +199,8 @@ public class Task implements Parcelable { intervalMillis = b.mIntervalMillis; initialBackoffMillis = b.mInitialBackoffMillis; backoffPolicy = b.mBackoffPolicy; + hasEarlyConstraint = b.mHasEarlyConstraint; + hasLateConstraint = b.mHasLateConstraint; } @Override @@ -195,6 +222,8 @@ public class Task implements Parcelable { out.writeLong(intervalMillis); out.writeLong(initialBackoffMillis); out.writeInt(backoffPolicy); + out.writeInt(hasEarlyConstraint ? 1 : 0); + out.writeInt(hasLateConstraint ? 1 : 0); } public static final Creator<Task> CREATOR = new Creator<Task>() { @@ -212,7 +241,7 @@ public class Task implements Parcelable { /** * Builder class for constructing {@link Task} objects. */ - public final class Builder { + public static final class Builder { private int mTaskId; private Bundle mExtras; private ComponentName mTaskService; @@ -225,6 +254,8 @@ public class Task implements Parcelable { private long mMaxExecutionDelayMillis; // Periodic parameters. private boolean mIsPeriodic; + private boolean mHasEarlyConstraint; + private boolean mHasLateConstraint; private long mIntervalMillis; // Back-off parameters. private long mInitialBackoffMillis = 5000L; @@ -255,7 +286,7 @@ public class Task implements Parcelable { /** * Set some description of the kind of network capabilities you would like to have. This - * will be a parameter defined in {@link android.content.Task.NetworkType}. + * will be a parameter defined in {@link android.app.task.Task.NetworkType}. * Not calling this function means the network is not necessary. * Bear in mind that calling this function defines network as a strict requirement for your * task if the network requested is not available your task will never run. See @@ -307,6 +338,7 @@ public class Task implements Parcelable { public Builder setPeriodic(long intervalMillis) { mIsPeriodic = true; mIntervalMillis = intervalMillis; + mHasEarlyConstraint = mHasLateConstraint = true; return this; } @@ -314,12 +346,13 @@ public class Task implements Parcelable { * Specify that this task should be delayed by the provided amount of time. * Because it doesn't make sense setting this property on a periodic task, doing so will * throw an {@link java.lang.IllegalArgumentException} when - * {@link android.content.Task.Builder#build()} is called. + * {@link android.app.task.Task.Builder#build()} is called. * @param minLatencyMillis Milliseconds before which this task will not be considered for * execution. */ public Builder setMinimumLatency(long minLatencyMillis) { mMinLatencyMillis = minLatencyMillis; + mHasEarlyConstraint = true; return this; } @@ -328,10 +361,11 @@ public class Task implements Parcelable { * deadline even if other requirements are not met. Because it doesn't make sense setting * this property on a periodic task, doing so will throw an * {@link java.lang.IllegalArgumentException} when - * {@link android.content.Task.Builder#build()} is called. + * {@link android.app.task.Task.Builder#build()} is called. */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { mMaxExecutionDelayMillis = maxExecutionDelayMillis; + mHasLateConstraint = true; return this; } @@ -360,31 +394,18 @@ public class Task implements Parcelable { * @return The task object to hand to the TaskManager. This object is immutable. */ public Task build() { - // Check that extras bundle only contains primitive types. - try { - for (String key : extras.keySet()) { - Object value = extras.get(key); - if (value == null) continue; - if (value instanceof Long) continue; - if (value instanceof Integer) continue; - if (value instanceof Boolean) continue; - if (value instanceof Float) continue; - if (value instanceof Double) continue; - if (value instanceof String) continue; - throw new IllegalArgumentException("Unexpected value type: " - + value.getClass().getName()); - } - } catch (IllegalArgumentException e) { - throw e; - } catch (RuntimeException exc) { - throw new IllegalArgumentException("error unparcelling Bundle", exc); + if (mExtras == null) { + mExtras = Bundle.EMPTY; + } + if (mTaskId < 0) { + throw new IllegalArgumentException("Task id must be greater than 0."); } // Check that a deadline was not set on a periodic task. - if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) { + if (mIsPeriodic && mHasLateConstraint) { throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " + "periodic task."); } - if (mIsPeriodic && (mMinLatencyMillis != 0L)) { + if (mIsPeriodic && mHasEarlyConstraint) { throw new IllegalArgumentException("Can't call setMinimumLatency() on a " + "periodic task"); } diff --git a/core/java/android/content/TaskManager.java b/core/java/android/app/task/TaskManager.java index d28d78a..00f57da 100644 --- a/core/java/android/content/TaskManager.java +++ b/core/java/android/app/task/TaskManager.java @@ -14,14 +14,19 @@ * limitations under the License */ -package android.content; +package android.app.task; import java.util.List; +import android.content.Context; + /** * Class for scheduling various types of tasks with the scheduling framework on the device. * - * Get an instance of this class through {@link Context#getSystemService(String)}. + * <p>You do not + * instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * Context.getSystemService(Context.TASK_SERVICE)}. */ public abstract class TaskManager { /* @@ -29,18 +34,19 @@ public abstract class TaskManager { * if the run-time for your task is too short, or perhaps the system can't resolve the * requisite {@link TaskService} in your package. */ - static final int RESULT_INVALID_PARAMETERS = -1; + public static final int RESULT_FAILURE = 0; /** * Returned from {@link #schedule(Task)} if this application has made too many requests for * work over too short a time. */ // TODO: Determine if this is necessary. - static final int RESULT_OVER_QUOTA = -2; + public static final int RESULT_SUCCESS = 1; - /* - * @param task The task you wish scheduled. See {@link Task#TaskBuilder} for more detail on - * the sorts of tasks you can schedule. - * @return If >0, this int corresponds to the taskId of the successfully scheduled task. + /** + * @param task The task you wish scheduled. See + * {@link android.app.task.Task.Builder Task.Builder} for more detail on the sorts of tasks + * you can schedule. + * @return If >0, this int returns the taskId of the successfully scheduled task. * Otherwise you have to compare the return value to the error codes defined in this class. */ public abstract int schedule(Task task); diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java index 0351082..dacb348 100644 --- a/core/java/android/app/task/TaskParams.java +++ b/core/java/android/app/task/TaskParams.java @@ -47,7 +47,7 @@ public class TaskParams implements Parcelable { /** * @return The extras you passed in when constructing this task with - * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will + * {@link android.app.task.Task.Builder#setExtras(android.os.Bundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. */ public Bundle getExtras() { diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java index ab1a565..8ce4484 100644 --- a/core/java/android/app/task/TaskService.java +++ b/core/java/android/app/task/TaskService.java @@ -28,7 +28,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; /** - * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p> + * <p>Entry point for the callback from the {@link android.app.task.TaskManager}.</p> * <p>This is the base class that handles asynchronous requests that were previously scheduled. You * are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where * you will implement your task logic.</p> @@ -215,9 +215,9 @@ public abstract class TaskService extends Service { * * <p>This will happen if the requirements specified at schedule time are no longer met. For * example you may have requested WiFi with - * {@link android.content.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your + * {@link android.app.task.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your * task was executing the user toggled WiFi. Another example is if you had specified - * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its + * {@link android.app.task.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its * idle maintenance window. You are solely responsible for the behaviour of your application * upon receipt of this message; your app will likely start to misbehave if you ignore it. One * immediate repercussion is that the system will cease holding a wakelock for you.</p> @@ -237,7 +237,7 @@ public abstract class TaskService extends Service { * You can specify post-execution behaviour to the scheduler here with * <code>needsReschedule </code>. This will apply a back-off timer to your task based on * the default, or what was set with - * {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The original + * {@link android.app.task.Task.Builder#setBackoffCriteria(long, int)}. The original * requirements are always honoured even for a backed-off task. Note that a task running in * idle mode will not be backed-off. Instead what will happen is the task will be re-added * to the queue and re-executed within a future idle maintenance window. diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index d3e9089..e5bf7d0 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -317,9 +317,9 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; /** - * Sent to providers after AppWidget state related to the provider has been restored from - * backup. The intent contains information about how to translate AppWidget ids from the - * restored data to their new equivalents. + * Sent to an {@link AppWidgetProvider} after AppWidget state related to that provider has + * been restored from backup. The intent contains information about how to translate AppWidget + * ids from the restored data to their new equivalents. * * <p>The intent will contain the following extras: * @@ -343,7 +343,7 @@ public class AppWidgetManager { * <p class="note">This is a protected intent that can only be sent * by the system. * - * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast + * @see #ACTION_APPWIDGET_HOST_RESTORED */ public static final String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED"; @@ -352,7 +352,7 @@ public class AppWidgetManager { * Sent to widget hosts after AppWidget state related to the host has been restored from * backup. The intent contains information about how to translate AppWidget ids from the * restored data to their new equivalents. If an application maintains multiple separate - * widget hosts instances, it will receive this broadcast separately for each one. + * widget host instances, it will receive this broadcast separately for each one. * * <p>The intent will contain the following extras: * @@ -380,7 +380,7 @@ public class AppWidgetManager { * <p class="note">This is a protected intent that can only be sent * by the system. * - * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast + * @see #ACTION_APPWIDGET_RESTORED */ public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 9e1c995..42c2aeb 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -18,6 +18,8 @@ package android.bluetooth; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.BluetoothLeScanner; import android.content.Context; import android.os.Handler; import android.os.IBinder; diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java deleted file mode 100644 index 2fa5e49..0000000 --- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java +++ /dev/null @@ -1,645 +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.bluetooth; - -import android.annotation.Nullable; -import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Represents Bluetooth LE advertise and scan response data. This could be either the advertisement - * data to be advertised, or the scan record obtained from BLE scans. - * <p> - * The exact bluetooth advertising and scan response data fields and types are defined in Bluetooth - * 4.0 specification, Volume 3, Part C, Section 11 and 18, as well as Supplement to the Bluetooth - * Core Specification Version 4. Currently the following fields are allowed to be set: - * <li>Service UUIDs which identify the bluetooth gatt services running on the device. - * <li>Tx power level which is the transmission power level. - * <li>Service data which is the data associated with a service. - * <li>Manufacturer specific data which is the data associated with a particular manufacturer. - * - * @see BluetoothLeAdvertiser - */ -public final class BluetoothLeAdvertiseScanData { - private static final String TAG = "BluetoothLeAdvertiseScanData"; - - /** - * Bluetooth LE Advertising Data type, the data will be placed in AdvData field of advertising - * packet. - */ - public static final int ADVERTISING_DATA = 0; - /** - * Bluetooth LE scan response data, the data will be placed in ScanRspData field of advertising - * packet. - * <p> - */ - public static final int SCAN_RESPONSE_DATA = 1; - /** - * Scan record parsed from Bluetooth LE scans. The content can contain a concatenation of - * advertising data and scan response data. - */ - public static final int PARSED_SCAN_RECORD = 2; - - /** - * Base data type which contains the common fields for {@link AdvertisementData} and - * {@link ScanRecord}. - */ - public abstract static class AdvertiseBaseData { - - private final int mDataType; - - @Nullable - private final List<ParcelUuid> mServiceUuids; - - private final int mManufacturerId; - @Nullable - private final byte[] mManufacturerSpecificData; - - @Nullable - private final ParcelUuid mServiceDataUuid; - @Nullable - private final byte[] mServiceData; - - private AdvertiseBaseData(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData) { - mDataType = dataType; - mServiceUuids = serviceUuids; - mManufacturerId = manufacturerId; - mManufacturerSpecificData = manufacturerSpecificData; - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - } - - /** - * Returns the type of data, indicating whether the data is advertising data, scan response - * data or scan record. - */ - public int getDataType() { - return mDataType; - } - - /** - * Returns a list of service uuids within the advertisement that are used to identify the - * bluetooth gatt services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth - * SIG. - */ - public int getManufacturerId() { - return mManufacturerId; - } - - /** - * Returns the manufacturer specific data which is the content of manufacturer specific data - * field. The first 2 bytes of the data contain the company id. - */ - public byte[] getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns a 16 bit uuid of the service that the service data is associated with. - */ - public ParcelUuid getServiceDataUuid() { - return mServiceDataUuid; - } - - /** - * Returns service data. The first two bytes should be a 16 bit service uuid associated with - * the service data. - */ - public byte[] getServiceData() { - return mServiceData; - } - - @Override - public String toString() { - return "AdvertiseBaseData [mDataType=" + mDataType + ", mServiceUuids=" + mServiceUuids - + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" - + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" - + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + "]"; - } - } - - /** - * Advertisement data packet for Bluetooth LE advertising. This represents the data to be - * broadcasted in Bluetooth LE advertising. - * <p> - * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to - * be advertised. - * - * @see BluetoothLeAdvertiser - */ - public static final class AdvertisementData extends AdvertiseBaseData implements Parcelable { - - private boolean mIncludeTxPowerLevel; - - /** - * Whether the transmission power level will be included in the advertisement packet. - */ - public boolean getIncludeTxPowerLevel() { - return mIncludeTxPowerLevel; - } - - /** - * Returns a {@link Builder} to build {@link AdvertisementData}. - */ - public static Builder newBuilder() { - return new Builder(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(getDataType()); - List<ParcelUuid> uuids = getServiceUuids(); - if (uuids == null) { - dest.writeInt(0); - } else { - dest.writeInt(uuids.size()); - dest.writeList(uuids); - } - - dest.writeInt(getManufacturerId()); - byte[] manufacturerData = getManufacturerSpecificData(); - if (manufacturerData == null) { - dest.writeInt(0); - } else { - dest.writeInt(manufacturerData.length); - dest.writeByteArray(manufacturerData); - } - - ParcelUuid serviceDataUuid = getServiceDataUuid(); - if (serviceDataUuid == null) { - dest.writeInt(0); - } else { - dest.writeInt(1); - dest.writeParcelable(serviceDataUuid, flags); - byte[] serviceData = getServiceData(); - if (serviceData == null) { - dest.writeInt(0); - } else { - dest.writeInt(serviceData.length); - dest.writeByteArray(serviceData); - } - } - dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); - } - - private AdvertisementData(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData, boolean mIncludeTxPowerLevel) { - super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, - manufacturerSpecificData); - this.mIncludeTxPowerLevel = mIncludeTxPowerLevel; - } - - public static final Parcelable.Creator<AdvertisementData> CREATOR = - new Creator<AdvertisementData>() { - @Override - public AdvertisementData[] newArray(int size) { - return new AdvertisementData[size]; - } - - @Override - public AdvertisementData createFromParcel(Parcel in) { - Builder builder = newBuilder(); - int dataType = in.readInt(); - builder.dataType(dataType); - if (in.readInt() > 0) { - List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); - in.readList(uuids, ParcelUuid.class.getClassLoader()); - builder.serviceUuids(uuids); - } - int manufacturerId = in.readInt(); - int manufacturerDataLength = in.readInt(); - if (manufacturerDataLength > 0) { - byte[] manufacturerData = new byte[manufacturerDataLength]; - in.readByteArray(manufacturerData); - builder.manufacturerData(manufacturerId, manufacturerData); - } - if (in.readInt() == 1) { - ParcelUuid serviceDataUuid = in.readParcelable( - ParcelUuid.class.getClassLoader()); - int serviceDataLength = in.readInt(); - if (serviceDataLength > 0) { - byte[] serviceData = new byte[serviceDataLength]; - in.readByteArray(serviceData); - builder.serviceData(serviceDataUuid, serviceData); - } - } - builder.includeTxPowerLevel(in.readByte() == 1); - return builder.build(); - } - }; - - /** - * Builder for {@link BluetoothLeAdvertiseScanData.AdvertisementData}. Use - * {@link AdvertisementData#newBuilder()} to get an instance of the Builder. - */ - public static final class Builder { - private static final int MAX_ADVERTISING_DATA_BYTES = 31; - // Each fields need one byte for field length and another byte for field type. - private static final int OVERHEAD_BYTES_PER_FIELD = 2; - // Flags field will be set by system. - private static final int FLAGS_FIELD_BYTES = 3; - - private int mDataType; - @Nullable - private List<ParcelUuid> mServiceUuids; - private boolean mIncludeTxPowerLevel; - private int mManufacturerId; - @Nullable - private byte[] mManufacturerSpecificData; - @Nullable - private ParcelUuid mServiceDataUuid; - @Nullable - private byte[] mServiceData; - - /** - * Set data type. - * - * @param dataType Data type, could only be - * {@link BluetoothLeAdvertiseScanData#ADVERTISING_DATA} - * @throws IllegalArgumentException If the {@code dataType} is invalid. - */ - public Builder dataType(int dataType) { - if (mDataType != ADVERTISING_DATA && mDataType != SCAN_RESPONSE_DATA) { - throw new IllegalArgumentException("invalid data type - " + dataType); - } - mDataType = dataType; - return this; - } - - /** - * Set the service uuids. Note the corresponding bluetooth Gatt services need to be - * already added on the device before start BLE advertising. - * - * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or - * 128-bit uuids. - * @throws IllegalArgumentException If the {@code serviceUuids} are null. - */ - public Builder serviceUuids(List<ParcelUuid> serviceUuids) { - if (serviceUuids == null) { - throw new IllegalArgumentException("serivceUuids are null"); - } - mServiceUuids = serviceUuids; - return this; - } - - /** - * Add service data to advertisement. - * - * @param serviceDataUuid A 16 bit uuid of the service data - * @param serviceData Service data - the first two bytes of the service data are the - * service data uuid. - * @throws IllegalArgumentException If the {@code serviceDataUuid} or - * {@code serviceData} is empty. - */ - public Builder serviceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null || serviceData == null) { - throw new IllegalArgumentException( - "serviceDataUuid or serviceDataUuid is null"); - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - return this; - } - - /** - * Set manufacturer id and data. See <a - * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned - * manufacturer identifies</a> for the existing company identifiers. - * - * @param manufacturerId Manufacturer id assigned by Bluetooth SIG. - * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of - * the manufacturer specific data are the manufacturer id. - * @throws IllegalArgumentException If the {@code manufacturerId} is negative or - * {@code manufacturerSpecificData} is null. - */ - public Builder manufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { - if (manufacturerId < 0) { - throw new IllegalArgumentException( - "invalid manufacturerId - " + manufacturerId); - } - if (manufacturerSpecificData == null) { - throw new IllegalArgumentException("manufacturerSpecificData is null"); - } - mManufacturerId = manufacturerId; - mManufacturerSpecificData = manufacturerSpecificData; - return this; - } - - /** - * Whether the transmission power level should be included in the advertising packet. - */ - public Builder includeTxPowerLevel(boolean includeTxPowerLevel) { - mIncludeTxPowerLevel = includeTxPowerLevel; - return this; - } - - /** - * Build the {@link BluetoothLeAdvertiseScanData}. - * - * @throws IllegalArgumentException If the data size is larger than 31 bytes. - */ - public AdvertisementData build() { - if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException( - "advertisement data size is larger than 31 bytes"); - } - return new AdvertisementData(mDataType, - mServiceUuids, - mServiceDataUuid, - mServiceData, mManufacturerId, mManufacturerSpecificData, - mIncludeTxPowerLevel); - } - - // Compute the size of the advertisement data. - private int totalBytes() { - int size = FLAGS_FIELD_BYTES; // flags field is always set. - if (mServiceUuids != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : mServiceUuids) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + - num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - if (mServiceData != null) { - size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length; - } - if (mManufacturerSpecificData != null) { - size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length; - } - if (mIncludeTxPowerLevel) { - size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. - } - return size; - } - } - - } - - /** - * Represents a scan record from Bluetooth LE scan. - */ - public static final class ScanRecord extends AdvertiseBaseData { - // Flags of the advertising data. - private final int mAdvertiseFlags; - - // Transmission power level(in dB). - private final int mTxPowerLevel; - - // Local name of the Bluetooth LE device. - private final String mLocalName; - - /** - * Returns the advertising flags indicating the discoverable mode and capability of the - * device. Returns -1 if the flag field is not set. - */ - public int getAdvertiseFlags() { - return mAdvertiseFlags; - } - - /** - * Returns the transmission power level of the packet in dBm. Returns - * {@link Integer#MIN_VALUE} if the field is not set. This value can be used to calculate - * the path loss of a received packet using the following equation: - * <p> - * <code>pathloss = txPowerLevel - rssi</code> - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * Returns the local name of the BLE device. The is a UTF-8 encoded string. - */ - @Nullable - public String getLocalName() { - return mLocalName; - } - - ScanRecord(int dataType, - List<ParcelUuid> serviceUuids, - ParcelUuid serviceDataUuid, byte[] serviceData, - int manufacturerId, - byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, - String localName) { - super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId, - manufacturerSpecificData); - mLocalName = localName; - mAdvertiseFlags = advertiseFlags; - mTxPowerLevel = txPowerLevel; - } - - /** - * Get a {@link Parser} to parse the scan record byte array into {@link ScanRecord}. - */ - public static Parser getParser() { - return new Parser(); - } - - /** - * A parser class used to parse a Bluetooth LE scan record to - * {@link BluetoothLeAdvertiseScanData}. Note not all field types would be parsed. - */ - public static final class Parser { - private static final String PARSER_TAG = "BluetoothLeAdvertiseDataParser"; - - // The following data type values are assigned by Bluetooth SIG. - // For more details refer to Bluetooth 4.0 specification, Volume 3, Part C, Section 18. - private static final int DATA_TYPE_FLAGS = 0x01; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; - private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; - private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; - private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; - private static final int DATA_TYPE_SERVICE_DATA = 0x16; - private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } - - /** - * Parse scan record to {@link BluetoothLeAdvertiseScanData.ScanRecord}. - * <p> - * The format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11 - * and 18. - * <p> - * All numerical multi-byte entities and values shall use little-endian - * <strong>byte</strong> order. - * - * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. - */ - public ScanRecord parseFromScanRecord(byte[] scanRecord) { - if (scanRecord == null) { - return null; - } - - int currentPos = 0; - int advertiseFlag = -1; - List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); - String localName = null; - int txPowerLevel = Integer.MIN_VALUE; - ParcelUuid serviceDataUuid = null; - byte[] serviceData = null; - int manufacturerId = -1; - byte[] manufacturerSpecificData = null; - - try { - while (currentPos < scanRecord.length) { - // length is unsigned int. - int length = scanRecord[currentPos++] & 0xFF; - if (length == 0) { - break; - } - // Note the length includes the length of the field type itself. - int dataLength = length - 1; - // fieldType is unsigned int. - int fieldType = scanRecord[currentPos++] & 0xFF; - switch (fieldType) { - case DATA_TYPE_FLAGS: - advertiseFlag = scanRecord[currentPos] & 0xFF; - break; - case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, - dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); - break; - case DATA_TYPE_LOCAL_NAME_SHORT: - case DATA_TYPE_LOCAL_NAME_COMPLETE: - localName = new String( - extractBytes(scanRecord, currentPos, dataLength)); - break; - case DATA_TYPE_TX_POWER_LEVEL: - txPowerLevel = scanRecord[currentPos]; - break; - case DATA_TYPE_SERVICE_DATA: - serviceData = extractBytes(scanRecord, currentPos, dataLength); - // The first two bytes of the service data are service data uuid. - int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; - byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, - serviceUuidLength); - serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); - break; - case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: - manufacturerSpecificData = extractBytes(scanRecord, currentPos, - dataLength); - // The first two bytes of the manufacturer specific data are - // manufacturer ids in little endian. - manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) + - (manufacturerSpecificData[0] & 0xFF); - break; - default: - // Just ignore, we don't handle such data type. - break; - } - currentPos += dataLength; - } - - if (serviceUuids.isEmpty()) { - serviceUuids = null; - } - return new ScanRecord(PARSED_SCAN_RECORD, - serviceUuids, serviceDataUuid, serviceData, - manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, - localName); - } catch (IndexOutOfBoundsException e) { - Log.e(PARSER_TAG, - "unable to parse scan record: " + Arrays.toString(scanRecord)); - return null; - } - } - - // Parse service uuids. - private int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, - int uuidLength, List<ParcelUuid> serviceUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, - uuidLength); - serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - } - } - -} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/BluetoothLeAdvertiser.java deleted file mode 100644 index 30c90c4..0000000 --- a/core/java/android/bluetooth/BluetoothLeAdvertiser.java +++ /dev/null @@ -1,615 +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.bluetooth; - -import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData; -import android.os.Handler; -import android.os.Looper; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.RemoteException; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop - * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by - * {@link BluetoothLeAdvertiseScanData.AdvertisementData}. - * <p> - * To get an instance of {@link BluetoothLeAdvertiser}, call the - * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. - * <p> - * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * - * @see BluetoothLeAdvertiseScanData.AdvertisementData - */ -public class BluetoothLeAdvertiser { - - private static final String TAG = "BluetoothLeAdvertiser"; - - /** - * The {@link Settings} provide a way to adjust advertising preferences for each individual - * advertisement. Use {@link Settings.Builder} to create a {@link Settings} instance. - */ - public static final class Settings implements Parcelable { - /** - * Perform Bluetooth LE advertising in low power mode. This is the default and preferred - * advertising mode as it consumes the least power. - */ - public static final int ADVERTISE_MODE_LOW_POWER = 0; - /** - * Perform Bluetooth LE advertising in balanced power mode. This is balanced between - * advertising frequency and power consumption. - */ - public static final int ADVERTISE_MODE_BALANCED = 1; - /** - * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest - * power consumption and should not be used for background continuous advertising. - */ - public static final int ADVERTISE_MODE_LOW_LATENCY = 2; - - /** - * Advertise using the lowest transmission(tx) power level. An app can use low transmission - * power to restrict the visibility range of its advertising packet. - */ - public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; - /** - * Advertise using low tx power level. - */ - public static final int ADVERTISE_TX_POWER_LOW = 1; - /** - * Advertise using medium tx power level. - */ - public static final int ADVERTISE_TX_POWER_MEDIUM = 2; - /** - * Advertise using high tx power level. This is corresponding to largest visibility range of - * the advertising packet. - */ - public static final int ADVERTISE_TX_POWER_HIGH = 3; - - /** - * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.0 - * vol6, part B, section 4.4.2 - Advertising state. - */ - public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; - /** - * Scannable undirected advertise type, as defined in same spec mentioned above. This event - * type allows a scanner to send a scan request asking additional information about the - * advertiser. - */ - public static final int ADVERTISE_TYPE_SCANNABLE = 1; - /** - * Connectable undirected advertising type, as defined in same spec mentioned above. This - * event type allows a scanner to send scan request asking additional information about the - * advertiser. It also allows an initiator to send a connect request for connection. - */ - public static final int ADVERTISE_TYPE_CONNECTABLE = 2; - - private final int mAdvertiseMode; - private final int mAdvertiseTxPowerLevel; - private final int mAdvertiseEventType; - - private Settings(int advertiseMode, int advertiseTxPowerLevel, - int advertiseEventType) { - mAdvertiseMode = advertiseMode; - mAdvertiseTxPowerLevel = advertiseTxPowerLevel; - mAdvertiseEventType = advertiseEventType; - } - - private Settings(Parcel in) { - mAdvertiseMode = in.readInt(); - mAdvertiseTxPowerLevel = in.readInt(); - mAdvertiseEventType = in.readInt(); - } - - /** - * Creates a {@link Builder} to construct a {@link Settings} object. - */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Returns the advertise mode. - */ - public int getMode() { - return mAdvertiseMode; - } - - /** - * Returns the tx power level for advertising. - */ - public int getTxPowerLevel() { - return mAdvertiseTxPowerLevel; - } - - /** - * Returns the advertise event type. - */ - public int getType() { - return mAdvertiseEventType; - } - - @Override - public String toString() { - return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" - + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAdvertiseMode); - dest.writeInt(mAdvertiseTxPowerLevel); - dest.writeInt(mAdvertiseEventType); - } - - public static final Parcelable.Creator<Settings> CREATOR = - new Creator<BluetoothLeAdvertiser.Settings>() { - @Override - public Settings[] newArray(int size) { - return new Settings[size]; - } - - @Override - public Settings createFromParcel(Parcel in) { - return new Settings(in); - } - }; - - /** - * Builder class for {@link BluetoothLeAdvertiser.Settings}. Caller should use - * {@link Settings#newBuilder()} to get an instance of the builder. - */ - public static final class Builder { - private int mMode = ADVERTISE_MODE_LOW_POWER; - private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; - private int mType = ADVERTISE_TYPE_NON_CONNECTABLE; - - // Private constructor, use Settings.newBuilder() get an instance of BUILDER. - private Builder() { - } - - /** - * Set advertise mode to control the advertising power and latency. - * - * @param advertiseMode Bluetooth LE Advertising mode, can only be one of - * {@link Settings#ADVERTISE_MODE_LOW_POWER}, - * {@link Settings#ADVERTISE_MODE_BALANCED}, or - * {@link Settings#ADVERTISE_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the advertiseMode is invalid. - */ - public Builder advertiseMode(int advertiseMode) { - if (advertiseMode < ADVERTISE_MODE_LOW_POWER - || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("unknown mode " + advertiseMode); - } - mMode = advertiseMode; - return this; - } - - /** - * Set advertise tx power level to control the transmission power level for the - * advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one - * of {@link Settings#ADVERTISE_TX_POWER_ULTRA_LOW}, - * {@link Settings#ADVERTISE_TX_POWER_LOW}, - * {@link Settings#ADVERTISE_TX_POWER_MEDIUM} or - * {@link Settings#ADVERTISE_TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder txPowerLevel(int txPowerLevel) { - if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW - || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { - throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set advertise type to control the event type of advertising. - * - * @param type Bluetooth LE Advertising type, can be either - * {@link Settings#ADVERTISE_TYPE_NON_CONNECTABLE}, - * {@link Settings#ADVERTISE_TYPE_SCANNABLE} or - * {@link Settings#ADVERTISE_TYPE_CONNECTABLE}. - * @throws IllegalArgumentException If the {@code type} is invalid. - */ - public Builder type(int type) { - if (type < ADVERTISE_TYPE_NON_CONNECTABLE - || type > ADVERTISE_TYPE_CONNECTABLE) { - throw new IllegalArgumentException("unknown advertise type " + type); - } - mType = type; - return this; - } - - /** - * Build the {@link Settings} object. - */ - public Settings build() { - return new Settings(mMode, mTxPowerLevel, mType); - } - } - } - - /** - * Callback of Bluetooth LE advertising, which is used to deliver operation status for start and - * stop advertising. - */ - public interface AdvertiseCallback { - - /** - * The operation is success. - * - * @hide - */ - public static final int SUCCESS = 0; - /** - * Fails to start advertising as the advertisement data contains services that are not added - * to the local bluetooth Gatt server. - */ - public static final int ADVERTISING_SERVICE_UNKNOWN = 1; - /** - * Fails to start advertising as system runs out of quota for advertisers. - */ - public static final int TOO_MANY_ADVERTISERS = 2; - - /** - * Fails to start advertising as the advertising is already started. - */ - public static final int ADVERTISING_ALREADY_STARTED = 3; - /** - * Fails to stop advertising as the advertising is not started. - */ - public static final int ADVERISING_NOT_STARTED = 4; - - /** - * Operation fails due to bluetooth controller failure. - */ - public static final int CONTROLLER_FAILURE = 5; - - /** - * Callback when advertising operation succeeds. - * - * @param settingsInEffect The actual settings used for advertising, which may be different - * from what the app asks. - */ - public void onSuccess(Settings settingsInEffect); - - /** - * Callback when advertising operation fails. - * - * @param errorCode Error code for failures. - */ - public void onFailure(int errorCode); - } - - private final IBluetoothGatt mBluetoothGatt; - private final Handler mHandler; - private final Map<Settings, AdvertiseCallbackWrapper> - mLeAdvertisers = new HashMap<Settings, AdvertiseCallbackWrapper>(); - - // Package private constructor, use BluetoothAdapter.getLeAdvertiser() instead. - BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) { - mBluetoothGatt = bluetoothGatt; - mHandler = new Handler(Looper.getMainLooper()); - } - - /** - * Bluetooth GATT interface callbacks for advertising. - */ - private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { - private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; - private final AdvertiseCallback mAdvertiseCallback; - private final AdvertisementData mAdvertisement; - private final AdvertisementData mScanResponse; - private final Settings mSettings; - private final IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -1: scan stopped - // >0: registered and scan started - private int mLeHandle; - private boolean isAdvertising = false; - - public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, - AdvertisementData advertiseData, AdvertisementData scanResponse, Settings settings, - IBluetoothGatt bluetoothGatt) { - mAdvertiseCallback = advertiseCallback; - mAdvertisement = advertiseData; - mScanResponse = scanResponse; - mSettings = settings; - mBluetoothGatt = bluetoothGatt; - mLeHandle = 0; - } - - public boolean advertiseStarted() { - boolean started = false; - synchronized (this) { - if (mLeHandle == -1) { - return false; - } - try { - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - started = (mLeHandle > 0 && isAdvertising); - } - return started; - } - - public boolean advertiseStopped() { - synchronized (this) { - try { - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - return !isAdvertising; - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onClientRegistered(int status, int clientIf) { - Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); - synchronized (this) { - if (status == BluetoothGatt.GATT_SUCCESS) { - mLeHandle = clientIf; - try { - mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, - mScanResponse, mSettings); - } catch (RemoteException e) { - Log.e(TAG, "fail to start le advertise: " + e); - mLeHandle = -1; - notifyAll(); - } catch (Exception e) { - Log.e(TAG, "fail to start advertise: " + e.getStackTrace()); - } - } else { - // registration failed - mLeHandle = -1; - notifyAll(); - } - } - } - - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - // no op - } - - @Override - public void onScanResult(String address, int rssi, byte[] advData) { - // no op - } - - @Override - public void onGetService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid) { - // no op - } - - @Override - public void onGetIncludedService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int inclSrvcType, int inclSrvcInstId, - ParcelUuid inclSrvcUuid) { - // no op - } - - @Override - public void onGetCharacteristic(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int charProps) { - // no op - } - - @Override - public void onGetDescriptor(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descUuid) { - // no op - } - - @Override - public void onSearchComplete(String address, int status) { - // no op - } - - @Override - public void onCharacteristicRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, byte[] value) { - // no op - } - - @Override - public void onCharacteristicWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid) { - // no op - } - - @Override - public void onNotify(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - byte[] value) { - // no op - } - - @Override - public void onDescriptorRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid, byte[] value) { - // no op - } - - @Override - public void onDescriptorWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid) { - // no op - } - - @Override - public void onExecuteWrite(String address, int status) { - // no op - } - - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - // no op - } - - @Override - public void onAdvertiseStateChange(int advertiseState, int status) { - // no op - } - - @Override - public void onMultiAdvertiseCallback(int status) { - synchronized (this) { - if (status == 0) { - isAdvertising = !isAdvertising; - if (!isAdvertising) { - try { - mBluetoothGatt.unregisterClient(mLeHandle); - mLeHandle = -1; - } catch (RemoteException e) { - Log.e(TAG, "remote exception when unregistering", e); - } - } - mAdvertiseCallback.onSuccess(null); - } else { - mAdvertiseCallback.onFailure(status); - } - notifyAll(); - } - - } - - /** - * Callback reporting LE ATT MTU. - * - * @hide - */ - public void onConfigureMTU(String address, int mtu, int status) { - // no op - } - } - - /** - * Start Bluetooth LE Advertising. - * - * @param settings {@link Settings} for Bluetooth LE advertising. - * @param advertiseData {@link AdvertisementData} to be advertised. - * @param callback {@link AdvertiseCallback} for advertising status. - */ - public void startAdvertising(Settings settings, - AdvertisementData advertiseData, final AdvertiseCallback callback) { - startAdvertising(settings, advertiseData, null, callback); - } - - /** - * Start Bluetooth LE Advertising. - * @param settings {@link Settings} for Bluetooth LE advertising. - * @param advertiseData {@link AdvertisementData} to be advertised in advertisement packet. - * @param scanResponse {@link AdvertisementData} for scan response. - * @param callback {@link AdvertiseCallback} for advertising status. - */ - public void startAdvertising(Settings settings, - AdvertisementData advertiseData, AdvertisementData scanResponse, - final AdvertiseCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - if (mLeAdvertisers.containsKey(settings)) { - postCallbackFailure(callback, AdvertiseCallback.ADVERTISING_ALREADY_STARTED); - return; - } - AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, - scanResponse, settings, mBluetoothGatt); - UUID uuid = UUID.randomUUID(); - try { - mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); - if (wrapper.advertiseStarted()) { - mLeAdvertisers.put(settings, wrapper); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to stop advertising", e); - } - } - - /** - * Stop Bluetooth LE advertising. Returns immediately, the operation status will be delivered - * through the {@code callback}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * - * @param settings {@link Settings} used to start Bluetooth LE advertising. - * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. - */ - public void stopAdvertising(final Settings settings, final AdvertiseCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(settings); - if (wrapper == null) { - postCallbackFailure(callback, AdvertiseCallback.ADVERISING_NOT_STARTED); - return; - } - try { - mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle); - if (wrapper.advertiseStopped()) { - mLeAdvertisers.remove(settings); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to stop advertising", e); - } - } - - private void postCallbackFailure(final AdvertiseCallback callback, final int error) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onFailure(error); - } - }); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeScanner.java b/core/java/android/bluetooth/BluetoothLeScanner.java deleted file mode 100644 index ed3188b..0000000 --- a/core/java/android/bluetooth/BluetoothLeScanner.java +++ /dev/null @@ -1,759 +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.bluetooth; - -import android.annotation.Nullable; -import android.os.Handler; -import android.os.Looper; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Log; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * This class provides methods to perform scan related operations for Bluetooth LE devices. An - * application can scan for a particular type of BLE devices using {@link BluetoothLeScanFilter}. It - * can also request different types of callbacks for delivering the result. - * <p> - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of - * {@link BluetoothLeScanner}. - * <p> - * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * - * @see BluetoothLeScanFilter - */ -public class BluetoothLeScanner { - - private static final String TAG = "BluetoothLeScanner"; - private static final boolean DBG = true; - - /** - * Settings for Bluetooth LE scan. - */ - public static final class Settings implements Parcelable { - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes - * the least power. - */ - public static final int SCAN_MODE_LOW_POWER = 0; - /** - * Perform Bluetooth LE scan in balanced power mode. - */ - public static final int SCAN_MODE_BALANCED = 1; - /** - * Scan using highest duty cycle. It's recommended only using this mode when the application - * is running in foreground. - */ - public static final int SCAN_MODE_LOW_LATENCY = 2; - - /** - * Callback each time when a bluetooth advertisement is found. - */ - public static final int CALLBACK_TYPE_ON_UPDATE = 0; - /** - * Callback when a bluetooth advertisement is found for the first time. - */ - public static final int CALLBACK_TYPE_ON_FOUND = 1; - /** - * Callback when a bluetooth advertisement is found for the first time, then lost. - */ - public static final int CALLBACK_TYPE_ON_LOST = 2; - - /** - * Full scan result which contains device mac address, rssi, advertising and scan response - * and scan timestamp. - */ - public static final int SCAN_RESULT_TYPE_FULL = 0; - /** - * Truncated scan result which contains device mac address, rssi and scan timestamp. Note - * it's possible for an app to get more scan results that it asks if there are multiple apps - * using this type. TODO: decide whether we could unhide this setting. - * - * @hide - */ - public static final int SCAN_RESULT_TYPE_TRUNCATED = 1; - - // Bluetooth LE scan mode. - private int mScanMode; - - // Bluetooth LE scan callback type - private int mCallbackType; - - // Bluetooth LE scan result type - private int mScanResultType; - - // Time of delay for reporting the scan result - private long mReportDelayMicros; - - public int getScanMode() { - return mScanMode; - } - - public int getCallbackType() { - return mCallbackType; - } - - public int getScanResultType() { - return mScanResultType; - } - - /** - * Returns report delay timestamp based on the device clock. - */ - public long getReportDelayMicros() { - return mReportDelayMicros; - } - - /** - * Creates a new {@link Builder} to build {@link Settings} object. - */ - public static Builder newBuilder() { - return new Builder(); - } - - private Settings(int scanMode, int callbackType, int scanResultType, - long reportDelayMicros) { - mScanMode = scanMode; - mCallbackType = callbackType; - mScanResultType = scanResultType; - mReportDelayMicros = reportDelayMicros; - } - - private Settings(Parcel in) { - mScanMode = in.readInt(); - mCallbackType = in.readInt(); - mScanResultType = in.readInt(); - mReportDelayMicros = in.readLong(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mScanMode); - dest.writeInt(mCallbackType); - dest.writeInt(mScanResultType); - dest.writeLong(mReportDelayMicros); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<Settings> CREATOR = new Creator<Settings>() { - @Override - public Settings[] newArray(int size) { - return new Settings[size]; - } - - @Override - public Settings createFromParcel(Parcel in) { - return new Settings(in); - } - }; - - /** - * Builder for {@link BluetoothLeScanner.Settings}. - */ - public static class Builder { - private int mScanMode = SCAN_MODE_LOW_POWER; - private int mCallbackType = CALLBACK_TYPE_ON_UPDATE; - private int mScanResultType = SCAN_RESULT_TYPE_FULL; - private long mReportDelayMicros = 0; - - // Hidden constructor. - private Builder() { - } - - /** - * Set scan mode for Bluetooth LE scan. - * - * @param scanMode The scan mode can be one of {@link Settings#SCAN_MODE_LOW_POWER}, - * {@link Settings#SCAN_MODE_BALANCED} or - * {@link Settings#SCAN_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the {@code scanMode} is invalid. - */ - public Builder scanMode(int scanMode) { - if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("invalid scan mode " + scanMode); - } - mScanMode = scanMode; - return this; - } - - /** - * Set callback type for Bluetooth LE scan. - * - * @param callbackType The callback type for the scan. Can be either one of - * {@link Settings#CALLBACK_TYPE_ON_UPDATE}, - * {@link Settings#CALLBACK_TYPE_ON_FOUND} or - * {@link Settings#CALLBACK_TYPE_ON_LOST}. - * @throws IllegalArgumentException If the {@code callbackType} is invalid. - */ - public Builder callbackType(int callbackType) { - if (callbackType < CALLBACK_TYPE_ON_UPDATE - || callbackType > CALLBACK_TYPE_ON_LOST) { - throw new IllegalArgumentException("invalid callback type - " + callbackType); - } - mCallbackType = callbackType; - return this; - } - - /** - * Set scan result type for Bluetooth LE scan. - * - * @param scanResultType Type for scan result, could be either - * {@link Settings#SCAN_RESULT_TYPE_FULL} or - * {@link Settings#SCAN_RESULT_TYPE_TRUNCATED}. - * @throws IllegalArgumentException If the {@code scanResultType} is invalid. - * @hide - */ - public Builder scanResultType(int scanResultType) { - if (scanResultType < SCAN_RESULT_TYPE_FULL - || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) { - throw new IllegalArgumentException( - "invalid scanResultType - " + scanResultType); - } - mScanResultType = scanResultType; - return this; - } - - /** - * Set report delay timestamp for Bluetooth LE scan. - */ - public Builder reportDelayMicros(long reportDelayMicros) { - mReportDelayMicros = reportDelayMicros; - return this; - } - - /** - * Build {@link Settings}. - */ - public Settings build() { - return new Settings(mScanMode, mCallbackType, mScanResultType, mReportDelayMicros); - } - } - } - - /** - * ScanResult for Bluetooth LE scan. - */ - public static final class ScanResult implements Parcelable { - // Remote bluetooth device. - private BluetoothDevice mDevice; - - // Scan record, including advertising data and scan response data. - private byte[] mScanRecord; - - // Received signal strength. - private int mRssi; - - // Device timestamp when the result was last seen. - private long mTimestampMicros; - - // Constructor of scan result. - public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, long timestampMicros) { - mDevice = device; - mScanRecord = scanRecord; - mRssi = rssi; - mTimestampMicros = timestampMicros; - } - - private ScanResult(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - if (mDevice != null) { - dest.writeInt(1); - mDevice.writeToParcel(dest, flags); - } else { - dest.writeInt(0); - } - if (mScanRecord != null) { - dest.writeInt(1); - dest.writeByteArray(mScanRecord); - } else { - dest.writeInt(0); - } - dest.writeInt(mRssi); - dest.writeLong(mTimestampMicros); - } - - private void readFromParcel(Parcel in) { - if (in.readInt() == 1) { - mDevice = BluetoothDevice.CREATOR.createFromParcel(in); - } - if (in.readInt() == 1) { - mScanRecord = in.createByteArray(); - } - mRssi = in.readInt(); - mTimestampMicros = in.readLong(); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Returns the remote bluetooth device identified by the bluetooth device address. - */ - @Nullable - public BluetoothDevice getDevice() { - return mDevice; - } - - @Nullable /** - * Returns the scan record, which can be a combination of advertisement and scan response. - */ - public byte[] getScanRecord() { - return mScanRecord; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 127]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampMicros() { - return mTimestampMicros; - } - - @Override - public int hashCode() { - return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampMicros); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanResult other = (ScanResult) obj; - return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && - Objects.deepEquals(mScanRecord, other.mScanRecord) - && (mTimestampMicros == other.mTimestampMicros); - } - - @Override - public String toString() { - return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord=" - + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampMicros=" - + mTimestampMicros + '}'; - } - - public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { - @Override - public ScanResult createFromParcel(Parcel source) { - return new ScanResult(source); - } - - @Override - public ScanResult[] newArray(int size) { - return new ScanResult[size]; - } - }; - - } - - /** - * Callback of Bluetooth LE scans. The results of the scans will be delivered through the - * callbacks. - */ - public interface ScanCallback { - /** - * Callback when any BLE beacon is found. - * - * @param result A Bluetooth LE scan result. - */ - public void onDeviceUpdate(ScanResult result); - - /** - * Callback when the BLE beacon is found for the first time. - * - * @param result The Bluetooth LE scan result when the onFound event is triggered. - */ - public void onDeviceFound(ScanResult result); - - /** - * Callback when the BLE device was lost. Note a device has to be "found" before it's lost. - * - * @param device The Bluetooth device that is lost. - */ - public void onDeviceLost(BluetoothDevice device); - - /** - * Callback when batch results are delivered. - * - * @param results List of scan results that are previously scanned. - */ - public void onBatchScanResults(List<ScanResult> results); - - /** - * Fails to start scan as BLE scan with the same settings is already started by the app. - */ - public static final int SCAN_ALREADY_STARTED = 1; - /** - * Fails to start scan as app cannot be registered. - */ - public static final int APPLICATION_REGISTRATION_FAILED = 2; - /** - * Fails to start scan due to gatt service failure. - */ - public static final int GATT_SERVICE_FAILURE = 3; - /** - * Fails to start scan due to controller failure. - */ - public static final int CONTROLLER_FAILURE = 4; - - /** - * Callback when scan failed. - */ - public void onScanFailed(int errorCode); - } - - private final IBluetoothGatt mBluetoothGatt; - private final Handler mHandler; - private final Map<Settings, BleScanCallbackWrapper> mLeScanClients; - - BluetoothLeScanner(IBluetoothGatt bluetoothGatt) { - mBluetoothGatt = bluetoothGatt; - mHandler = new Handler(Looper.getMainLooper()); - mLeScanClients = new HashMap<Settings, BleScanCallbackWrapper>(); - } - - /** - * Bluetooth GATT interface callbacks - */ - private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub { - private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; - - private final ScanCallback mScanCallback; - private final List<BluetoothLeScanFilter> mFilters; - private Settings mSettings; - private IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -1: scan stopped - // > 0: registered and scan started - private int mLeHandle; - - public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, - List<BluetoothLeScanFilter> filters, Settings settings, ScanCallback scanCallback) { - mBluetoothGatt = bluetoothGatt; - mFilters = filters; - mSettings = settings; - mScanCallback = scanCallback; - mLeHandle = 0; - } - - public boolean scanStarted() { - synchronized (this) { - if (mLeHandle == -1) { - return false; - } - try { - wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); - } catch (InterruptedException e) { - Log.e(TAG, "Callback reg wait interrupted: " + e); - } - } - return mLeHandle > 0; - } - - public void stopLeScan() { - synchronized (this) { - if (mLeHandle <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); - return; - } - try { - mBluetoothGatt.stopScan(mLeHandle, false); - mBluetoothGatt.unregisterClient(mLeHandle); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop scan and unregister" + e); - } - mLeHandle = -1; - notifyAll(); - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onClientRegistered(int status, int clientIf) { - Log.d(TAG, "onClientRegistered() - status=" + status + - " clientIf=" + clientIf); - - synchronized (this) { - if (mLeHandle == -1) { - if (DBG) - Log.d(TAG, "onClientRegistered LE scan canceled"); - } - - if (status == BluetoothGatt.GATT_SUCCESS) { - mLeHandle = clientIf; - try { - mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters); - } catch (RemoteException e) { - Log.e(TAG, "fail to start le scan: " + e); - mLeHandle = -1; - } - } else { - // registration failed - mLeHandle = -1; - } - notifyAll(); - } - } - - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - // no op - } - - /** - * Callback reporting an LE scan result. - * - * @hide - */ - @Override - public void onScanResult(String address, int rssi, byte[] advData) { - if (DBG) - Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi); - - // Check null in case the scan has been stopped - synchronized (this) { - if (mLeHandle <= 0) - return; - } - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - address); - long scanMicros = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos()); - ScanResult result = new ScanResult(device, advData, rssi, - scanMicros); - mScanCallback.onDeviceUpdate(result); - } - - @Override - public void onGetService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid) { - // no op - } - - @Override - public void onGetIncludedService(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int inclSrvcType, int inclSrvcInstId, - ParcelUuid inclSrvcUuid) { - // no op - } - - @Override - public void onGetCharacteristic(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int charProps) { - // no op - } - - @Override - public void onGetDescriptor(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descUuid) { - // no op - } - - @Override - public void onSearchComplete(String address, int status) { - // no op - } - - @Override - public void onCharacteristicRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, byte[] value) { - // no op - } - - @Override - public void onCharacteristicWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid) { - // no op - } - - @Override - public void onNotify(String address, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - byte[] value) { - // no op - } - - @Override - public void onDescriptorRead(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid, byte[] value) { - // no op - } - - @Override - public void onDescriptorWrite(String address, int status, int srvcType, - int srvcInstId, ParcelUuid srvcUuid, - int charInstId, ParcelUuid charUuid, - int descInstId, ParcelUuid descrUuid) { - // no op - } - - @Override - public void onExecuteWrite(String address, int status) { - // no op - } - - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - // no op - } - - @Override - public void onAdvertiseStateChange(int advertiseState, int status) { - // no op - } - - @Override - public void onMultiAdvertiseCallback(int status) { - // no op - } - - @Override - public void onConfigureMTU(String address, int mtu, int status) { - // no op - } - } - - /** - * Scan Bluetooth LE scan. The scan results will be delivered through {@code callback}. - * - * @param filters {@link BluetoothLeScanFilter}s for finding exact BLE devices. - * @param settings Settings for ble scan. - * @param callback Callback when scan results are delivered. - * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. - */ - public void startScan(List<BluetoothLeScanFilter> filters, Settings settings, - final ScanCallback callback) { - if (settings == null || callback == null) { - throw new IllegalArgumentException("settings or callback is null"); - } - synchronized (mLeScanClients) { - if (mLeScanClients.get(settings) != null) { - postCallbackError(callback, ScanCallback.SCAN_ALREADY_STARTED); - return; - } - BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters, - settings, callback); - try { - UUID uuid = UUID.randomUUID(); - mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); - if (wrapper.scanStarted()) { - mLeScanClients.put(settings, wrapper); - } else { - postCallbackError(callback, ScanCallback.APPLICATION_REGISTRATION_FAILED); - return; - } - } catch (RemoteException e) { - Log.e(TAG, "GATT service exception when starting scan", e); - postCallbackError(callback, ScanCallback.GATT_SERVICE_FAILURE); - } - } - } - - private void postCallbackError(final ScanCallback callback, final int errorCode) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onScanFailed(errorCode); - } - }); - } - - /** - * Stop Bluetooth LE scan. - * - * @param settings The same settings as used in {@link #startScan}, which is used to identify - * the BLE scan. - */ - public void stopScan(Settings settings) { - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.remove(settings); - if (wrapper == null) { - return; - } - wrapper.stopLeScan(); - } - } - - /** - * Returns available storage size for batch scan results. It's recommended not to use batch scan - * if available storage size is small (less than 1k bytes, for instance). - * - * @hide TODO: unhide when batching is supported in stack. - */ - public int getAvailableBatchStorageSizeBytes() { - throw new UnsupportedOperationException("not impelemented"); - } - - /** - * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results - * batched on bluetooth controller. - * - * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one - * used to start scan. - * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will - * get batch scan callback if the batch scan buffer is flushed. - * @return Batch Scan results. - * @hide TODO: unhide when batching is supported in stack. - */ - public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) { - throw new UnsupportedOperationException("not impelemented"); - } - -} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 1574090..d898060 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -104,6 +104,12 @@ public interface BluetoothProfile { public static final int MAP = 9; /** + * A2DP Sink Profile + * @hide + */ + public static final int A2DP_SINK = 10; + + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile * @hide diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index ceed52b..00a0750 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -17,10 +17,10 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeAdvertiseScanData; -import android.bluetooth.BluetoothLeAdvertiser; -import android.bluetooth.BluetoothLeScanFilter; -import android.bluetooth.BluetoothLeScanner; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisementData; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; import android.os.ParcelUuid; import android.bluetooth.IBluetoothGattCallback; @@ -38,13 +38,12 @@ interface IBluetoothGatt { void startScanWithUuidsScanParam(in int appIf, in boolean isServer, in ParcelUuid[] ids, int scanWindow, int scanInterval); void startScanWithFilters(in int appIf, in boolean isServer, - in BluetoothLeScanner.Settings settings, - in List<BluetoothLeScanFilter> filters); + in ScanSettings settings, in List<ScanFilter> filters); void stopScan(in int appIf, in boolean isServer); void startMultiAdvertising(in int appIf, - in BluetoothLeAdvertiseScanData.AdvertisementData advertiseData, - in BluetoothLeAdvertiseScanData.AdvertisementData scanResponse, - in BluetoothLeAdvertiser.Settings settings); + in AdvertisementData advertiseData, + in AdvertisementData scanResponse, + in AdvertiseSettings settings); void stopMultiAdvertising(in int appIf); void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback); void unregisterClient(in int clientIf); diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java new file mode 100644 index 0000000..f1334c2 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertiseCallback.java @@ -0,0 +1,68 @@ +/* + * 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.bluetooth.le; + +/** + * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status. + */ +public abstract class AdvertiseCallback { + + /** + * The operation is success. + * + * @hide + */ + public static final int SUCCESS = 0; + /** + * Fails to start advertising as the advertisement data contains services that are not added to + * the local bluetooth GATT server. + */ + public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1; + /** + * Fails to start advertising as system runs out of quota for advertisers. + */ + public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; + + /** + * Fails to start advertising as the advertising is already started. + */ + public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; + /** + * Fails to stop advertising as the advertising is not started. + */ + public static final int ADVERTISE_FAILED_NOT_STARTED = 4; + + /** + * Operation fails due to bluetooth controller failure. + */ + public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5; + + /** + * Callback when advertising operation succeeds. + * + * @param settingsInEffect The actual settings used for advertising, which may be different from + * what the app asks. + */ + public abstract void onSuccess(AdvertiseSettings settingsInEffect); + + /** + * Callback when advertising operation fails. + * + * @param errorCode Error code for failures. + */ + public abstract void onFailure(int errorCode); +} diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl b/core/java/android/bluetooth/le/AdvertiseSettings.aidl index 86ee06d..9f47d74 100644 --- a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl +++ b/core/java/android/bluetooth/le/AdvertiseSettings.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeScanFilter; +parcelable AdvertiseSettings;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java new file mode 100644 index 0000000..87d0346 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertiseSettings.java @@ -0,0 +1,218 @@ +/* + * 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.bluetooth.le; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each + * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance. + */ +public final class AdvertiseSettings implements Parcelable { + /** + * Perform Bluetooth LE advertising in low power mode. This is the default and preferred + * advertising mode as it consumes the least power. + */ + public static final int ADVERTISE_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising + * frequency and power consumption. + */ + public static final int ADVERTISE_MODE_BALANCED = 1; + /** + * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power + * consumption and should not be used for background continuous advertising. + */ + public static final int ADVERTISE_MODE_LOW_LATENCY = 2; + + /** + * Advertise using the lowest transmission(tx) power level. An app can use low transmission + * power to restrict the visibility range of its advertising packet. + */ + public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; + /** + * Advertise using low tx power level. + */ + public static final int ADVERTISE_TX_POWER_LOW = 1; + /** + * Advertise using medium tx power level. + */ + public static final int ADVERTISE_TX_POWER_MEDIUM = 2; + /** + * Advertise using high tx power level. This is corresponding to largest visibility range of the + * advertising packet. + */ + public static final int ADVERTISE_TX_POWER_HIGH = 3; + + /** + * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1 + * vol6, part B, section 4.4.2 - Advertising state. + */ + public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; + /** + * Scannable undirected advertise type, as defined in same spec mentioned above. This event type + * allows a scanner to send a scan request asking additional information about the advertiser. + */ + public static final int ADVERTISE_TYPE_SCANNABLE = 1; + /** + * Connectable undirected advertising type, as defined in same spec mentioned above. This event + * type allows a scanner to send scan request asking additional information about the + * advertiser. It also allows an initiator to send a connect request for connection. + */ + public static final int ADVERTISE_TYPE_CONNECTABLE = 2; + + private final int mAdvertiseMode; + private final int mAdvertiseTxPowerLevel; + private final int mAdvertiseEventType; + + private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel, + int advertiseEventType) { + mAdvertiseMode = advertiseMode; + mAdvertiseTxPowerLevel = advertiseTxPowerLevel; + mAdvertiseEventType = advertiseEventType; + } + + private AdvertiseSettings(Parcel in) { + mAdvertiseMode = in.readInt(); + mAdvertiseTxPowerLevel = in.readInt(); + mAdvertiseEventType = in.readInt(); + } + + /** + * Returns the advertise mode. + */ + public int getMode() { + return mAdvertiseMode; + } + + /** + * Returns the tx power level for advertising. + */ + public int getTxPowerLevel() { + return mAdvertiseTxPowerLevel; + } + + /** + * Returns the advertise event type. + */ + public int getType() { + return mAdvertiseEventType; + } + + @Override + public String toString() { + return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel=" + + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mAdvertiseMode); + dest.writeInt(mAdvertiseTxPowerLevel); + dest.writeInt(mAdvertiseEventType); + } + + public static final Parcelable.Creator<AdvertiseSettings> CREATOR = + new Creator<AdvertiseSettings>() { + @Override + public AdvertiseSettings[] newArray(int size) { + return new AdvertiseSettings[size]; + } + + @Override + public AdvertiseSettings createFromParcel(Parcel in) { + return new AdvertiseSettings(in); + } + }; + + /** + * Builder class for {@link AdvertiseSettings}. + */ + public static final class Builder { + private int mMode = ADVERTISE_MODE_LOW_POWER; + private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; + private int mType = ADVERTISE_TYPE_NON_CONNECTABLE; + + /** + * Set advertise mode to control the advertising power and latency. + * + * @param advertiseMode Bluetooth LE Advertising mode, can only be one of + * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_POWER}, + * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, or + * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the advertiseMode is invalid. + */ + public Builder setAdvertiseMode(int advertiseMode) { + if (advertiseMode < ADVERTISE_MODE_LOW_POWER + || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("unknown mode " + advertiseMode); + } + mMode = advertiseMode; + return this; + } + + /** + * Set advertise tx power level to control the transmission power level for the advertising. + * + * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW}, + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_LOW}, + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} or + * {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}. + * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. + */ + public Builder setTxPowerLevel(int txPowerLevel) { + if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW + || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { + throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); + } + mTxPowerLevel = txPowerLevel; + return this; + } + + /** + * Set advertise type to control the event type of advertising. + * + * @param type Bluetooth LE Advertising type, can be either + * {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE}, + * {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or + * {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}. + * @throws IllegalArgumentException If the {@code type} is invalid. + */ + public Builder setType(int type) { + if (type < ADVERTISE_TYPE_NON_CONNECTABLE + || type > ADVERTISE_TYPE_CONNECTABLE) { + throw new IllegalArgumentException("unknown advertise type " + type); + } + mType = type; + return this; + } + + /** + * Build the {@link AdvertiseSettings} object. + */ + public AdvertiseSettings build() { + return new AdvertiseSettings(mMode, mTxPowerLevel, mType); + } + } +} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl b/core/java/android/bluetooth/le/AdvertisementData.aidl index 3108610..3da1321 100644 --- a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl +++ b/core/java/android/bluetooth/le/AdvertisementData.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeAdvertiser.Settings;
\ No newline at end of file +parcelable AdvertisementData;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertisementData.java new file mode 100644 index 0000000..c587204 --- /dev/null +++ b/core/java/android/bluetooth/le/AdvertisementData.java @@ -0,0 +1,344 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothUuid; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Advertisement data packet for Bluetooth LE advertising. This represents the data to be + * broadcasted in Bluetooth LE advertising as well as the scan response for active scan. + * <p> + * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be + * advertised. + * + * @see BluetoothLeAdvertiser + * @see ScanRecord + */ +public final class AdvertisementData implements Parcelable { + + @Nullable + private final List<ParcelUuid> mServiceUuids; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerSpecificData; + + @Nullable + private final ParcelUuid mServiceDataUuid; + @Nullable + private final byte[] mServiceData; + + private boolean mIncludeTxPowerLevel; + + private AdvertisementData(List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, boolean includeTxPowerLevel) { + mServiceUuids = serviceUuids; + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + mIncludeTxPowerLevel = includeTxPowerLevel; + } + + /** + * Returns a list of service uuids within the advertisement that are used to identify the + * bluetooth GATT services. + */ + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** + * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth + * SIG. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + /** + * Returns the manufacturer specific data which is the content of manufacturer specific data + * field. The first 2 bytes of the data contain the company id. + */ + public byte[] getManufacturerSpecificData() { + return mManufacturerSpecificData; + } + + /** + * Returns a 16 bit uuid of the service that the service data is associated with. + */ + public ParcelUuid getServiceDataUuid() { + return mServiceDataUuid; + } + + /** + * Returns service data. The first two bytes should be a 16 bit service uuid associated with the + * service data. + */ + public byte[] getServiceData() { + return mServiceData; + } + + /** + * Whether the transmission power level will be included in the advertisement packet. + */ + public boolean getIncludeTxPowerLevel() { + return mIncludeTxPowerLevel; + } + + @Override + public String toString() { + return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId=" + + mManufacturerId + ", mManufacturerSpecificData=" + + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" + + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mServiceUuids == null) { + dest.writeInt(0); + } else { + dest.writeInt(mServiceUuids.size()); + dest.writeList(mServiceUuids); + } + + dest.writeInt(mManufacturerId); + if (mManufacturerSpecificData == null) { + dest.writeInt(0); + } else { + dest.writeInt(mManufacturerSpecificData.length); + dest.writeByteArray(mManufacturerSpecificData); + } + + if (mServiceDataUuid == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(mServiceDataUuid, flags); + if (mServiceData == null) { + dest.writeInt(0); + } else { + dest.writeInt(mServiceData.length); + dest.writeByteArray(mServiceData); + } + } + dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); + } + + public static final Parcelable.Creator<AdvertisementData> CREATOR = + new Creator<AdvertisementData>() { + @Override + public AdvertisementData[] newArray(int size) { + return new AdvertisementData[size]; + } + + @Override + public AdvertisementData createFromParcel(Parcel in) { + Builder builder = new Builder(); + if (in.readInt() > 0) { + List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); + in.readList(uuids, ParcelUuid.class.getClassLoader()); + builder.setServiceUuids(uuids); + } + int manufacturerId = in.readInt(); + int manufacturerDataLength = in.readInt(); + if (manufacturerDataLength > 0) { + byte[] manufacturerData = new byte[manufacturerDataLength]; + in.readByteArray(manufacturerData); + builder.setManufacturerData(manufacturerId, manufacturerData); + } + if (in.readInt() == 1) { + ParcelUuid serviceDataUuid = in.readParcelable( + ParcelUuid.class.getClassLoader()); + int serviceDataLength = in.readInt(); + if (serviceDataLength > 0) { + byte[] serviceData = new byte[serviceDataLength]; + in.readByteArray(serviceData); + builder.setServiceData(serviceDataUuid, serviceData); + } + } + builder.setIncludeTxPowerLevel(in.readByte() == 1); + return builder.build(); + } + }; + + /** + * Builder for {@link AdvertisementData}. + */ + public static final class Builder { + private static final int MAX_ADVERTISING_DATA_BYTES = 31; + // Each fields need one byte for field length and another byte for field type. + private static final int OVERHEAD_BYTES_PER_FIELD = 2; + // Flags field will be set by system. + private static final int FLAGS_FIELD_BYTES = 3; + + @Nullable + private List<ParcelUuid> mServiceUuids; + private boolean mIncludeTxPowerLevel; + private int mManufacturerId; + @Nullable + private byte[] mManufacturerSpecificData; + @Nullable + private ParcelUuid mServiceDataUuid; + @Nullable + private byte[] mServiceData; + + /** + * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already + * added on the device before start BLE advertising. + * + * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit + * uuids. + * @throws IllegalArgumentException If the {@code serviceUuids} are null. + */ + public Builder setServiceUuids(List<ParcelUuid> serviceUuids) { + if (serviceUuids == null) { + throw new IllegalArgumentException("serivceUuids are null"); + } + mServiceUuids = serviceUuids; + return this; + } + + /** + * Add service data to advertisement. + * + * @param serviceDataUuid A 16 bit uuid of the service data + * @param serviceData Service data - the first two bytes of the service data are the service + * data uuid. + * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is + * empty. + */ + public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { + if (serviceDataUuid == null || serviceData == null) { + throw new IllegalArgumentException( + "serviceDataUuid or serviceDataUuid is null"); + } + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + return this; + } + + /** + * Set manufacturer id and data. See <a + * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned + * manufacturer identifies</a> for the existing company identifiers. + * + * @param manufacturerId Manufacturer id assigned by Bluetooth SIG. + * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the + * manufacturer specific data are the manufacturer id. + * @throws IllegalArgumentException If the {@code manufacturerId} is negative or + * {@code manufacturerSpecificData} is null. + */ + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { + if (manufacturerId < 0) { + throw new IllegalArgumentException( + "invalid manufacturerId - " + manufacturerId); + } + if (manufacturerSpecificData == null) { + throw new IllegalArgumentException("manufacturerSpecificData is null"); + } + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + return this; + } + + /** + * Whether the transmission power level should be included in the advertising packet. + */ + public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) { + mIncludeTxPowerLevel = includeTxPowerLevel; + return this; + } + + /** + * Build the {@link AdvertisementData}. + * + * @throws IllegalArgumentException If the data size is larger than 31 bytes. + */ + public AdvertisementData build() { + if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) { + throw new IllegalArgumentException( + "advertisement data size is larger than 31 bytes"); + } + return new AdvertisementData(mServiceUuids, + mServiceDataUuid, + mServiceData, mManufacturerId, mManufacturerSpecificData, + mIncludeTxPowerLevel); + } + + // Compute the size of the advertisement data. + private int totalBytes() { + int size = FLAGS_FIELD_BYTES; // flags field is always set. + if (mServiceUuids != null) { + int num16BitUuids = 0; + int num32BitUuids = 0; + int num128BitUuids = 0; + for (ParcelUuid uuid : mServiceUuids) { + if (BluetoothUuid.is16BitUuid(uuid)) { + ++num16BitUuids; + } else if (BluetoothUuid.is32BitUuid(uuid)) { + ++num32BitUuids; + } else { + ++num128BitUuids; + } + } + // 16 bit service uuids are grouped into one field when doing advertising. + if (num16BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; + } + // 32 bit service uuids are grouped into one field when doing advertising. + if (num32BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; + } + // 128 bit service uuids are grouped into one field when doing advertising. + if (num128BitUuids != 0) { + size += OVERHEAD_BYTES_PER_FIELD + + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; + } + } + if (mServiceData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length; + } + if (mManufacturerSpecificData != null) { + size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length; + } + if (mIncludeTxPowerLevel) { + size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. + } + return size; + } + } +} diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java new file mode 100644 index 0000000..ed43407 --- /dev/null +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -0,0 +1,368 @@ +/* + * 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.bluetooth.le; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothGattCallback; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop + * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by + * {@link AdvertisementData}. + * <p> + * To get an instance of {@link BluetoothLeAdvertiser}, call the + * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. + * <p> + * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see AdvertisementData + */ +public final class BluetoothLeAdvertiser { + + private static final String TAG = "BluetoothLeAdvertiser"; + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> + mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); + + /** + * Use BluetoothAdapter.getLeAdvertiser() instead. + * + * @param bluetoothGatt + * @hide + */ + public BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + } + + /** + * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the + * operation succeeds. Returns immediately, the operation status are delivered through + * {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param settings Settings for Bluetooth LE advertising. + * @param advertiseData Advertisement data to be broadcasted. + * @param callback Callback for advertising status. + */ + public void startAdvertising(AdvertiseSettings settings, + AdvertisementData advertiseData, final AdvertiseCallback callback) { + startAdvertising(settings, advertiseData, null, callback); + } + + /** + * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the + * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends + * active scan request. Method returns immediately, the operation status are delivered through + * {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param settings Settings for Bluetooth LE advertising. + * @param advertiseData Advertisement data to be advertised in advertisement packet. + * @param scanResponse Scan response associated with the advertisement data. + * @param callback Callback for advertising status. + */ + public void startAdvertising(AdvertiseSettings settings, + AdvertisementData advertiseData, AdvertisementData scanResponse, + final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + if (mLeAdvertisers.containsKey(callback)) { + postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); + return; + } + AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, + scanResponse, settings, mBluetoothGatt); + UUID uuid = UUID.randomUUID(); + try { + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.advertiseStarted()) { + mLeAdvertisers.put(callback, wrapper); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + /** + * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in + * {@link BluetoothLeAdvertiser#startAdvertising}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. + */ + public void stopAdvertising(final AdvertiseCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); + if (wrapper == null) { + postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED); + return; + } + try { + mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle); + if (wrapper.advertiseStopped()) { + mLeAdvertisers.remove(callback); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to stop advertising", e); + } + } + + /** + * Bluetooth GATT interface callbacks for advertising. + */ + private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; + private final AdvertiseCallback mAdvertiseCallback; + private final AdvertisementData mAdvertisement; + private final AdvertisementData mScanResponse; + private final AdvertiseSettings mSettings; + private final IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // >0: registered and scan started + private int mLeHandle; + private boolean isAdvertising = false; + + public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, + AdvertisementData advertiseData, AdvertisementData scanResponse, + AdvertiseSettings settings, + IBluetoothGatt bluetoothGatt) { + mAdvertiseCallback = advertiseCallback; + mAdvertisement = advertiseData; + mScanResponse = scanResponse; + mSettings = settings; + mBluetoothGatt = bluetoothGatt; + mLeHandle = 0; + } + + public boolean advertiseStarted() { + boolean started = false; + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: ", e); + } + started = (mLeHandle > 0 && isAdvertising); + } + return started; + } + + public boolean advertiseStopped() { + synchronized (this) { + try { + wait(LE_CALLBACK_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + return !isAdvertising; + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); + synchronized (this) { + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, + mScanResponse, mSettings); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le advertise: " + e); + mLeHandle = -1; + notifyAll(); + } catch (Exception e) { + Log.e(TAG, "fail to start advertise: " + e.getStackTrace()); + } + } else { + // registration failed + mLeHandle = -1; + notifyAll(); + } + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + // no op + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + synchronized (this) { + if (status == 0) { + isAdvertising = !isAdvertising; + if (!isAdvertising) { + try { + mBluetoothGatt.unregisterClient(mLeHandle); + mLeHandle = -1; + } catch (RemoteException e) { + Log.e(TAG, "remote exception when unregistering", e); + } + } + mAdvertiseCallback.onSuccess(null); + } else { + mAdvertiseCallback.onFailure(status); + } + notifyAll(); + } + + } + + /** + * Callback reporting LE ATT MTU. + * + * @hide + */ + @Override + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + private void postCallbackFailure(final AdvertiseCallback callback, final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onFailure(error); + } + }); + } +} diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java new file mode 100644 index 0000000..4c6346c --- /dev/null +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -0,0 +1,371 @@ +/* + * 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.bluetooth.le; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothGattCallback; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * This class provides methods to perform scan related operations for Bluetooth LE devices. An + * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also + * request different types of callbacks for delivering the result. + * <p> + * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of + * {@link BluetoothLeScanner}. + * <p> + * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @see ScanFilter + */ +public final class BluetoothLeScanner { + + private static final String TAG = "BluetoothLeScanner"; + private static final boolean DBG = true; + + private final IBluetoothGatt mBluetoothGatt; + private final Handler mHandler; + private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; + + /** + * @hide + */ + public BluetoothLeScanner(IBluetoothGatt bluetoothGatt) { + mBluetoothGatt = bluetoothGatt; + mHandler = new Handler(Looper.getMainLooper()); + mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); + } + + /** + * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param filters {@link ScanFilter}s for finding exact BLE devices. + * @param settings Settings for ble scan. + * @param callback Callback when scan results are delivered. + * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. + */ + public void startScan(List<ScanFilter> filters, ScanSettings settings, + final ScanCallback callback) { + if (settings == null || callback == null) { + throw new IllegalArgumentException("settings or callback is null"); + } + synchronized (mLeScanClients) { + if (mLeScanClients.containsKey(callback)) { + postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); + return; + } + BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters, + settings, callback); + try { + UUID uuid = UUID.randomUUID(); + mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (wrapper.scanStarted()) { + mLeScanClients.put(callback, wrapper); + } else { + postCallbackError(callback, + ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "GATT service exception when starting scan", e); + postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE); + } + } + } + + /** + * Stops an ongoing Bluetooth LE scan. + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param callback + */ + public void stopScan(ScanCallback callback) { + synchronized (mLeScanClients) { + BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); + if (wrapper == null) { + return; + } + wrapper.stopLeScan(); + } + } + + /** + * Returns available storage size for batch scan results. It's recommended not to use batch scan + * if available storage size is small (less than 1k bytes, for instance). + * + * @hide TODO: unhide when batching is supported in stack. + */ + public int getAvailableBatchStorageSizeBytes() { + throw new UnsupportedOperationException("not impelemented"); + } + + /** + * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results + * batched on bluetooth controller. + * + * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one + * used to start scan. + * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will + * get batch scan callback if the batch scan buffer is flushed. + * @return Batch Scan results. + * @hide TODO: unhide when batching is supported in stack. + */ + public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) { + throw new UnsupportedOperationException("not impelemented"); + } + + /** + * Bluetooth GATT interface callbacks + */ + private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub { + private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; + + private final ScanCallback mScanCallback; + private final List<ScanFilter> mFilters; + private ScanSettings mSettings; + private IBluetoothGatt mBluetoothGatt; + + // mLeHandle 0: not registered + // -1: scan stopped + // > 0: registered and scan started + private int mLeHandle; + + public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, + List<ScanFilter> filters, ScanSettings settings, + ScanCallback scanCallback) { + mBluetoothGatt = bluetoothGatt; + mFilters = filters; + mSettings = settings; + mScanCallback = scanCallback; + mLeHandle = 0; + } + + public boolean scanStarted() { + synchronized (this) { + if (mLeHandle == -1) { + return false; + } + try { + wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Callback reg wait interrupted: " + e); + } + } + return mLeHandle > 0; + } + + public void stopLeScan() { + synchronized (this) { + if (mLeHandle <= 0) { + Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); + return; + } + try { + mBluetoothGatt.stopScan(mLeHandle, false); + mBluetoothGatt.unregisterClient(mLeHandle); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop scan and unregister" + e); + } + mLeHandle = -1; + notifyAll(); + } + } + + /** + * Application interface registered - app is ready to go + */ + @Override + public void onClientRegistered(int status, int clientIf) { + Log.d(TAG, "onClientRegistered() - status=" + status + + " clientIf=" + clientIf); + + synchronized (this) { + if (mLeHandle == -1) { + if (DBG) + Log.d(TAG, "onClientRegistered LE scan canceled"); + } + + if (status == BluetoothGatt.GATT_SUCCESS) { + mLeHandle = clientIf; + try { + mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters); + } catch (RemoteException e) { + Log.e(TAG, "fail to start le scan: " + e); + mLeHandle = -1; + } + } else { + // registration failed + mLeHandle = -1; + } + notifyAll(); + } + } + + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + // no op + } + + /** + * Callback reporting an LE scan result. + * + * @hide + */ + @Override + public void onScanResult(String address, int rssi, byte[] advData) { + if (DBG) + Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi); + + // Check null in case the scan has been stopped + synchronized (this) { + if (mLeHandle <= 0) + return; + } + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + address); + long scanNanos = SystemClock.elapsedRealtimeNanos(); + ScanResult result = new ScanResult(device, advData, rssi, + scanNanos); + mScanCallback.onAdvertisementUpdate(result); + } + + @Override + public void onGetService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid) { + // no op + } + + @Override + public void onGetIncludedService(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int inclSrvcType, int inclSrvcInstId, + ParcelUuid inclSrvcUuid) { + // no op + } + + @Override + public void onGetCharacteristic(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int charProps) { + // no op + } + + @Override + public void onGetDescriptor(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descUuid) { + // no op + } + + @Override + public void onSearchComplete(String address, int status) { + // no op + } + + @Override + public void onCharacteristicRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, byte[] value) { + // no op + } + + @Override + public void onCharacteristicWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid) { + // no op + } + + @Override + public void onNotify(String address, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + byte[] value) { + // no op + } + + @Override + public void onDescriptorRead(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid, byte[] value) { + // no op + } + + @Override + public void onDescriptorWrite(String address, int status, int srvcType, + int srvcInstId, ParcelUuid srvcUuid, + int charInstId, ParcelUuid charUuid, + int descInstId, ParcelUuid descrUuid) { + // no op + } + + @Override + public void onExecuteWrite(String address, int status) { + // no op + } + + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + // no op + } + + @Override + public void onAdvertiseStateChange(int advertiseState, int status) { + // no op + } + + @Override + public void onMultiAdvertiseCallback(int status) { + // no op + } + + @Override + public void onConfigureMTU(String address, int mtu, int status) { + // no op + } + } + + private void postCallbackError(final ScanCallback callback, final int errorCode) { + mHandler.post(new Runnable() { + @Override + public void run() { + callback.onScanFailed(errorCode); + } + }); + } +} diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java new file mode 100644 index 0000000..50ebf50 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanCallback.java @@ -0,0 +1,79 @@ +/* + * 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.bluetooth.le; + +import java.util.List; + +/** + * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks. + */ +public abstract class ScanCallback { + + /** + * Fails to start scan as BLE scan with the same settings is already started by the app. + */ + public static final int SCAN_FAILED_ALREADY_STARTED = 1; + /** + * Fails to start scan as app cannot be registered. + */ + public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; + /** + * Fails to start scan due to gatt service failure. + */ + public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3; + /** + * Fails to start scan due to controller failure. + */ + public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4; + + /** + * Callback when a BLE advertisement is found. + * + * @param result A Bluetooth LE scan result. + */ + public abstract void onAdvertisementUpdate(ScanResult result); + + /** + * Callback when the BLE advertisement is found for the first time. + * + * @param result The Bluetooth LE scan result when the onFound event is triggered. + * @hide + */ + public abstract void onAdvertisementFound(ScanResult result); + + /** + * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's + * lost. + * + * @param result The Bluetooth scan result that was last found. + * @hide + */ + public abstract void onAdvertisementLost(ScanResult result); + + /** + * Callback when batch results are delivered. + * + * @param results List of scan results that are previously scanned. + * @hide + */ + public abstract void onBatchScanResults(List<ScanResult> results); + + /** + * Callback when scan failed. + */ + public abstract void onScanFailed(int errorCode); +} diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl b/core/java/android/bluetooth/le/ScanFilter.aidl index 4aa8881..4cecfe6 100644 --- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl +++ b/core/java/android/bluetooth/le/ScanFilter.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -parcelable BluetoothLeAdvertiseScanData.AdvertisementData;
\ No newline at end of file +parcelable ScanFilter; diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java index 2ed85ba..c2e316b 100644 --- a/core/java/android/bluetooth/BluetoothLeScanFilter.java +++ b/core/java/android/bluetooth/le/ScanFilter.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; import android.annotation.Nullable; -import android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; @@ -29,8 +29,7 @@ import java.util.Objects; import java.util.UUID; /** - * {@link BluetoothLeScanFilter} abstracts different scan filters across Bluetooth Advertisement - * packet fields. + * {@link ScanFilter} abstracts different scan filters across Bluetooth Advertisement packet fields. * <p> * Current filtering on the following fields are supported: * <li>Service UUIDs which identify the bluetooth gatt services running on the device. @@ -40,10 +39,10 @@ import java.util.UUID; * <li>Service data which is the data associated with a service. * <li>Manufacturer specific data which is the data associated with a particular manufacturer. * - * @see BluetoothLeAdvertiseScanData.ScanRecord + * @see ScanRecord * @see BluetoothLeScanner */ -public final class BluetoothLeScanFilter implements Parcelable { +public final class ScanFilter implements Parcelable { @Nullable private final String mLocalName; @@ -70,7 +69,7 @@ public final class BluetoothLeScanFilter implements Parcelable { private final int mMinRssi; private final int mMaxRssi; - private BluetoothLeScanFilter(String name, String macAddress, ParcelUuid uuid, + private ScanFilter(String name, String macAddress, ParcelUuid uuid, ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, int minRssi, int maxRssi) { @@ -105,88 +104,93 @@ public final class BluetoothLeScanFilter implements Parcelable { dest.writeInt(mServiceUuid == null ? 0 : 1); if (mServiceUuid != null) { dest.writeParcelable(mServiceUuid, flags); - } - dest.writeInt(mServiceUuidMask == null ? 0 : 1); - if (mServiceUuidMask != null) { - dest.writeParcelable(mServiceUuidMask, flags); + dest.writeInt(mServiceUuidMask == null ? 0 : 1); + if (mServiceUuidMask != null) { + dest.writeParcelable(mServiceUuidMask, flags); + } } dest.writeInt(mServiceData == null ? 0 : mServiceData.length); if (mServiceData != null) { dest.writeByteArray(mServiceData); - } - dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length); - if (mServiceDataMask != null) { - dest.writeByteArray(mServiceDataMask); + dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length); + if (mServiceDataMask != null) { + dest.writeByteArray(mServiceDataMask); + } } dest.writeInt(mManufacturerId); dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length); if (mManufacturerData != null) { dest.writeByteArray(mManufacturerData); - } - dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length); - if (mManufacturerDataMask != null) { - dest.writeByteArray(mManufacturerDataMask); + dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length); + if (mManufacturerDataMask != null) { + dest.writeByteArray(mManufacturerDataMask); + } } dest.writeInt(mMinRssi); dest.writeInt(mMaxRssi); } /** - * A {@link android.os.Parcelable.Creator} to create {@link BluetoothLeScanFilter} form parcel. + * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} form parcel. */ - public static final Creator<BluetoothLeScanFilter> - CREATOR = new Creator<BluetoothLeScanFilter>() { + public static final Creator<ScanFilter> + CREATOR = new Creator<ScanFilter>() { @Override - public BluetoothLeScanFilter[] newArray(int size) { - return new BluetoothLeScanFilter[size]; + public ScanFilter[] newArray(int size) { + return new ScanFilter[size]; } @Override - public BluetoothLeScanFilter createFromParcel(Parcel in) { - Builder builder = newBuilder(); + public ScanFilter createFromParcel(Parcel in) { + Builder builder = new Builder(); if (in.readInt() == 1) { - builder.name(in.readString()); + builder.setName(in.readString()); } if (in.readInt() == 1) { - builder.macAddress(in.readString()); + builder.setMacAddress(in.readString()); } if (in.readInt() == 1) { ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); - builder.serviceUuid(uuid); - } - if (in.readInt() == 1) { - ParcelUuid uuidMask = in.readParcelable(ParcelUuid.class.getClassLoader()); - builder.serviceUuidMask(uuidMask); + builder.setServiceUuid(uuid); + if (in.readInt() == 1) { + ParcelUuid uuidMask = in.readParcelable( + ParcelUuid.class.getClassLoader()); + builder.setServiceUuid(uuid, uuidMask); + } } + int serviceDataLength = in.readInt(); if (serviceDataLength > 0) { byte[] serviceData = new byte[serviceDataLength]; in.readByteArray(serviceData); - builder.serviceData(serviceData); - } - int serviceDataMaskLength = in.readInt(); - if (serviceDataMaskLength > 0) { - byte[] serviceDataMask = new byte[serviceDataMaskLength]; - in.readByteArray(serviceDataMask); - builder.serviceDataMask(serviceDataMask); + builder.setServiceData(serviceData); + int serviceDataMaskLength = in.readInt(); + if (serviceDataMaskLength > 0) { + byte[] serviceDataMask = new byte[serviceDataMaskLength]; + in.readByteArray(serviceDataMask); + builder.setServiceData(serviceData, serviceDataMask); + } } + int manufacturerId = in.readInt(); int manufacturerDataLength = in.readInt(); if (manufacturerDataLength > 0) { byte[] manufacturerData = new byte[manufacturerDataLength]; in.readByteArray(manufacturerData); - builder.manufacturerData(manufacturerId, manufacturerData); - } - int manufacturerDataMaskLength = in.readInt(); - if (manufacturerDataMaskLength > 0) { - byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; - in.readByteArray(manufacturerDataMask); - builder.manufacturerDataMask(manufacturerDataMask); + builder.setManufacturerData(manufacturerId, manufacturerData); + int manufacturerDataMaskLength = in.readInt(); + if (manufacturerDataMaskLength > 0) { + byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; + in.readByteArray(manufacturerDataMask); + builder.setManufacturerData(manufacturerId, manufacturerData, + manufacturerDataMask); + } } + int minRssi = in.readInt(); int maxRssi = in.readInt(); - builder.rssiRange(minRssi, maxRssi); + builder.setRssiRange(minRssi, maxRssi); return builder.build(); } }; @@ -199,9 +203,10 @@ public final class BluetoothLeScanFilter implements Parcelable { return mLocalName; } - @Nullable /** - * Returns the filter set on the service uuid. - */ + /** + * Returns the filter set on the service uuid. + */ + @Nullable public ParcelUuid getServiceUuid() { return mServiceUuid; } @@ -277,7 +282,7 @@ public final class BluetoothLeScanFilter implements Parcelable { } byte[] scanRecordBytes = scanResult.getScanRecord(); - ScanRecord scanRecord = ScanRecord.getParser().parseFromScanRecord(scanRecordBytes); + ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordBytes); // Scan record is null but there exist filters on it. if (scanRecord == null @@ -386,13 +391,13 @@ public final class BluetoothLeScanFilter implements Parcelable { if (obj == null || getClass() != obj.getClass()) { return false; } - BluetoothLeScanFilter other = (BluetoothLeScanFilter) obj; + ScanFilter other = (ScanFilter) obj; return Objects.equals(mLocalName, other.mLocalName) && Objects.equals(mMacAddress, other.mMacAddress) && - mManufacturerId == other.mManufacturerId && + mManufacturerId == other.mManufacturerId && Objects.deepEquals(mManufacturerData, other.mManufacturerData) && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) && - mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi && + mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi && Objects.deepEquals(mServiceData, other.mServiceData) && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) && Objects.equals(mServiceUuid, other.mServiceUuid) && @@ -400,17 +405,9 @@ public final class BluetoothLeScanFilter implements Parcelable { } /** - * Returns the {@link Builder} for {@link BluetoothLeScanFilter}. + * Builder class for {@link ScanFilter}. */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Builder class for {@link BluetoothLeScanFilter}. Use - * {@link BluetoothLeScanFilter#newBuilder()} to get an instance of the {@link Builder}. - */ - public static class Builder { + public static final class Builder { private String mLocalName; private String mMacAddress; @@ -428,27 +425,23 @@ public final class BluetoothLeScanFilter implements Parcelable { private int mMinRssi = Integer.MIN_VALUE; private int mMaxRssi = Integer.MAX_VALUE; - // Private constructor, use BluetoothLeScanFilter.newBuilder instead. - private Builder() { - } - /** - * Set filtering on local name. + * Set filter on local name. */ - public Builder name(String localName) { + public Builder setName(String localName) { mLocalName = localName; return this; } /** - * Set filtering on device mac address. + * Set filter on device mac address. * * @param macAddress The device mac address for the filter. It needs to be in the format of * "01:02:03:AB:CD:EF". The mac address can be validated using * {@link BluetoothAdapter#checkBluetoothAddress}. * @throws IllegalArgumentException If the {@code macAddress} is invalid. */ - public Builder macAddress(String macAddress) { + public Builder setMacAddress(String macAddress) { if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) { throw new IllegalArgumentException("invalid mac address " + macAddress); } @@ -457,68 +450,115 @@ public final class BluetoothLeScanFilter implements Parcelable { } /** - * Set filtering on service uuid. + * Set filter on service uuid. */ - public Builder serviceUuid(ParcelUuid serviceUuid) { + public Builder setServiceUuid(ParcelUuid serviceUuid) { mServiceUuid = serviceUuid; + mUuidMask = null; // clear uuid mask return this; } /** - * Set partial uuid filter. The {@code uuidMask} is the bit mask for the {@code uuid} set - * through {@link #serviceUuid(ParcelUuid)} method. Set any bit in the mask to 1 to indicate - * a match is needed for the bit in {@code serviceUuid}, and 0 to ignore that bit. - * <p> - * The length of {@code uuidMask} must be the same as {@code serviceUuid}. + * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the + * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the + * bit in {@code serviceUuid}, and 0 to ignore that bit. + * + * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but + * {@code uuidMask} is not {@code null}. */ - public Builder serviceUuidMask(ParcelUuid uuidMask) { + public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { + if (mUuidMask != null && mServiceUuid == null) { + throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); + } + mServiceUuid = serviceUuid; mUuidMask = uuidMask; return this; } /** - * Set service data filter. + * Set filtering on service data. */ - public Builder serviceData(byte[] serviceData) { + public Builder setServiceData(byte[] serviceData) { mServiceData = serviceData; + mServiceDataMask = null; // clear service data mask return this; } /** - * Set partial service data filter bit mask. For any bit in the mask, set it to 1 if it - * needs to match the one in service data, otherwise set it to 0 to ignore that bit. + * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to + * match the one in service data, otherwise set it to 0 to ignore that bit. * <p> - * The {@code serviceDataMask} must have the same length of the {@code serviceData} set - * through {@link #serviceData(byte[])}. + * The {@code serviceDataMask} must have the same length of the {@code serviceData}. + * + * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while + * {@code serviceData} is not or {@code serviceDataMask} and {@code serviceData} + * has different length. */ - public Builder serviceDataMask(byte[] serviceDataMask) { + public Builder setServiceData(byte[] serviceData, byte[] serviceDataMask) { + if (mServiceDataMask != null) { + if (mServiceData == null) { + throw new IllegalArgumentException( + "serviceData is null while serviceDataMask is not null"); + } + // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two + // byte array need to be the same. + if (mServiceData.length != mServiceDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for service data and service data mask"); + } + } + mServiceData = serviceData; mServiceDataMask = serviceDataMask; return this; } /** - * Set manufacturerId and manufacturerData. A negative manufacturerId is considered as - * invalid id. + * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. * <p> * Note the first two bytes of the {@code manufacturerData} is the manufacturerId. + * + * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. */ - public Builder manufacturerData(int manufacturerId, byte[] manufacturerData) { + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { if (manufacturerData != null && manufacturerId < 0) { throw new IllegalArgumentException("invalid manufacture id"); } mManufacturerId = manufacturerId; mManufacturerData = manufacturerData; + mManufacturerDataMask = null; // clear manufacturer data mask return this; } /** - * Set partial manufacture data filter bit mask. For any bit in the mask, set it the 1 if it + * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it * needs to match the one in manufacturer data, otherwise set it to 0. * <p> - * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData} - * set through {@link #manufacturerData(int, byte[])}. + * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. + * + * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or + * {@code manufacturerData} is null while {@code manufacturerDataMask} is not, + * or {@code manufacturerData} and {@code manufacturerDataMask} have different + * length. */ - public Builder manufacturerDataMask(byte[] manufacturerDataMask) { + public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, + byte[] manufacturerDataMask) { + if (manufacturerData != null && manufacturerId < 0) { + throw new IllegalArgumentException("invalid manufacture id"); + } + if (mManufacturerDataMask != null) { + if (mManufacturerData == null) { + throw new IllegalArgumentException( + "manufacturerData is null while manufacturerDataMask is not null"); + } + // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths + // of the two byte array need to be the same. + if (mManufacturerData.length != mManufacturerDataMask.length) { + throw new IllegalArgumentException( + "size mismatch for manufacturerData and manufacturerDataMask"); + } + } + mManufacturerId = manufacturerId; + mManufacturerData = manufacturerData; mManufacturerDataMask = manufacturerDataMask; return this; } @@ -527,48 +567,19 @@ public final class BluetoothLeScanFilter implements Parcelable { * Set the desired rssi range for the filter. A scan result with rssi in the range of * [minRssi, maxRssi] will be consider as a match. */ - public Builder rssiRange(int minRssi, int maxRssi) { + public Builder setRssiRange(int minRssi, int maxRssi) { mMinRssi = minRssi; mMaxRssi = maxRssi; return this; } /** - * Build {@link BluetoothLeScanFilter}. + * Build {@link ScanFilter}. * * @throws IllegalArgumentException If the filter cannot be built. */ - public BluetoothLeScanFilter build() { - if (mUuidMask != null && mServiceUuid == null) { - throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); - } - - if (mServiceDataMask != null) { - if (mServiceData == null) { - throw new IllegalArgumentException( - "serviceData is null while serviceDataMask is not null"); - } - // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two - // byte array need to be the same. - if (mServiceData.length != mServiceDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for service data and service data mask"); - } - } - - if (mManufacturerDataMask != null) { - if (mManufacturerData == null) { - throw new IllegalArgumentException( - "manufacturerData is null while manufacturerDataMask is not null"); - } - // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths - // of the two byte array need to be the same. - if (mManufacturerData.length != mManufacturerDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for manufacturerData and manufacturerDataMask"); - } - } - return new BluetoothLeScanFilter(mLocalName, mMacAddress, + public ScanFilter build() { + return new ScanFilter(mLocalName, mMacAddress, mServiceUuid, mUuidMask, mServiceData, mServiceDataMask, mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi); diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java new file mode 100644 index 0000000..bd7304b --- /dev/null +++ b/core/java/android/bluetooth/le/ScanRecord.java @@ -0,0 +1,278 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents a scan record from Bluetooth LE scan. + */ +public final class ScanRecord { + + private static final String TAG = "ScanRecord"; + + // The following data type values are assigned by Bluetooth SIG. + // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. + private static final int DATA_TYPE_FLAGS = 0x01; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; + private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; + private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; + private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; + private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; + private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; + private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; + private static final int DATA_TYPE_SERVICE_DATA = 0x16; + private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; + + // Flags of the advertising data. + private final int mAdvertiseFlags; + + @Nullable + private final List<ParcelUuid> mServiceUuids; + + private final int mManufacturerId; + @Nullable + private final byte[] mManufacturerSpecificData; + + @Nullable + private final ParcelUuid mServiceDataUuid; + @Nullable + private final byte[] mServiceData; + + // Transmission power level(in dB). + private final int mTxPowerLevel; + + // Local name of the Bluetooth LE device. + private final String mLocalName; + + /** + * Returns the advertising flags indicating the discoverable mode and capability of the device. + * Returns -1 if the flag field is not set. + */ + public int getAdvertiseFlags() { + return mAdvertiseFlags; + } + + /** + * Returns a list of service uuids within the advertisement that are used to identify the + * bluetooth gatt services. + */ + public List<ParcelUuid> getServiceUuids() { + return mServiceUuids; + } + + /** + * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth + * SIG. + */ + public int getManufacturerId() { + return mManufacturerId; + } + + /** + * Returns the manufacturer specific data which is the content of manufacturer specific data + * field. The first 2 bytes of the data contain the company id. + */ + public byte[] getManufacturerSpecificData() { + return mManufacturerSpecificData; + } + + /** + * Returns a 16 bit uuid of the service that the service data is associated with. + */ + public ParcelUuid getServiceDataUuid() { + return mServiceDataUuid; + } + + /** + * Returns service data. The first two bytes should be a 16 bit service uuid associated with the + * service data. + */ + public byte[] getServiceData() { + return mServiceData; + } + + /** + * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} + * if the field is not set. This value can be used to calculate the path loss of a received + * packet using the following equation: + * <p> + * <code>pathloss = txPowerLevel - rssi</code> + */ + public int getTxPowerLevel() { + return mTxPowerLevel; + } + + /** + * Returns the local name of the BLE device. The is a UTF-8 encoded string. + */ + @Nullable + public String getLocalName() { + return mLocalName; + } + + private ScanRecord(List<ParcelUuid> serviceUuids, + ParcelUuid serviceDataUuid, byte[] serviceData, + int manufacturerId, + byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, + String localName) { + mServiceUuids = serviceUuids; + mManufacturerId = manufacturerId; + mManufacturerSpecificData = manufacturerSpecificData; + mServiceDataUuid = serviceDataUuid; + mServiceData = serviceData; + mLocalName = localName; + mAdvertiseFlags = advertiseFlags; + mTxPowerLevel = txPowerLevel; + } + + @Override + public String toString() { + return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids + + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" + + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" + + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]"; + } + + /** + * Parse scan record bytes to {@link ScanRecord}. + * <p> + * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. + * <p> + * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> + * order. + * + * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. + */ + public static ScanRecord parseFromBytes(byte[] scanRecord) { + if (scanRecord == null) { + return null; + } + + int currentPos = 0; + int advertiseFlag = -1; + List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); + String localName = null; + int txPowerLevel = Integer.MIN_VALUE; + ParcelUuid serviceDataUuid = null; + byte[] serviceData = null; + int manufacturerId = -1; + byte[] manufacturerSpecificData = null; + + try { + while (currentPos < scanRecord.length) { + // length is unsigned int. + int length = scanRecord[currentPos++] & 0xFF; + if (length == 0) { + break; + } + // Note the length includes the length of the field type itself. + int dataLength = length - 1; + // fieldType is unsigned int. + int fieldType = scanRecord[currentPos++] & 0xFF; + switch (fieldType) { + case DATA_TYPE_FLAGS: + advertiseFlag = scanRecord[currentPos] & 0xFF; + break; + case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, + dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); + break; + case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: + case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: + parseServiceUuid(scanRecord, currentPos, dataLength, + BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); + break; + case DATA_TYPE_LOCAL_NAME_SHORT: + case DATA_TYPE_LOCAL_NAME_COMPLETE: + localName = new String( + extractBytes(scanRecord, currentPos, dataLength)); + break; + case DATA_TYPE_TX_POWER_LEVEL: + txPowerLevel = scanRecord[currentPos]; + break; + case DATA_TYPE_SERVICE_DATA: + serviceData = extractBytes(scanRecord, currentPos, dataLength); + // The first two bytes of the service data are service data uuid. + int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; + byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, + serviceUuidLength); + serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); + break; + case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: + manufacturerSpecificData = extractBytes(scanRecord, currentPos, + dataLength); + // The first two bytes of the manufacturer specific data are + // manufacturer ids in little endian. + manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) + + (manufacturerSpecificData[0] & 0xFF); + break; + default: + // Just ignore, we don't handle such data type. + break; + } + currentPos += dataLength; + } + + if (serviceUuids.isEmpty()) { + serviceUuids = null; + } + return new ScanRecord(serviceUuids, serviceDataUuid, serviceData, + manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, + localName); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); + return null; + } + } + + // Parse service uuids. + private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, + int uuidLength, List<ParcelUuid> serviceUuids) { + while (dataLength > 0) { + byte[] uuidBytes = extractBytes(scanRecord, currentPos, + uuidLength); + serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); + dataLength -= uuidLength; + currentPos += uuidLength; + } + return currentPos; + } + + // Helper method to extract bytes from byte array. + private static byte[] extractBytes(byte[] scanRecord, int start, int length) { + byte[] bytes = new byte[length]; + System.arraycopy(scanRecord, start, bytes, 0, length); + return bytes; + } +} diff --git a/core/java/android/bluetooth/le/ScanResult.aidl b/core/java/android/bluetooth/le/ScanResult.aidl new file mode 100644 index 0000000..3943035 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanResult.aidl @@ -0,0 +1,19 @@ +/* + * 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.bluetooth.le; + +parcelable ScanResult;
\ No newline at end of file diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java new file mode 100644 index 0000000..7e6e8f8 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanResult.java @@ -0,0 +1,162 @@ +/* + * 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.bluetooth.le; + +import android.annotation.Nullable; +import android.bluetooth.BluetoothDevice; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * ScanResult for Bluetooth LE scan. + */ +public final class ScanResult implements Parcelable { + // Remote bluetooth device. + private BluetoothDevice mDevice; + + // Scan record, including advertising data and scan response data. + private byte[] mScanRecord; + + // Received signal strength. + private int mRssi; + + // Device timestamp when the result was last seen. + private long mTimestampNanos; + + /** + * Constructor of scan result. + * + * @hide + */ + public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, + long timestampNanos) { + mDevice = device; + mScanRecord = scanRecord; + mRssi = rssi; + mTimestampNanos = timestampNanos; + } + + private ScanResult(Parcel in) { + readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mDevice != null) { + dest.writeInt(1); + mDevice.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } + if (mScanRecord != null) { + dest.writeInt(1); + dest.writeByteArray(mScanRecord); + } else { + dest.writeInt(0); + } + dest.writeInt(mRssi); + dest.writeLong(mTimestampNanos); + } + + private void readFromParcel(Parcel in) { + if (in.readInt() == 1) { + mDevice = BluetoothDevice.CREATOR.createFromParcel(in); + } + if (in.readInt() == 1) { + mScanRecord = in.createByteArray(); + } + mRssi = in.readInt(); + mTimestampNanos = in.readLong(); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the remote bluetooth device identified by the bluetooth device address. + */ + @Nullable + public BluetoothDevice getDevice() { + return mDevice; + } + + /** + * Returns the scan record, which can be a combination of advertisement and scan response. + */ + @Nullable + public byte[] getScanRecord() { + return mScanRecord; + } + + /** + * Returns the received signal strength in dBm. The valid range is [-127, 127]. + */ + public int getRssi() { + return mRssi; + } + + /** + * Returns timestamp since boot when the scan record was observed. + */ + public long getTimestampNanos() { + return mTimestampNanos; + } + + @Override + public int hashCode() { + return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ScanResult other = (ScanResult) obj; + return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && + Objects.deepEquals(mScanRecord, other.mScanRecord) + && (mTimestampNanos == other.mTimestampNanos); + } + + @Override + public String toString() { + return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord=" + + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos=" + + mTimestampNanos + '}'; + } + + public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { + @Override + public ScanResult createFromParcel(Parcel source) { + return new ScanResult(source); + } + + @Override + public ScanResult[] newArray(int size) { + return new ScanResult[size]; + } + }; + +} diff --git a/core/java/android/bluetooth/le/ScanSettings.aidl b/core/java/android/bluetooth/le/ScanSettings.aidl new file mode 100644 index 0000000..eb169c1 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanSettings.aidl @@ -0,0 +1,19 @@ +/* + * 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.bluetooth.le; + +parcelable ScanSettings; diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java new file mode 100644 index 0000000..0a85675 --- /dev/null +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -0,0 +1,221 @@ +/* + * 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.bluetooth.le; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Settings for Bluetooth LE scan. + */ +public final class ScanSettings implements Parcelable { + /** + * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the + * least power. + */ + public static final int SCAN_MODE_LOW_POWER = 0; + /** + * Perform Bluetooth LE scan in balanced power mode. + */ + public static final int SCAN_MODE_BALANCED = 1; + /** + * Scan using highest duty cycle. It's recommended only using this mode when the application is + * running in foreground. + */ + public static final int SCAN_MODE_LOW_LATENCY = 2; + + /** + * Callback each time when a bluetooth advertisement is found. + */ + public static final int CALLBACK_TYPE_ON_UPDATE = 0; + /** + * Callback when a bluetooth advertisement is found for the first time. + * + * @hide + */ + public static final int CALLBACK_TYPE_ON_FOUND = 1; + /** + * Callback when a bluetooth advertisement is found for the first time, then lost. + * + * @hide + */ + public static final int CALLBACK_TYPE_ON_LOST = 2; + + /** + * Full scan result which contains device mac address, rssi, advertising and scan response and + * scan timestamp. + */ + public static final int SCAN_RESULT_TYPE_FULL = 0; + /** + * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's + * possible for an app to get more scan results that it asks if there are multiple apps using + * this type. TODO: decide whether we could unhide this setting. + * + * @hide + */ + public static final int SCAN_RESULT_TYPE_TRUNCATED = 1; + + // Bluetooth LE scan mode. + private int mScanMode; + + // Bluetooth LE scan callback type + private int mCallbackType; + + // Bluetooth LE scan result type + private int mScanResultType; + + // Time of delay for reporting the scan result + private long mReportDelayNanos; + + public int getScanMode() { + return mScanMode; + } + + public int getCallbackType() { + return mCallbackType; + } + + public int getScanResultType() { + return mScanResultType; + } + + /** + * Returns report delay timestamp based on the device clock. + */ + public long getReportDelayNanos() { + return mReportDelayNanos; + } + + private ScanSettings(int scanMode, int callbackType, int scanResultType, + long reportDelayNanos) { + mScanMode = scanMode; + mCallbackType = callbackType; + mScanResultType = scanResultType; + mReportDelayNanos = reportDelayNanos; + } + + private ScanSettings(Parcel in) { + mScanMode = in.readInt(); + mCallbackType = in.readInt(); + mScanResultType = in.readInt(); + mReportDelayNanos = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mScanMode); + dest.writeInt(mCallbackType); + dest.writeInt(mScanResultType); + dest.writeLong(mReportDelayNanos); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ScanSettings> + CREATOR = new Creator<ScanSettings>() { + @Override + public ScanSettings[] newArray(int size) { + return new ScanSettings[size]; + } + + @Override + public ScanSettings createFromParcel(Parcel in) { + return new ScanSettings(in); + } + }; + + /** + * Builder for {@link ScanSettings}. + */ + public static final class Builder { + private int mScanMode = SCAN_MODE_LOW_POWER; + private int mCallbackType = CALLBACK_TYPE_ON_UPDATE; + private int mScanResultType = SCAN_RESULT_TYPE_FULL; + private long mReportDelayNanos = 0; + + /** + * Set scan mode for Bluetooth LE scan. + * + * @param scanMode The scan mode can be one of + * {@link ScanSettings#SCAN_MODE_LOW_POWER}, + * {@link ScanSettings#SCAN_MODE_BALANCED} or + * {@link ScanSettings#SCAN_MODE_LOW_LATENCY}. + * @throws IllegalArgumentException If the {@code scanMode} is invalid. + */ + public Builder setScanMode(int scanMode) { + if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) { + throw new IllegalArgumentException("invalid scan mode " + scanMode); + } + mScanMode = scanMode; + return this; + } + + /** + * Set callback type for Bluetooth LE scan. + * + * @param callbackType The callback type for the scan. Can only be + * {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}. + * @throws IllegalArgumentException If the {@code callbackType} is invalid. + */ + public Builder setCallbackType(int callbackType) { + if (callbackType < CALLBACK_TYPE_ON_UPDATE + || callbackType > CALLBACK_TYPE_ON_LOST) { + throw new IllegalArgumentException("invalid callback type - " + callbackType); + } + mCallbackType = callbackType; + return this; + } + + /** + * Set scan result type for Bluetooth LE scan. + * + * @param scanResultType Type for scan result, could be either + * {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or + * {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}. + * @throws IllegalArgumentException If the {@code scanResultType} is invalid. + * @hide + */ + public Builder setScanResultType(int scanResultType) { + if (scanResultType < SCAN_RESULT_TYPE_FULL + || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) { + throw new IllegalArgumentException( + "invalid scanResultType - " + scanResultType); + } + mScanResultType = scanResultType; + return this; + } + + /** + * Set report delay timestamp for Bluetooth LE scan. + */ + public Builder setReportDelayNanos(long reportDelayNanos) { + mReportDelayNanos = reportDelayNanos; + return this; + } + + /** + * Build {@link ScanSettings}. + */ + public ScanSettings build() { + return new ScanSettings(mScanMode, mCallbackType, mScanResultType, + mReportDelayNanos); + } + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 6ae006c..a040efb 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -40,6 +40,7 @@ import android.os.Looper; import android.os.StatFs; import android.os.UserHandle; import android.os.UserManager; +import android.provider.MediaStore; import android.util.AttributeSet; import android.view.DisplayAdjustments; import android.view.Display; @@ -929,6 +930,40 @@ public abstract class Context { public abstract File[] getExternalCacheDirs(); /** + * Returns absolute paths to application-specific directories on all + * external storage devices where the application can place media files. + * These files are scanned and made available to other apps through + * {@link MediaStore}. + * <p> + * This is like {@link #getExternalFilesDirs} in that these files will be + * deleted when the application is uninstalled, however there are some + * important differences: + * <ul> + * <li>External files are not always available: they will disappear if the + * user mounts the external storage on a computer or removes it. + * <li>There is no security enforced with these files. + * </ul> + * <p> + * External storage devices returned here are considered a permanent part of + * the device, including both emulated external storage and physical media + * slots, such as SD cards in a battery compartment. The returned paths do + * not include transient devices, such as USB flash drives. + * <p> + * An application may store data on any or all of the returned devices. For + * example, an app may choose to store large files on the device with the + * most available space, as measured by {@link StatFs}. + * <p> + * No permissions are required to read or write to the returned paths; they + * are always accessible to the calling app. Write access outside of these + * paths on secondary external storage devices is not available. + * <p> + * Returned paths may be {@code null} if a storage device is unavailable. + * + * @see Environment#getExternalStorageState(File) + */ + public abstract File[] getExternalMediaDirs(); + + /** * Returns an array of strings naming the private files associated with * this Context's application package. * @@ -2023,6 +2058,7 @@ public abstract class Context { PRINT_SERVICE, MEDIA_SESSION_SERVICE, BATTERY_SERVICE, + TASK_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -2079,6 +2115,8 @@ public abstract class Context { * <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads * <dt> {@link #BATTERY_SERVICE} ("batterymanager") * <dd> A {@link android.os.BatteryManager} for managing battery state + * <dt> {@link #TASK_SERVICE} ("taskmanager") + * <dd> A {@link android.app.task.TaskManager} for managing scheduled tasks * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -2134,6 +2172,8 @@ public abstract class Context { * @see android.app.DownloadManager * @see #BATTERY_SERVICE * @see android.os.BatteryManager + * @see #TASK_SERVICE + * @see android.app.task.TaskManager */ public abstract Object getSystemService(@ServiceName @NonNull String name); @@ -2358,6 +2398,7 @@ public abstract class Context { * * @see #getSystemService * @see android.net.wifi.passpoint.WifiPasspointManager + * @hide */ public static final String WIFI_PASSPOINT_SERVICE = "wifipasspoint"; @@ -2654,6 +2695,15 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a + * {@link android.content.RestrictionsManager} for retrieving application restrictions + * and requesting permissions for restricted operations. + * @see #getSystemService + * @see android.content.RestrictionsManager + */ + public static final String RESTRICTIONS_SERVICE = "restrictions"; + + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.app.AppOpsManager} for tracking application operations * on the device. * @@ -2701,11 +2751,11 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a - * {@link android.tv.TvInputManager} for interacting with TV inputs on the - * device. + * {@link android.media.tv.TvInputManager} for interacting with TV inputs + * on the device. * * @see #getSystemService - * @see android.tv.TvInputManager + * @see android.media.tv.TvInputManager */ public static final String TV_INPUT_SERVICE = "tv_input"; @@ -2728,6 +2778,15 @@ public abstract class Context { public static final String USAGE_STATS_SERVICE = "usagestats"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.app.task.TaskManager} instance for managing occasional + * background tasks. + * @see #getSystemService + * @see android.app.task.TaskManager + */ + public static final String TASK_SERVICE = "task"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index c66355b..dbf9122 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -237,6 +237,11 @@ public class ContextWrapper extends Context { } @Override + public File[] getExternalMediaDirs() { + return mBase.getExternalMediaDirs(); + } + + @Override public File getDir(String name, int mode) { return mBase.getDir(name, mode); } diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/content/IRestrictionsManager.aidl index 71fc780..b1c0a3a 100644 --- a/core/java/android/tv/ITvInputServiceCallback.aidl +++ b/core/java/android/content/IRestrictionsManager.aidl @@ -14,15 +14,17 @@ * limitations under the License. */ -package android.tv; +package android.content; -import android.content.ComponentName; +import android.os.Bundle; /** - * Helper interface for ITvInputService to allow the TV input to notify the client when its status - * has been changed. + * Interface used by the RestrictionsManager * @hide */ -oneway interface ITvInputServiceCallback { - void onAvailabilityChanged(in String inputId, boolean isAvailable); +interface IRestrictionsManager { + Bundle getApplicationRestrictions(in String packageName); + boolean hasRestrictionsProvider(); + void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData); + void notifyPermissionResponse(in String packageName, in Bundle response); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 076f657..bd07470 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -45,6 +45,7 @@ import android.util.AttributeSet; import android.util.Log; import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.Serializable; @@ -604,6 +605,15 @@ import java.util.Set; * of all possible flags. */ public class Intent implements Parcelable, Cloneable { + private static final String ATTR_ACTION = "action"; + private static final String TAG_CATEGORIES = "categories"; + private static final String ATTR_CATEGORY = "category"; + private static final String TAG_EXTRA = "extra"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_COMPONENT = "component"; + private static final String ATTR_DATA = "data"; + private static final String ATTR_FLAGS = "flags"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent activity actions (see action variable). @@ -3729,32 +3739,27 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000; /** - * This flag is used to break out "documents" into separate tasks that can - * be reached via the Recents mechanism. Such a document is any kind of - * item for which an application may want to maintain multiple simultaneous - * instances. Examples might be text files, web pages, spreadsheets, or - * emails. Each such document will be in a separate task in the Recents list. - * - * <p>When set, the activity specified by this Intent will launch into a - * separate task rooted at that activity. The activity launched must be - * defined with {@link android.R.attr#launchMode} <code>standard</code> - * or <code>singleTop</code>. + * This flag is used to open a document into a new task rooted at the activity launched + * by this Intent. Through the use of this flag, or its equivalent attribute, + * {@link android.R.attr#documentLaunchMode} multiple instances of the same activity + * containing different douments will appear in the recent tasks list. * - * <p>If FLAG_ACTIVITY_NEW_DOCUMENT is used without - * {@link #FLAG_ACTIVITY_MULTIPLE_TASK} then the activity manager will - * search for an existing task with a matching target activity and Intent - * data URI and relaunch that task, first finishing all activities down to - * the root activity and then calling the root activity's - * {@link android.app.Activity#onNewIntent(Intent)} method. If no existing - * task's root activity matches the Intent's data URI then a new task will - * be launched with the target activity as root. + * <p>The use of the activity attribute form of this, + * {@link android.R.attr#documentLaunchMode}, is + * preferred over the Intent flag described here. The attribute form allows the + * Activity to specify multiple document behavior for all launchers of the Activity + * whereas using this flag requires each Intent that launches the Activity to specify it. * - * <p>When paired with {@link #FLAG_ACTIVITY_MULTIPLE_TASK} this will - * always create a new task. Thus the same document may be made to appear - * more than one time in Recents. + * <p>FLAG_ACTIVITY_NEW_DOCUMENT may be used in conjunction with {@link + * #FLAG_ACTIVITY_MULTIPLE_TASK}. When used alone it is the + * equivalent of the Activity manifest specifying {@link + * android.R.attr#documentLaunchMode}="intoExisting". When used with + * FLAG_ACTIVITY_MULTIPLE_TASK it is the equivalent of the Activity manifest specifying + * {@link android.R.attr#documentLaunchMode}="always". * - * <p>This is equivalent to the attribute {@link android.R.attr#documentLaunchMode}. + * Refer to {@link android.R.attr#documentLaunchMode} for more information. * + * @see android.R.attr#documentLaunchMode * @see #FLAG_ACTIVITY_MULTIPLE_TASK */ public static final int FLAG_ACTIVITY_NEW_DOCUMENT = @@ -7347,7 +7352,7 @@ public class Intent implements Parcelable, Cloneable { } String nodeName = parser.getName(); - if (nodeName.equals("category")) { + if (nodeName.equals(TAG_CATEGORIES)) { sa = resources.obtainAttributes(attrs, com.android.internal.R.styleable.IntentCategory); String cat = sa.getString(com.android.internal.R.styleable.IntentCategory_name); @@ -7358,11 +7363,11 @@ public class Intent implements Parcelable, Cloneable { } XmlUtils.skipCurrentTag(parser); - } else if (nodeName.equals("extra")) { + } else if (nodeName.equals(TAG_EXTRA)) { if (intent.mExtras == null) { intent.mExtras = new Bundle(); } - resources.parseBundleExtra("extra", attrs, intent.mExtras); + resources.parseBundleExtra(TAG_EXTRA, attrs, intent.mExtras); XmlUtils.skipCurrentTag(parser); } else { @@ -7373,6 +7378,76 @@ public class Intent implements Parcelable, Cloneable { return intent; } + /** @hide */ + public void saveToXml(XmlSerializer out) throws IOException { + if (mAction != null) { + out.attribute(null, ATTR_ACTION, mAction); + } + if (mData != null) { + out.attribute(null, ATTR_DATA, mData.toString()); + } + if (mType != null) { + out.attribute(null, ATTR_TYPE, mType); + } + if (mComponent != null) { + out.attribute(null, ATTR_COMPONENT, mComponent.flattenToShortString()); + } + out.attribute(null, ATTR_FLAGS, Integer.toHexString(getFlags())); + + if (mCategories != null) { + out.startTag(null, TAG_CATEGORIES); + for (int categoryNdx = mCategories.size() - 1; categoryNdx >= 0; --categoryNdx) { + out.attribute(null, ATTR_CATEGORY, mCategories.valueAt(categoryNdx)); + } + } + } + + /** @hide */ + public static Intent restoreFromXml(XmlPullParser in) throws IOException, + XmlPullParserException { + Intent intent = new Intent(); + final int outerDepth = in.getDepth(); + + int attrCount = in.getAttributeCount(); + for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) { + final String attrName = in.getAttributeName(attrNdx); + final String attrValue = in.getAttributeValue(attrNdx); + if (ATTR_ACTION.equals(attrName)) { + intent.setAction(attrValue); + } else if (ATTR_DATA.equals(attrName)) { + intent.setData(Uri.parse(attrValue)); + } else if (ATTR_TYPE.equals(attrName)) { + intent.setType(attrValue); + } else if (ATTR_COMPONENT.equals(attrName)) { + intent.setComponent(ComponentName.unflattenFromString(attrValue)); + } else if (ATTR_FLAGS.equals(attrName)) { + intent.setFlags(Integer.valueOf(attrValue, 16)); + } else { + Log.e("Intent", "restoreFromXml: unknown attribute=" + attrName); + } + } + + int event; + String name; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + name = in.getName(); + if (TAG_CATEGORIES.equals(name)) { + attrCount = in.getAttributeCount(); + for (int attrNdx = attrCount - 1; attrNdx >= 0; --attrNdx) { + intent.addCategory(in.getAttributeValue(attrNdx)); + } + } else { + Log.w("Intent", "restoreFromXml: unknown name=" + name); + XmlUtils.skipCurrentTag(in); + } + } + } + + return intent; + } + /** * Normalize a MIME data type. * diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java index 3ff53bf..62f88a9 100644 --- a/core/java/android/content/RestrictionEntry.java +++ b/core/java/android/content/RestrictionEntry.java @@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable { */ public static final int TYPE_MULTI_SELECT = 4; + /** + * A type of restriction. Use this for storing an integer value. The range of values + * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}. + */ + public static final int TYPE_INTEGER = 5; + /** The type of restriction. */ - private int type; + private int mType; /** The unique key that identifies the restriction. */ - private String key; + private String mKey; /** The user-visible title of the restriction. */ - private String title; + private String mTitle; /** The user-visible secondary description of the restriction. */ - private String description; + private String mDescription; /** The user-visible set of choices used for single-select and multi-select lists. */ - private String [] choices; + private String [] mChoiceEntries; /** The values corresponding to the user-visible choices. The value(s) of this entry will * one or more of these, returned by {@link #getAllSelectedStrings()} and * {@link #getSelectedString()}. */ - private String [] values; + private String [] mChoiceValues; /* The chosen value, whose content depends on the type of the restriction. */ - private String currentValue; + private String mCurrentValue; /* List of selected choices in the multi-select case. */ - private String[] currentValues; + private String[] mCurrentValues; /** * Constructor for {@link #TYPE_CHOICE} type. @@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the current value */ public RestrictionEntry(String key, String selectedString) { - this.key = key; - this.type = TYPE_CHOICE; - this.currentValue = selectedString; + this.mKey = key; + this.mType = TYPE_CHOICE; + this.mCurrentValue = selectedString; } /** @@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable { * @param selectedState whether this restriction is selected or not */ public RestrictionEntry(String key, boolean selectedState) { - this.key = key; - this.type = TYPE_BOOLEAN; + this.mKey = key; + this.mType = TYPE_BOOLEAN; setSelectedState(selectedState); } @@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable { * @param selectedStrings the list of values that are currently selected */ public RestrictionEntry(String key, String[] selectedStrings) { - this.key = key; - this.type = TYPE_MULTI_SELECT; - this.currentValues = selectedStrings; + this.mKey = key; + this.mType = TYPE_MULTI_SELECT; + this.mCurrentValues = selectedStrings; + } + + /** + * Constructor for {@link #TYPE_INTEGER} type. + * @param key the unique key for this restriction + * @param selectedInt the integer value of the restriction + */ + public RestrictionEntry(String key, int selectedInt) { + mKey = key; + mType = TYPE_INTEGER; + setIntValue(selectedInt); } /** @@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable { * @param type the type for this restriction. */ public void setType(int type) { - this.type = type; + this.mType = type; } /** @@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable { * @return the type for this restriction */ public int getType() { - return type; + return mType; } /** @@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable { * single string values. */ public String getSelectedString() { - return currentValue; + return mCurrentValue; } /** @@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable { * null otherwise. */ public String[] getAllSelectedStrings() { - return currentValues; + return mCurrentValues; } /** @@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable { * @return the current selected state of the entry. */ public boolean getSelectedState() { - return Boolean.parseBoolean(currentValue); + return Boolean.parseBoolean(mCurrentValue); + } + + /** + * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}. + * @return the integer value of the entry. + */ + public int getIntValue() { + return Integer.parseInt(mCurrentValue); + } + + /** + * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}. + * @param value the integer value to set. + */ + public void setIntValue(int value) { + mCurrentValue = Integer.toString(value); } /** @@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable { * @param selectedString the string value to select. */ public void setSelectedString(String selectedString) { - currentValue = selectedString; + mCurrentValue = selectedString; } /** @@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable { * @param state the current selected state */ public void setSelectedState(boolean state) { - currentValue = Boolean.toString(state); + mCurrentValue = Boolean.toString(state); } /** @@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable { * @param allSelectedStrings the current list of selected values. */ public void setAllSelectedStrings(String[] allSelectedStrings) { - currentValues = allSelectedStrings; + mCurrentValues = allSelectedStrings; } /** @@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable { * @see #getAllSelectedStrings() */ public void setChoiceValues(String[] choiceValues) { - values = choiceValues; + mChoiceValues = choiceValues; } /** @@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceValues(Context context, int stringArrayResId) { - values = context.getResources().getStringArray(stringArrayResId); + mChoiceValues = context.getResources().getStringArray(stringArrayResId); } /** @@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of possible values. */ public String[] getChoiceValues() { - return values; + return mChoiceValues; } /** @@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable { * @see #setChoiceValues(String[]) */ public void setChoiceEntries(String[] choiceEntries) { - choices = choiceEntries; + mChoiceEntries = choiceEntries; } /** Sets a list of strings that will be presented as choices to the user. This is similar to @@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable { * @param stringArrayResId the resource id of a string array containing the possible entries. */ public void setChoiceEntries(Context context, int stringArrayResId) { - choices = context.getResources().getStringArray(stringArrayResId); + mChoiceEntries = context.getResources().getStringArray(stringArrayResId); } /** @@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable { * @return the list of choices presented to the user. */ public String[] getChoiceEntries() { - return choices; + return mChoiceEntries; } /** @@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible description, null if none was set earlier. */ public String getDescription() { - return description; + return mDescription; } /** @@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable { * @param description the user-visible description string. */ public void setDescription(String description) { - this.description = description; + this.mDescription = description; } /** @@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable { * @return the key for the restriction. */ public String getKey() { - return key; + return mKey; } /** @@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable { * @return the user-visible title for the entry, null if none was set earlier. */ public String getTitle() { - return title; + return mTitle; } /** @@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable { * @param title the user-visible title for the entry. */ public void setTitle(String title) { - this.title = title; + this.mTitle = title; } private boolean equalArrays(String[] one, String[] other) { @@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable { if (!(o instanceof RestrictionEntry)) return false; final RestrictionEntry other = (RestrictionEntry) o; // Make sure that either currentValue matches or currentValues matches. - return type == other.type && key.equals(other.key) + return mType == other.mType && mKey.equals(other.mKey) && - ((currentValues == null && other.currentValues == null - && currentValue != null && currentValue.equals(other.currentValue)) + ((mCurrentValues == null && other.mCurrentValues == null + && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue)) || - (currentValue == null && other.currentValue == null - && currentValues != null && equalArrays(currentValues, other.currentValues))); + (mCurrentValue == null && other.mCurrentValue == null + && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues))); } @Override public int hashCode() { int result = 17; - result = 31 * result + key.hashCode(); - if (currentValue != null) { - result = 31 * result + currentValue.hashCode(); - } else if (currentValues != null) { - for (String value : currentValues) { + result = 31 * result + mKey.hashCode(); + if (mCurrentValue != null) { + result = 31 * result + mCurrentValue.hashCode(); + } else if (mCurrentValues != null) { + for (String value : mCurrentValues) { if (value != null) { result = 31 * result + value.hashCode(); } @@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable { } public RestrictionEntry(Parcel in) { - type = in.readInt(); - key = in.readString(); - title = in.readString(); - description = in.readString(); - choices = readArray(in); - values = readArray(in); - currentValue = in.readString(); - currentValues = readArray(in); + mType = in.readInt(); + mKey = in.readString(); + mTitle = in.readString(); + mDescription = in.readString(); + mChoiceEntries = readArray(in); + mChoiceValues = readArray(in); + mCurrentValue = in.readString(); + mCurrentValues = readArray(in); } @Override @@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(type); - dest.writeString(key); - dest.writeString(title); - dest.writeString(description); - writeArray(dest, choices); - writeArray(dest, values); - dest.writeString(currentValue); - writeArray(dest, currentValues); + dest.writeInt(mType); + dest.writeString(mKey); + dest.writeString(mTitle); + dest.writeString(mDescription); + writeArray(dest, mChoiceEntries); + writeArray(dest, mChoiceValues); + dest.writeString(mCurrentValue); + writeArray(dest, mCurrentValues); } public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() { @@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable { @Override public String toString() { - return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}"; + return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}"; } } diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java new file mode 100644 index 0000000..0dd0edd --- /dev/null +++ b/core/java/android/content/RestrictionsManager.java @@ -0,0 +1,344 @@ +/* + * 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.content; + +import android.app.admin.DevicePolicyManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for apps to query restrictions imposed by an entity that + * manages the user. Apps can also send permission requests to a local or remote + * device administrator to override default app-specific restrictions or any other + * operation that needs explicit authorization from the administrator. + * <p> + * Apps can expose a set of restrictions via a runtime receiver mechanism or via + * static meta data in the manifest. + * <p> + * If the user has an active restrictions provider, dynamic requests can be made in + * addition to the statically imposed restrictions. Dynamic requests are app-specific + * and can be expressed via a predefined set of templates. + * <p> + * The RestrictionsManager forwards the dynamic requests to the active + * restrictions provider. The restrictions provider can respond back to requests by calling + * {@link #notifyPermissionResponse(String, Bundle)}, when + * a response is received from the administrator of the device or user + * The response is relayed back to the application via a protected broadcast, + * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}. + * <p> + * Static restrictions are specified by an XML file referenced by a meta-data attribute + * in the manifest. This enables applications as well as any web administration consoles + * to be able to read the template from the apk. + * <p> + * The syntax of the XML format is as follows: + * <pre> + * <restrictions> + * <restriction + * android:key="<key>" + * android:restrictionType="boolean|string|integer|multi-select|null" + * ... /> + * <restriction ... /> + * </restrictions> + * </pre> + * <p> + * The attributes for each restriction depend on the restriction type. + * + * @see RestrictionEntry + */ +public class RestrictionsManager { + + /** + * Broadcast intent delivered when a response is received for a permission + * request. The response is not available for later query, so the receiver + * must persist and/or immediately act upon the response. The application + * should not interrupt the user by coming to the foreground if it isn't + * currently in the foreground. It can post a notification instead, informing + * the user of a change in state. + * <p> + * For instance, if the user requested permission to make an in-app purchase, + * the app can post a notification that the request had been granted or denied, + * and allow the purchase to go through. + * <p> + * The broadcast Intent carries the following extra: + * {@link #EXTRA_RESPONSE_BUNDLE}. + */ + public static final String ACTION_PERMISSION_RESPONSE_RECEIVED = + "android.intent.action.PERMISSION_RESPONSE_RECEIVED"; + + /** + * Protected broadcast intent sent to the active restrictions provider. The intent + * contains the following extras:<p> + * <ul> + * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting + * permission.</li> + * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li> + * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values + * for the request. + * </ul> + * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName) + * @see #requestPermission(String, String, Bundle) + */ + public static final String ACTION_REQUEST_PERMISSION = + "android.intent.action.REQUEST_PERMISSION"; + + /** + * The package name of the application making the request. + */ + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + /** + * The template id that specifies what kind of a request it is and may indicate + * how the request is to be presented to the administrator. Must be either one of + * the predefined templates or a custom one specified by the application that the + * restrictions provider is familiar with. + */ + public static final String EXTRA_TEMPLATE_ID = "template_id"; + + /** + * A bundle containing the details about the request. The contents depend on the + * template id. + * @see #EXTRA_TEMPLATE_ID + */ + public static final String EXTRA_REQUEST_BUNDLE = "request_bundle"; + + /** + * Contains a response from the administrator for specific request. + * The bundle contains the following information, at least: + * <ul> + * <li>{@link #REQUEST_KEY_ID}: The request id.</li> + * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li> + * </ul> + * <p> + * And depending on what the request template was, the bundle will contain the actual + * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in + * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator + * approved the request, false otherwise. + */ + public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle"; + + + /** + * Request template that presents a simple question, with a possible title and icon. + * <p> + * Required keys are + * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}. + * <p> + * Optional keys are + * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE}, + * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}. + */ + public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple"; + + /** + * Key for request ID contained in the request bundle. + * <p> + * App-generated request id to identify the specific request when receiving + * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_ID = "android.req_template.req_id"; + + /** + * Key for request data contained in the request bundle. + * <p> + * Optional, typically used to identify the specific data that is being referred to, + * such as the unique identifier for a movie or book. This is not used for display + * purposes and is more like a cookie. This value is returned in the + * {@link #EXTRA_RESPONSE_BUNDLE}. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DATA = "android.req_template.data"; + + /** + * Key for request title contained in the request bundle. + * <p> + * Optional, typically used as the title of any notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_TITLE = "android.req_template.title"; + + /** + * Key for request message contained in the request bundle. + * <p> + * Required, shown as the actual message in a notification or dialog presented + * to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg"; + + /** + * Key for request icon contained in the request bundle. + * <p> + * Optional, shown alongside the request message presented to the administrator + * who approves the request. + * <p> + * Type: Bitmap + */ + public static final String REQUEST_KEY_ICON = "android.req_template.icon"; + + /** + * Key for request approval button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the positive button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept"; + + /** + * Key for request rejection button label contained in the request bundle. + * <p> + * Optional, may be shown as a label on the negative button in a dialog or + * notification presented to the administrator who approves the request. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject"; + + /** + * Key for requestor's name contained in the request bundle. This value is not specified by + * the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor"; + + /** + * Key for requestor's device name contained in the request bundle. This value is not specified + * by the application. It is automatically inserted into the Bundle by the Restrictions Provider + * before it is sent to the administrator. + * <p> + * Type: String + */ + public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device"; + + /** + * Key for the response in the response bundle sent to the application, for a permission + * request. + * <p> + * Type: boolean + */ + public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response"; + + private static final String TAG = "RestrictionsManager"; + + private final Context mContext; + private final IRestrictionsManager mService; + + /** + * @hide + */ + public RestrictionsManager(Context context, IRestrictionsManager service) { + mContext = context; + mService = service; + } + + /** + * Returns any available set of application-specific restrictions applicable + * to this application. + * @return + */ + public Bundle getApplicationRestrictions() { + try { + if (mService != null) { + return mService.getApplicationRestrictions(mContext.getPackageName()); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return null; + } + + /** + * Called by an application to check if permission requests can be made. If false, + * there is no need to request permission for an operation, unless a static + * restriction applies to that operation. + * @return + */ + public boolean hasRestrictionsProvider() { + try { + if (mService != null) { + return mService.hasRestrictionsProvider(); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + return false; + } + + /** + * Called by an application to request permission for an operation. The contents of the + * request are passed in a Bundle that contains several pieces of data depending on the + * chosen request template. + * + * @param requestTemplate The request template to use. The template could be one of the + * predefined templates specified in this class or a custom template that the specific + * Restrictions Provider might understand. For custom templates, the template name should be + * namespaced to avoid collisions with predefined templates and templates specified by + * other Restrictions Provider vendors. + * @param requestData A Bundle containing the data corresponding to the specified request + * template. The keys for the data in the bundle depend on the kind of template chosen. + */ + public void requestPermission(String requestTemplate, Bundle requestData) { + try { + if (mService != null) { + mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Called by the Restrictions Provider when a response is available to be + * delivered to an application. + * @param packageName + * @param response + */ + public void notifyPermissionResponse(String packageName, Bundle response) { + try { + if (mService != null) { + mService.notifyPermissionResponse(packageName, response); + } + } catch (RemoteException re) { + Log.w(TAG, "Couldn't reach service"); + } + } + + /** + * Parse and return the list of restrictions defined in the manifest for the specified + * package, if any. + * @param packageName The application for which to fetch the restrictions list. + * @return The list of RestrictionEntry objects created from the XML file specified + * in the manifest, or null if none was specified. + */ + public List<RestrictionEntry> getManifestRestrictions(String packageName) { + // TODO: + return null; + } +} diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index 6b4404d..46c9234 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -355,7 +355,14 @@ public interface SharedPreferences { /** * Registers a callback to be invoked when a change happens to a preference. - * + * + * <p class="caution"><strong>Caution:</strong> The preference manager does + * not currently store a strong reference to the listener. You must store a + * strong reference to the listener, or it will be susceptible to garbage + * collection. We recommend you keep a reference to the listener in the + * instance data of an object that will exist as long as you need the + * listener.</p> + * * @param listener The callback that will run. * @see #unregisterOnSharedPreferenceChangeListener */ diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 6cb781f..70668e1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -112,7 +112,7 @@ interface IPackageManager { ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); - boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest); + boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags, int userId); @@ -248,10 +248,10 @@ interface IPackageManager { void clearPackagePersistentPreferredActivities(String packageName, int userId); - void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig, - int userIdDest); + void addCrossProfileIntentFilter(in IntentFilter filter, boolean removable, int sourceUserId, + int targetUserId); - void clearForwardingIntentFilters(int userIdOrig); + void clearCrossProfileIntentFilters(int sourceUserId); /** * Report the set of 'Home' activity candidates, plus (if any) which of them @@ -433,6 +433,13 @@ interface IPackageManager { in VerificationParams verificationParams, in ContainerEncryptionParams encryptionParams); + void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI, + in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2, + int flags, in String installerPackageName, + in VerificationParams verificationParams, + in ContainerEncryptionParams encryptionParams, + in String packageAbiOverride); + int installExistingPackageAsUser(String packageName, int userId); void verifyPendingInstall(int id, int verificationCode); diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java index 9087338..5d48868 100644 --- a/core/java/android/content/pm/LauncherActivityInfo.java +++ b/core/java/android/content/pm/LauncherActivityInfo.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.DisplayMetrics; import android.util.Log; /** @@ -47,21 +48,22 @@ public class LauncherActivityInfo { private ActivityInfo mActivityInfo; private ComponentName mComponentName; private UserHandle mUser; - // TODO: Fetch this value from PM private long mFirstInstallTime; /** * Create a launchable activity object for a given ResolveInfo and user. - * + * * @param context The context for fetching resources. * @param info ResolveInfo from which to create the LauncherActivityInfo. * @param user The UserHandle of the profile to which this activity belongs. */ - LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) { + LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user, + long firstInstallTime) { this(context); - this.mActivityInfo = info.activityInfo; - this.mComponentName = LauncherApps.getComponentName(info); - this.mUser = user; + mActivityInfo = info.activityInfo; + mComponentName = LauncherApps.getComponentName(info); + mUser = user; + mFirstInstallTime = firstInstallTime; } LauncherActivityInfo(Context context) { @@ -79,7 +81,13 @@ public class LauncherActivityInfo { } /** - * Returns the user handle of the user profile that this activity belongs to. + * Returns the user handle of the user profile that this activity belongs to. In order to + * persist the identity of the profile, do not store the UserHandle. Instead retrieve its + * serial number from UserManager. You can convert the serial number back to a UserHandle + * for later use. + * + * @see UserManager#getSerialNumberForUser(UserHandle) + * @see UserManager#getUserForSerialNumber(long) * * @return The UserHandle of the profile. */ @@ -89,7 +97,7 @@ public class LauncherActivityInfo { /** * Retrieves the label for the activity. - * + * * @return The label for the activity. */ public CharSequence getLabel() { @@ -98,8 +106,10 @@ public class LauncherActivityInfo { /** * Returns the icon for this activity, without any badging for the profile. - * @param density The preferred density of the icon, zero for default density. + * @param density The preferred density of the icon, zero for default density. Use + * density DPI values from {@link DisplayMetrics}. * @see #getBadgedIcon(int) + * @see DisplayMetrics * @return The drawable associated with the activity */ public Drawable getIcon(int density) { @@ -109,15 +119,25 @@ public class LauncherActivityInfo { /** * Returns the application flags from the ApplicationInfo of the activity. - * + * * @return Application flags + * @hide remove before shipping */ public int getApplicationFlags() { return mActivityInfo.applicationInfo.flags; } /** + * Returns the application info for the appliction this activity belongs to. + * @return + */ + public ApplicationInfo getApplicationInfo() { + return mActivityInfo.applicationInfo; + } + + /** * Returns the time at which the package was first installed. + * * @return The time of installation of the package, in milliseconds. */ public long getFirstInstallTime() { @@ -134,7 +154,9 @@ public class LauncherActivityInfo { /** * Returns the activity icon with badging appropriate for the profile. - * @param density Optional density for the icon, or 0 to use the default density. + * @param density Optional density for the icon, or 0 to use the default density. Use + * {@link DisplayMetrics} for DPI values. + * @see DisplayMetrics * @return A badged icon for the activity. */ public Drawable getBadgedIcon(int density) { diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 8025b60..04c0b9f 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,15 +16,18 @@ package android.content.pm; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; +import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Rect; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import java.util.ArrayList; @@ -36,6 +39,12 @@ import java.util.List; * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile. * Since the PackageManager will not deliver package broadcasts for other profiles, you can register * for package changes here. + * <p> + * To watch for managed profiles being added or removed, register for the following broadcasts: + * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}. + * <p> + * You can retrieve the list of profiles associated with this user with + * {@link UserManager#getUserProfiles()}. */ public class LauncherApps { @@ -44,12 +53,13 @@ public class LauncherApps { private Context mContext; private ILauncherApps mService; + private PackageManager mPm; private List<OnAppsChangedListener> mListeners = new ArrayList<OnAppsChangedListener>(); /** - * Callbacks for changes to this and related managed profiles. + * Callbacks for package changes to this and related managed profiles. */ public interface OnAppsChangedListener { /** @@ -57,6 +67,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that was removed. + * @hide remove before ship */ void onPackageRemoved(UserHandle user, String packageName); @@ -65,6 +76,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that was added. + * @hide remove before ship */ void onPackageAdded(UserHandle user, String packageName); @@ -73,6 +85,7 @@ public class LauncherApps { * * @param user The UserHandle of the profile that generated the change. * @param packageName The name of the package that has changed. + * @hide remove before ship */ void onPackageChanged(UserHandle user, String packageName); @@ -86,6 +99,7 @@ public class LauncherApps { * available. * @param replacing Indicates whether these packages are replacing * existing ones. + * @hide remove before ship */ void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing); @@ -99,14 +113,66 @@ public class LauncherApps { * unavailable. * @param replacing Indicates whether the packages are about to be * replaced with new versions. + * @hide remove before ship */ void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing); + + /** + * Indicates that a package was removed from the specified profile. + * + * @param packageName The name of the package that was removed. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageRemoved(String packageName, UserHandle user); + + /** + * Indicates that a package was added to the specified profile. + * + * @param packageName The name of the package that was added. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageAdded(String packageName, UserHandle user); + + /** + * Indicates that a package was modified in the specified profile. + * + * @param packageName The name of the package that has changed. + * @param user The UserHandle of the profile that generated the change. + */ + void onPackageChanged(String packageName, UserHandle user); + + /** + * Indicates that one or more packages have become available. For + * example, this can happen when a removable storage card has + * reappeared. + * + * @param packageNames The names of the packages that have become + * available. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether these packages are replacing + * existing ones. + */ + void onPackagesAvailable(String [] packageNames, UserHandle user, boolean replacing); + + /** + * Indicates that one or more packages have become unavailable. For + * example, this can happen when a removable storage card has been + * removed. + * + * @param packageNames The names of the packages that have become + * unavailable. + * @param user The UserHandle of the profile that generated the change. + * @param replacing Indicates whether the packages are about to be + * replaced with new versions. + */ + void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing); } /** @hide */ public LauncherApps(Context context, ILauncherApps service) { mContext = context; mService = service; + mPm = context.getPackageManager(); } /** @@ -131,7 +197,15 @@ public class LauncherApps { final int count = activities.size(); for (int i = 0; i < count; i++) { ResolveInfo ri = activities.get(i); - LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user); + long firstInstallTime = 0; + try { + firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime; + } catch (NameNotFoundException nnfe) { + // Sorry, can't find package + } + LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user, + firstInstallTime); if (DEBUG) { Log.v(TAG, "Returning activity for profile " + user + " : " + lai.getComponentName()); @@ -157,7 +231,15 @@ public class LauncherApps { try { ResolveInfo ri = mService.resolveActivity(intent, user); if (ri != null) { - LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user); + long firstInstallTime = 0; + try { + firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime; + } catch (NameNotFoundException nnfe) { + // Sorry, can't find package + } + LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user, + firstInstallTime); return info; } } catch (RemoteException re) { @@ -173,9 +255,23 @@ public class LauncherApps { * @param sourceBounds The Rect containing the source bounds of the clicked icon * @param opts Options to pass to startActivity * @param user The UserHandle of the profile + * @hide remove before ship */ public void startActivityForProfile(ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) { + startActivityForProfile(component, user, sourceBounds, opts); + } + + /** + * Starts an activity in the specified profile. + * + * @param component The ComponentName of the activity to launch + * @param user The UserHandle of the profile + * @param sourceBounds The Rect containing the source bounds of the clicked icon + * @param opts Options to pass to startActivity + */ + public void startActivityForProfile(ComponentName component, UserHandle user, Rect sourceBounds, + Bundle opts) { if (DEBUG) { Log.i(TAG, "StartActivityForProfile " + component + " " + user.getIdentifier()); } @@ -224,13 +320,15 @@ public class LauncherApps { * * @param listener The listener to add. */ - public synchronized void addOnAppsChangedListener(OnAppsChangedListener listener) { - if (listener != null && !mListeners.contains(listener)) { - mListeners.add(listener); - if (mListeners.size() == 1) { - try { - mService.addOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { + public void addOnAppsChangedListener(OnAppsChangedListener listener) { + synchronized (this) { + if (listener != null && !mListeners.contains(listener)) { + mListeners.add(listener); + if (mListeners.size() == 1) { + try { + mService.addOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } } } } @@ -242,12 +340,14 @@ public class LauncherApps { * @param listener The listener to remove. * @see #addOnAppsChangedListener(OnAppsChangedListener) */ - public synchronized void removeOnAppsChangedListener(OnAppsChangedListener listener) { - mListeners.remove(listener); - if (mListeners.size() == 0) { - try { - mService.removeOnAppsChangedListener(mAppsChangedListener); - } catch (RemoteException re) { + public void removeOnAppsChangedListener(OnAppsChangedListener listener) { + synchronized (this) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + try { + mService.removeOnAppsChangedListener(mAppsChangedListener); + } catch (RemoteException re) { + } } } } @@ -261,7 +361,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageRemoved(user, packageName); + listener.onPackageRemoved(user, packageName); // TODO: Remove before ship + listener.onPackageRemoved(packageName, user); } } } @@ -273,7 +374,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageChanged(user, packageName); + listener.onPackageChanged(user, packageName); // TODO: Remove before ship + listener.onPackageChanged(packageName, user); } } } @@ -285,7 +387,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackageAdded(user, packageName); + listener.onPackageAdded(user, packageName); // TODO: Remove before ship + listener.onPackageAdded(packageName, user); } } } @@ -298,7 +401,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesAvailable(user, packageNames, replacing); + listener.onPackagesAvailable(user, packageNames, replacing); // TODO: Remove + listener.onPackagesAvailable(packageNames, user, replacing); } } } @@ -311,7 +415,8 @@ public class LauncherApps { } synchronized (LauncherApps.this) { for (OnAppsChangedListener listener : mListeners) { - listener.onPackagesUnavailable(user, packageNames, replacing); + listener.onPackagesUnavailable(user, packageNames, replacing); // TODO: Remove + listener.onPackagesUnavailable(packageNames, user, replacing); } } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 35bcc02..c5cd5c9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3576,24 +3576,38 @@ public abstract class PackageManager { } /** - * Adds a forwarding intent filter. After calling this method all intents sent from the user - * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if - * they match the specified intent filter. - * @param filter the {@link IntentFilter} the intent has to match to be forwarded - * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent - * filter - * @param userIdOrig user from which the intent can be forwarded - * @param userIdDest user to which the intent can be forwarded + * Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the + * user with id sourceUserId can also be be resolved by activities in the user with id + * targetUserId if they match the specified intent filter. + * @param filter the {@link IntentFilter} the intent has to match + * @param removable if set to false, {@link clearCrossProfileIntentFilters} will not remove this + * {@link CrossProfileIntentFilter} * @hide */ + public abstract void addCrossProfileIntentFilter(IntentFilter filter, boolean removable, + int sourceUserId, int targetUserId); + + /** + * @hide + * @deprecated + * TODO: remove it as soon as the code of ManagedProvisionning is updated + */ public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable, - int userIdOrig, int userIdDest); + int sourceUserId, int targetUserId); /** - * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as - * the origin. - * @param userIdOrig user from which the intent can be forwarded + * Clearing removable {@link CrossProfileIntentFilter}s which have the specified user as their + * source + * @param sourceUserId + * be cleared. * @hide */ - public abstract void clearForwardingIntentFilters(int userIdOrig); + public abstract void clearCrossProfileIntentFilters(int sourceUserId); + + /** + * @hide + * @deprecated + * TODO: remove it as soon as the code of ManagedProvisionning is updated + */ + public abstract void clearForwardingIntentFilters(int sourceUserId); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1c838c3..ab8bf61 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2174,7 +2174,6 @@ public class PackageParser { } final int innerDepth = parser.getDepth(); - int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { @@ -2548,13 +2547,13 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_singleUser, false)) { a.info.flags |= ActivityInfo.FLAG_SINGLE_USER; - if (a.info.exported) { + if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Activity exported request ignored due to singleUser: " + a.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); a.info.exported = false; + setExported = true; } - setExported = true; } if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly, @@ -2907,7 +2906,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_singleUser, false)) { p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; - if (p.info.exported) { + if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Provider exported request ignored due to singleUser: " + p.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); @@ -3181,13 +3180,13 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_singleUser, false)) { s.info.flags |= ServiceInfo.FLAG_SINGLE_USER; - if (s.info.exported) { + if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) { Slog.w(TAG, "Service exported request ignored due to singleUser: " + s.className + " at " + mArchiveSourcePath + " " + parser.getPositionDescription()); s.info.exported = false; + setExported = true; } - setExported = true; } sa.recycle(); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index a78f8e2..ed3f9aa 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -31,11 +31,11 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Trace; +import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; import android.util.TypedValue; import android.util.LongSparseArray; @@ -104,10 +104,10 @@ public class Resources { // These are protected by mAccessLock. private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); - private final ThemedCaches<ConstantState> mDrawableCache = - new ThemedCaches<ConstantState>(); - private final ThemedCaches<ConstantState> mColorDrawableCache = - new ThemedCaches<ConstantState>(); + private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache = + new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); + private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = + new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = new LongSparseArray<WeakReference<ColorStateList>>(); @@ -702,12 +702,17 @@ public class Resources { * Context.obtainStyledAttributes} with * an array containing the resource ID of interest to create the TypedArray.</p> * + * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use + * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)} + * or {@link #getDrawable(int, Theme)} passing the desired theme.</p> + * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * @return Drawable An object that can be used to draw this resource. * @throws NotFoundException Throws NotFoundException if the given ID does * not exist. + * @see #getDrawable(int, Theme) */ public Drawable getDrawable(int id) throws NotFoundException { return getDrawable(id, null); @@ -715,7 +720,9 @@ public class Resources { /** * Return a drawable object associated with a particular resource ID and - * styled for the specified theme. + * styled for the specified theme. Various types of objects will be + * returned depending on the underlying resource -- for example, a solid + * color, PNG image, scalable image, etc. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource @@ -755,6 +762,11 @@ public class Resources { * image, scalable image, etc. The Drawable API hides these implementation * details. * + * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use + * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)} + * or {@link #getDrawableForDensity(int, int, Theme)} passing the desired + * theme.</p> + * * @param id The desired resource identifier, as generated by the aapt tool. * This integer encodes the package, type, and resource entry. * The value 0 is an invalid identifier. @@ -1261,18 +1273,17 @@ public class Resources { * any of the style's attributes are already defined in the theme, the * current values in the theme will be overwritten. * - * @param resid The resource ID of a style resource from which to + * @param resId The resource ID of a style resource from which to * obtain attribute values. * @param force If true, values in the style resource will always be * used in the theme; otherwise, they will only be used * if not already defined in the theme. */ - public void applyStyle(int resid, boolean force) { - AssetManager.applyThemeStyle(mTheme, resid, force); + public void applyStyle(int resId, boolean force) { + AssetManager.applyThemeStyle(mTheme, resId, force); - // TODO: In very rare cases, we may end up with a hybrid theme - // that can't map to a single theme ID. - mThemeResId = resid; + mThemeResId = resId; + mKey += Integer.toHexString(resId) + (force ? "! " : " "); } /** @@ -1288,6 +1299,7 @@ public class Resources { AssetManager.copyTheme(mTheme, other.mTheme); mThemeResId = other.mThemeResId; + mKey = other.mKey; } /** @@ -1577,6 +1589,9 @@ public class Resources { /** Resource identifier for the theme. */ private int mThemeResId = 0; + /** Unique key for the series of styles applied to this theme. */ + private String mKey = ""; + // Needed by layoutlib. /*package*/ long getNativeTheme() { return mTheme; @@ -1585,6 +1600,10 @@ public class Resources { /*package*/ int getAppliedStyleResId() { return mThemeResId; } + + /*package*/ String getKey() { + return mKey; + } } /** @@ -1740,7 +1759,8 @@ public class Resources { } private void clearDrawableCachesLocked( - ThemedCaches<ConstantState> caches, int configChanges) { + ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, + int configChanges) { final int N = caches.size(); for (int i = 0; i < N; i++) { clearDrawableCacheLocked(caches.valueAt(i), configChanges); @@ -1763,7 +1783,7 @@ public class Resources { configChanges, cs.getChangingConfigurations())) { if (DEBUG_CONFIG) { Log.d(TAG, "FLUSHING #0x" - + Long.toHexString(mDrawableCache.keyAt(i)) + + Long.toHexString(cache.keyAt(i)) + " / " + cs + " with changes: 0x" + Integer.toHexString(cs.getChangingConfigurations())); } @@ -2205,7 +2225,7 @@ public class Resources { } final boolean isColorDrawable; - final ThemedCaches<ConstantState> caches; + final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { @@ -2258,7 +2278,8 @@ public class Resources { } private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable, - ThemedCaches<ConstantState> caches, long key, Drawable dr) { + ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, + long key, Drawable dr) { final ConstantState cs = dr.getConstantState(); if (cs == null) { return; @@ -2287,8 +2308,12 @@ public class Resources { } } else { synchronized (mAccessLock) { - final LongSparseArray<WeakReference<ConstantState>> themedCache; - themedCache = caches.getOrCreate(theme == null ? 0 : theme.mThemeResId); + final String themeKey = theme == null ? "" : theme.mKey; + LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); + if (themedCache == null) { + themedCache = new LongSparseArray<WeakReference<ConstantState>>(1); + caches.put(themeKey, themedCache); + } themedCache.put(key, new WeakReference<ConstantState>(cs)); } } @@ -2327,12 +2352,12 @@ public class Resources { if (file.endsWith(".xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); - dr = Drawable.createFromXmlThemed(this, rp, theme); + dr = Drawable.createFromXml(this, rp, theme); rp.close(); } else { final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); - dr = Drawable.createFromResourceStreamThemed(this, value, is, file, null, theme); + dr = Drawable.createFromResourceStream(this, value, is, file, null); is.close(); } } catch (Exception e) { @@ -2347,7 +2372,9 @@ public class Resources { return dr; } - private Drawable getCachedDrawable(ThemedCaches<ConstantState> caches, long key, Theme theme) { + private Drawable getCachedDrawable( + ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, + long key, Theme theme) { synchronized (mAccessLock) { final int themeKey = theme != null ? theme.mThemeResId : 0; final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); @@ -2584,21 +2611,4 @@ public class Resources { updateConfiguration(null, null); mAssets.ensureStringBlocks(); } - - static class ThemedCaches<T> extends SparseArray<LongSparseArray<WeakReference<T>>> { - /** - * Returns the cache of drawables styled for the specified theme. - * <p> - * Drawables that have themeable attributes but were loaded without - * specifying a theme are cached at themeResId = 0. - */ - public LongSparseArray<WeakReference<T>> getOrCreate(int themeResId) { - LongSparseArray<WeakReference<T>> result = get(themeResId); - if (result == null) { - result = new LongSparseArray<WeakReference<T>>(1); - put(themeResId, result); - } - return result; - } - } } diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index c593e9e..c8de2f1 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -142,9 +142,10 @@ public final class Sensor { public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature"; /** - * A constant describing a proximity sensor type. + * A constant describing a proximity sensor type. This is a wake up sensor. * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values} * for more details. + * @see #isWakeUpSensor() */ public static final int TYPE_PROXIMITY = 8; @@ -307,8 +308,10 @@ public final class Sensor { * itself. The sensor continues to operate while the device is asleep * and will automatically wake the device to notify when significant * motion is detected. The application does not need to hold any wake - * locks for this sensor to trigger. + * locks for this sensor to trigger. This is a wake up sensor. * <p>See {@link TriggerEvent} for more details. + * + * @see #isWakeUpSensor() */ public static final int TYPE_SIGNIFICANT_MOTION = 17; @@ -381,11 +384,17 @@ public final class Sensor { /** * A constant describing a heart rate monitor. * <p> - * A sensor that measures the heart rate in beats per minute. + * The reported value is the heart rate in beats per minute. + * <p> + * The reported accuracy represents the status of the monitor during the reading. See the + * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager} + * for more details on accuracy/status values. In particular, when the accuracy is + * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate + * value should be discarded. * <p> - * value[0] represents the beats per minute when the measurement was taken. - * value[0] is 0 if the heart rate monitor could not measure the rate or the - * rate is 0 beat per minute. + * This sensor requires permission {@code android.permission.BODY_SENSORS}. + * It will not be returned by {@code SensorManager.getSensorsList} nor + * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission. */ public static final int TYPE_HEART_RATE = 21; @@ -397,6 +406,321 @@ public final class Sensor { public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate"; /** + * A non-wake up variant of proximity sensor. + * + * @see #TYPE_PROXIMITY + */ + public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22; + + /** + * A constant string describing a non-wake up proximity sensor type. + * + * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR + */ + public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = + "android.sensor.non_wake_up_proximity_sensor"; + + /** + * A constant describing a wake up variant of accelerometer sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ACCELEROMETER + */ + public static final int TYPE_WAKE_UP_ACCELEROMETER = 23; + + /** + * A constant string describing a wake up accelerometer. + * + * @see #TYPE_WAKE_UP_ACCELEROMETER + */ + public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER = + "android.sensor.wake_up_accelerometer"; + + /** + * A constant describing a wake up variant of a magnetic field sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_MAGNETIC_FIELD + */ + public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24; + + /** + * A constant string describing a wake up magnetic field sensor. + * + * @see #TYPE_WAKE_UP_MAGNETIC_FIELD + */ + public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD = + "android.sensor.wake_up_magnetic_field"; + + /** + * A constant describing a wake up variant of an orientation sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ORIENTATION + */ + public static final int TYPE_WAKE_UP_ORIENTATION = 25; + + /** + * A constant string describing a wake up orientation sensor. + * + * @see #TYPE_WAKE_UP_ORIENTATION + */ + public static final String STRING_TYPE_WAKE_UP_ORIENTATION = + "android.sensor.wake_up_orientation"; + + /** + * A constant describing a wake up variant of a gyroscope sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GYROSCOPE + */ + public static final int TYPE_WAKE_UP_GYROSCOPE = 26; + + /** + * A constant string describing a wake up gyroscope sensor type. + * + * @see #TYPE_WAKE_UP_GYROSCOPE + */ + public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope"; + + /** + * A constant describing a wake up variant of a light sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_LIGHT + */ + public static final int TYPE_WAKE_UP_LIGHT = 27; + + /** + * A constant string describing a wake up light sensor type. + * + * @see #TYPE_WAKE_UP_LIGHT + */ + public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light"; + + /** + * A constant describing a wake up variant of a pressure sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_PRESSURE + */ + public static final int TYPE_WAKE_UP_PRESSURE = 28; + + /** + * A constant string describing a wake up pressure sensor type. + * + * @see #TYPE_WAKE_UP_PRESSURE + */ + public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure"; + + /** + * A constant describing a wake up variant of a gravity sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GRAVITY + */ + public static final int TYPE_WAKE_UP_GRAVITY = 29; + + /** + * A constant string describing a wake up gravity sensor type. + * + * @see #TYPE_WAKE_UP_GRAVITY + */ + public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity"; + + /** + * A constant describing a wake up variant of a linear acceleration sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_LINEAR_ACCELERATION + */ + public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30; + + /** + * A constant string describing a wake up linear acceleration sensor type. + * + * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION + */ + public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION = + "android.sensor.wake_up_linear_acceleration"; + + /** + * A constant describing a wake up variant of a rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31; + + /** + * A constant string describing a wake up rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR = + "android.sensor.wake_up_rotation_vector"; + + /** + * A constant describing a wake up variant of a relative humidity sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_RELATIVE_HUMIDITY + */ + public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32; + + /** + * A constant string describing a wake up relative humidity sensor type. + * + * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY + */ + public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY = + "android.sensor.wake_up_relative_humidity"; + + /** + * A constant describing a wake up variant of an ambient temperature sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_AMBIENT_TEMPERATURE + */ + public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33; + + /** + * A constant string describing a wake up ambient temperature sensor type. + * + * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE + */ + public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE = + "android.sensor.wake_up_ambient_temperature"; + + /** + * A constant describing a wake up variant of an uncalibrated magnetic field sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED + */ + public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34; + + /** + * A constant string describing a wake up uncalibrated magnetic field sensor type. + * + * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED + */ + public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = + "android.sensor.wake_up_magnetic_field_uncalibrated"; + + /** + * A constant describing a wake up variant of a game rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GAME_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35; + + /** + * A constant string describing a wake up game rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR = + "android.sensor.wake_up_game_rotation_vector"; + + /** + * A constant describing a wake up variant of an uncalibrated gyroscope sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GYROSCOPE_UNCALIBRATED + */ + public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36; + + /** + * A constant string describing a wake up uncalibrated gyroscope sensor type. + * + * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED + */ + public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = + "android.sensor.wake_up_gyroscope_uncalibrated"; + + /** + * A constant describing a wake up variant of a step detector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_STEP_DETECTOR + */ + public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37; + + /** + * A constant string describing a wake up step detector sensor type. + * + * @see #TYPE_WAKE_UP_STEP_DETECTOR + */ + public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR = + "android.sensor.wake_up_step_detector"; + + /** + * A constant describing a wake up variant of a step counter sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_STEP_COUNTER + */ + public static final int TYPE_WAKE_UP_STEP_COUNTER = 38; + + /** + * A constant string describing a wake up step counter sensor type. + * + * @see #TYPE_WAKE_UP_STEP_COUNTER + */ + public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER = + "android.sensor.wake_up_step_counter"; + + /** + * A constant describing a wake up variant of a geomagnetic rotation vector sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR + */ + public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39; + + /** + * A constant string describing a wake up geomagnetic rotation vector sensor type. + * + * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR + */ + public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = + "android.sensor.wake_up_geomagnetic_rotation_vector"; + + /** + * A constant describing a wake up variant of a heart rate sensor type. + * + * @see #isWakeUpSensor() + * @see #TYPE_HEART_RATE + */ + public static final int TYPE_WAKE_UP_HEART_RATE = 40; + + /** + * A constant string describing a wake up heart rate sensor type. + * + * @see #TYPE_WAKE_UP_HEART_RATE + */ + public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate"; + + /** + * A sensor of this type generates an event each time a tilt event is detected. A tilt event + * is generated if the direction of the 2-seconds window average gravity changed by at + * least 35 degrees since the activation of the sensor. It is a wake up sensor. + * + * @see #isWakeUpSensor() + */ + public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41; + + /** + * A constant string describing a wake up tilt detector sensor type. + * + * @see #TYPE_WAKE_UP_TILT_DETECTOR + */ + public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR = + "android.sensor.wake_up_tilt_detector"; + + /** * A constant describing a wake gesture sensor. * <p> * Wake gesture sensors enable waking up the device based on a device specific motion. @@ -410,6 +734,7 @@ public final class Sensor { * the device. This sensor must be low power, as it is likely to be activated 24/7. * Values of events created by this sensors should not be used. * + * @see #isWakeUpSensor() * @hide This sensor is expected to only be used by the power manager */ public static final int TYPE_WAKE_GESTURE = 42; @@ -467,7 +792,29 @@ public final class Sensor { REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR - REPORTING_MODE_ON_CHANGE, 1 // SENSOR_TYPE_HEART_RATE_MONITOR + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_HEART_RATE_MONITOR + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR + // wake up variants of base sensors + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_LIGHT + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_PRESSURE + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GRAVITY + REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION + REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY + REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE + REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED + REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR + REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER + REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR + REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR + REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_WAKE_GESTURE }; static int getReportingMode(Sensor sensor) { @@ -525,6 +872,8 @@ public final class Sensor { private int mFifoMaxEventCount; private String mStringType; private String mRequiredPermission; + private int mMaxDelay; + private boolean mWakeUpSensor; Sensor() { } @@ -625,6 +974,51 @@ public final class Sensor { return mHandle; } + /** + * This value is defined only for continuous mode sensors. It is the delay between two + * sensor events corresponding to the lowest frequency that this sensor supports. When + * lower frequencies are requested through registerListener() the events will be generated + * at this frequency instead. It can be used to estimate when the batch FIFO may be full. + * Older devices may set this value to zero. Ignore this value in case it is negative + * or zero. + * + * @return The max delay for this sensor in microseconds. + */ + public int getMaxDelay() { + return mMaxDelay; + } + + /** + * Returns whether this sensor is a wake-up sensor. + * <p> + * Wake up sensors wake the application processor up when they have events to deliver. When a + * wake up sensor is registered to without batching enabled, each event will wake the + * application processor up. + * <p> + * When a wake up sensor is registered to with batching enabled, it + * wakes the application processor up when maxReportingLatency has elapsed or when the hardware + * FIFO storing the events from wake up sensors is getting full. + * <p> + * Non-wake up sensors never wake the application processor up. Their events are only reported + * when the application processor is awake, for example because the application holds a wake + * lock, or another source woke the application processor up. + * <p> + * When a non-wake up sensor is registered to without batching enabled, the measurements made + * while the application processor is asleep might be lost and never returned. + * <p> + * When a non-wake up sensor is registered to with batching enabled, the measurements made while + * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors. + * When this FIFO gets full, new events start overwriting older events. When the application + * then wakes up, the latest events are returned, and some old events might be lost. The number + * of events actually returned depends on the hardware FIFO size, as well as on what other + * sensors are activated. If losing sensor events is not acceptable during batching, you must + * use the wake-up version of the sensor. + * @return true if this is a wake up sensor, false otherwise. + */ + public boolean isWakeUpSensor() { + return mWakeUpSensor; + } + void setRange(float max, float res) { mMaxRange = max; mResolution = res; diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java index 677d244..0d859fb 100644 --- a/core/java/android/hardware/SensorEventListener.java +++ b/core/java/android/hardware/SensorEventListener.java @@ -39,11 +39,13 @@ public interface SensorEventListener { public void onSensorChanged(SensorEvent event); /** - * Called when the accuracy of a sensor has changed. - * <p>See {@link android.hardware.SensorManager SensorManager} - * for details. + * Called when the accuracy of the registered sensor has changed. + * + * <p>See the SENSOR_STATUS_* constants in + * {@link android.hardware.SensorManager SensorManager} for details. * - * @param accuracy The new accuracy of this sensor + * @param accuracy The new accuracy of this sensor, one of + * {@code SensorManager.SENSOR_STATUS_*} */ - public void onAccuracyChanged(Sensor sensor, int accuracy); + public void onAccuracyChanged(Sensor sensor, int accuracy); } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 5f2b5f0..25c7630 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -321,6 +321,13 @@ public abstract class SensorManager { /** + * The values returned by this sensor cannot be trusted because the sensor + * had no contact with what it was measuring (for example, the heart rate + * monitor is not in contact with the user). + */ + public static final int SENSOR_STATUS_NO_CONTACT = -1; + + /** * The values returned by this sensor cannot be trusted, calibration is * needed or the environment doesn't allow readings */ @@ -421,9 +428,10 @@ public abstract class SensorManager { * {@link SensorManager#getSensorList(int) getSensorList}. * * @param type - * of sensors requested + * of sensors requested * - * @return the default sensors matching the asked type. + * @return the default sensor matching the requested type if one exists and the application + * has the necessary permissions, or null otherwise. * * @see #getSensorList(int) * @see Sensor diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 8684a04..b66ec86 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -395,25 +395,12 @@ public class SystemSensorManager extends SensorManager { t.timestamp = timestamp; t.accuracy = inAccuracy; t.sensor = sensor; - switch (t.sensor.getType()) { - // Only report accuracy for sensors that support it. - case Sensor.TYPE_MAGNETIC_FIELD: - case Sensor.TYPE_ORIENTATION: - // call onAccuracyChanged() only if the value changes - final int accuracy = mSensorAccuracies.get(handle); - if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { - mSensorAccuracies.put(handle, t.accuracy); - mListener.onAccuracyChanged(t.sensor, t.accuracy); - } - break; - default: - // For other sensors, just report the accuracy once - if (mFirstEvent.get(handle) == false) { - mFirstEvent.put(handle, true); - mListener.onAccuracyChanged( - t.sensor, SENSOR_STATUS_ACCURACY_HIGH); - } - break; + + // call onAccuracyChanged() only if the value changes + final int accuracy = mSensorAccuracies.get(handle); + if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { + mSensorAccuracies.put(handle, t.accuracy); + mListener.onAccuracyChanged(t.sensor, t.accuracy); } mListener.onSensorChanged(t); } diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java index 1af575f..ca71e81 100644 --- a/core/java/android/hardware/camera2/CameraAccessException.java +++ b/core/java/android/hardware/camera2/CameraAccessException.java @@ -114,7 +114,10 @@ public class CameraAccessException extends AndroidException { mReason = problem; } - private static String getDefaultMessage(int problem) { + /** + * @hide + */ + public static String getDefaultMessage(int problem) { switch (problem) { case CAMERA_IN_USE: return "The camera device is in use already"; diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 8391209..7738d2d 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -508,14 +508,62 @@ public abstract class CameraCaptureSession implements AutoCloseable { } /** - * This method is called when an image capture has completed and the + * This method is called when an image capture makes partial forward progress; some + * (but not all) results from an image capture are available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple {@link #onCaptureProgressed} calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final {@link #onCaptureCompleted} call will always + * contain all the fields (in particular, the union of all the fields of all + * the partial results composing the total result).</p> + * + * <p>For each request, some result data might be available earlier than others. The typical + * delay between each partial result (per request) is a single frame interval. + * For performance-oriented use-cases, applications should query the metadata they need + * to make forward progress from the partial results and avoid waiting for the completed + * result.</p> + * + * <p>Each request will generate at least {@code 1} partial results, and at most + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p> + * + * <p>Depending on the request settings, the number of partial results per request + * will vary, although typically the partial count could be the same as long as the + * camera device subsystems enabled stay the same.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param partialResult The partial output metadata from the capture, which + * includes a subset of the {@link TotalCaptureResult} fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureProgressed(CameraDevice camera, + CaptureRequest request, CaptureResult partialResult) { + // default empty implementation + } + + /** + * This method is called when an image capture has fully completed and all the * result metadata is available. * + * <p>This callback will always fire after the last {@link #onCaptureProgressed}; + * in other words, no more partial results will be delivered once the completed result + * is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, consider + * using {@link #onCaptureProgressed} instead.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera The CameraDevice sending the callback. * @param request The request that was given to the CameraDevice - * @param result The output metadata from the capture, including the + * @param result The total output metadata from the capture, including the * final capture parameters and the state of the camera system during * capture. * @@ -525,7 +573,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @see #setRepeatingBurst */ public void onCaptureCompleted(CameraDevice camera, - CaptureRequest request, CaptureResult result) { + CaptureRequest request, TotalCaptureResult result) { // default empty implementation } @@ -563,24 +611,57 @@ public abstract class CameraCaptureSession implements AutoCloseable { * when a capture sequence finishes and all {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this listener. * + * <p>In total, there will be at least one result/failure returned by this listener + * before this callback is invoked. If the capture sequence is aborted before any + * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p> + * + * <p>The default implementation does nothing.</p> + * * @param camera * The CameraDevice sending the callback. * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. - * @param lastFrameNumber + * @param frameNumber * The last frame number (returned by {@link CaptureResult#getFrameNumber} * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. - * The last frame number may be equal to NO_FRAMES_CAPTURED if no images - * were captured for this sequence. This can happen, for example, when a - * repeating request or burst is cleared right after being set. * * @see CaptureResult#getFrameNumber() * @see CaptureFailure#getFrameNumber() * @see CaptureResult#getSequenceId() * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceAborted */ public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, int lastFrameNumber) { + int sequenceId, long frameNumber) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence aborts before any {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * <p>Due to the asynchronous nature of the camera device, not all submitted captures + * are immediately processed. It is possible to clear out the pending requests + * by a variety of operations such as {@link CameraDevice#stopRepeating} or + * {@link CameraDevice#flush}. When such an event happens, + * {@link #onCaptureSequenceCompleted} will not be called.</p> + * + * <p>The default implementation does nothing.</p> + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceCompleted + */ + public void onCaptureSequenceAborted(CameraDevice camera, + int sequenceId) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 7cc6d1d..2f5b4fe 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -16,7 +16,9 @@ package android.hardware.camera2; +import android.hardware.camera2.CaptureResult.Key; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.TypeReference; import android.util.Rational; import java.util.Collections; @@ -35,18 +37,109 @@ import java.util.List; * @see CameraDevice * @see CameraManager */ -public final class CameraCharacteristics extends CameraMetadata { +public final class CameraCharacteristics extends CameraMetadata<CameraCharacteristics.Key<?>> { + + /** + * A {@code Key} is used to do camera characteristics field lookups with + * {@link CameraCharacteristics#get}. + * + * <p>For example, to get the stream configuration map: + * <code><pre> + * StreamConfigurationMap map = cameraCharacteristics.get( + * CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + * </pre></code> + * </p> + * + * <p>To enumerate over all possible keys for {@link CameraCharacteristics}, see + * {@link CameraCharacteristics#getKeys()}.</p> + * + * @see CameraCharacteristics#get + * @see CameraCharacteristics#getKeys() + */ + public static final class Key<T> { + private final CameraMetadataNative.Key<T> mKey; + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, Class<T> type) { + mKey = new CameraMetadataNative.Key<T>(name, type); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, TypeReference<T> typeReference) { + mKey = new CameraMetadataNative.Key<T>(name, typeReference); + } + + /** + * Return a camelCase, period separated name formatted like: + * {@code "root.section[.subsections].name"}. + * + * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."}; + * keys that are device/platform-specific are prefixed with {@code "com."}.</p> + * + * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would + * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device + * specific key might look like {@code "com.google.nexus.data.private"}.</p> + * + * @return String representation of the key name + */ + public String getName() { + return mKey.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return mKey.hashCode(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public final boolean equals(Object o) { + return o instanceof Key && ((Key<T>)o).mKey.equals(mKey); + } + + /** + * Visible for CameraMetadataNative implementation only; do not use. + * + * TODO: Make this private or remove it altogether. + * + * @hide + */ + public CameraMetadataNative.Key<T> getNativeKey() { + return mKey; + } + + @SuppressWarnings({ + "unused", "unchecked" + }) + private Key(CameraMetadataNative.Key<?> nativeKey) { + mKey = (CameraMetadataNative.Key<T>) nativeKey; + } + } private final CameraMetadataNative mProperties; - private List<Key<?>> mAvailableRequestKeys; - private List<Key<?>> mAvailableResultKeys; + private List<CaptureRequest.Key<?>> mAvailableRequestKeys; + private List<CaptureResult.Key<?>> mAvailableResultKeys; /** * Takes ownership of the passed-in properties object * @hide */ public CameraCharacteristics(CameraMetadataNative properties) { - mProperties = properties; + mProperties = CameraMetadataNative.move(properties); } /** @@ -57,12 +150,55 @@ public final class CameraCharacteristics extends CameraMetadata { return new CameraMetadataNative(mProperties); } - @Override + /** + * Get a camera characteristics field value. + * + * <p>The field definitions can be + * found in {@link CameraCharacteristics}.</p> + * + * <p>Querying the value for the same key more than once will return a value + * which is equal to the previous queried value.</p> + * + * @throws IllegalArgumentException if the key was not valid + * + * @param key The characteristics field to read. + * @return The value of that key, or {@code null} if the field is not set. + */ public <T> T get(Key<T> key) { return mProperties.get(key); } /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected <T> T getProtected(Key<?> key) { + return (T) mProperties.get(key); + } + + /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected Class<Key<?>> getKeyClass() { + Object thisClass = Key.class; + return (Class<Key<?>>)thisClass; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Key<?>> getKeys() { + // Force the javadoc for this function to show up on the CameraCharacteristics page + return super.getKeys(); + } + + /** * Returns the list of keys supported by this {@link CameraDevice} for querying * with a {@link CaptureRequest}. * @@ -76,9 +212,14 @@ public final class CameraCharacteristics extends CameraMetadata { * * @return List of keys supported by this CameraDevice for CaptureRequests. */ - public List<Key<?>> getAvailableCaptureRequestKeys() { + @SuppressWarnings({"unchecked"}) + public List<CaptureRequest.Key<?>> getAvailableCaptureRequestKeys() { if (mAvailableRequestKeys == null) { - mAvailableRequestKeys = getAvailableKeyList(CaptureRequest.class); + Object crKey = CaptureRequest.Key.class; + Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey; + + mAvailableRequestKeys = Collections.unmodifiableList( + getAvailableKeyList(CaptureRequest.class, crKeyTyped)); } return mAvailableRequestKeys; } @@ -97,9 +238,14 @@ public final class CameraCharacteristics extends CameraMetadata { * * @return List of keys supported by this CameraDevice for CaptureResults. */ - public List<Key<?>> getAvailableCaptureResultKeys() { + @SuppressWarnings({"unchecked"}) + public List<CaptureResult.Key<?>> getAvailableCaptureResultKeys() { if (mAvailableResultKeys == null) { - mAvailableResultKeys = getAvailableKeyList(CaptureResult.class); + Object crKey = CaptureResult.Key.class; + Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey; + + mAvailableResultKeys = Collections.unmodifiableList( + getAvailableKeyList(CaptureResult.class, crKeyTyped)); } return mAvailableResultKeys; } @@ -113,12 +259,14 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>Each key is only listed once in the list. The order of the keys is undefined.</p> * * @param metadataClass The subclass of CameraMetadata that you want to get the keys for. + * @param keyClass The class of the metadata key, e.g. CaptureRequest.Key.class * * @return List of keys supported by this CameraDevice for metadataClass. * * @throws IllegalArgumentException if metadataClass is not a subclass of CameraMetadata */ - private <T extends CameraMetadata> List<Key<?>> getAvailableKeyList(Class<T> metadataClass) { + private <TKey> List<TKey> + getAvailableKeyList(Class<?> metadataClass, Class<TKey> keyClass) { if (metadataClass.equals(CameraMetadata.class)) { throw new AssertionError( @@ -128,7 +276,9 @@ public final class CameraCharacteristics extends CameraMetadata { "metadataClass must be a subclass of CameraMetadata"); } - return Collections.unmodifiableList(getKeysStatic(metadataClass, /*instance*/null)); + List<TKey> staticKeyList = CameraCharacteristics.<TKey>getKeysStatic( + metadataClass, keyClass, /*instance*/null); + return Collections.unmodifiableList(staticKeyList); } /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ @@ -146,8 +296,8 @@ public final class CameraCharacteristics extends CameraMetadata { * valid anti-banding modes that the application may request * for this camera device; they must include AUTO.</p> */ - public static final Key<byte[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = - new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class); + public static final Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES = + new Key<int[]>("android.control.aeAvailableAntibandingModes", int[].class); /** * <p>The set of auto-exposure modes that are supported by this @@ -165,15 +315,15 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#CONTROL_AE_MODE */ - public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES = - new Key<byte[]>("android.control.aeAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AE_AVAILABLE_MODES = + new Key<int[]>("android.control.aeAvailableModes", int[].class); /** * <p>List of frame rate ranges supported by the * AE algorithm/hardware</p> */ - public static final Key<int[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = - new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class); + public static final Key<android.util.Range<Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES = + new Key<android.util.Range<Integer>[]>("android.control.aeAvailableTargetFpsRanges", new TypeReference<android.util.Range<Integer>[]>() {{ }}); /** * <p>Maximum and minimum exposure compensation @@ -182,8 +332,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP */ - public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE = - new Key<int[]>("android.control.aeCompensationRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_COMPENSATION_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeCompensationRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Smallest step by which exposure compensation @@ -205,8 +355,8 @@ public final class CameraCharacteristics extends CameraMetadata { * @see CaptureRequest#CONTROL_AF_MODE * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE */ - public static final Key<byte[]> CONTROL_AF_AVAILABLE_MODES = - new Key<byte[]>("android.control.afAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AF_AVAILABLE_MODES = + new Key<int[]>("android.control.afAvailableModes", int[].class); /** * <p>List containing the subset of color effects @@ -224,8 +374,8 @@ public final class CameraCharacteristics extends CameraMetadata { * @see CaptureRequest#CONTROL_EFFECT_MODE * @see CaptureRequest#CONTROL_MODE */ - public static final Key<byte[]> CONTROL_AVAILABLE_EFFECTS = - new Key<byte[]>("android.control.availableEffects", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_EFFECTS = + new Key<int[]>("android.control.availableEffects", int[].class); /** * <p>List containing a subset of scene modes @@ -238,15 +388,15 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#CONTROL_SCENE_MODE */ - public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES = - new Key<byte[]>("android.control.availableSceneModes", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_SCENE_MODES = + new Key<int[]>("android.control.availableSceneModes", int[].class); /** * <p>List of video stabilization modes that can * be supported</p> */ - public static final Key<byte[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = - new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class); + public static final Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES = + new Key<int[]>("android.control.availableVideoStabilizationModes", int[].class); /** * <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) @@ -264,8 +414,8 @@ public final class CameraCharacteristics extends CameraMetadata { * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM * @see CaptureRequest#CONTROL_AWB_MODE */ - public static final Key<byte[]> CONTROL_AWB_AVAILABLE_MODES = - new Key<byte[]>("android.control.awbAvailableModes", byte[].class); + public static final Key<int[]> CONTROL_AWB_AVAILABLE_MODES = + new Key<int[]>("android.control.awbAvailableModes", int[].class); /** * <p>List of the maximum number of regions that can be used for metering in @@ -277,19 +427,53 @@ public final class CameraCharacteristics extends CameraMetadata { * @see CaptureRequest#CONTROL_AE_REGIONS * @see CaptureRequest#CONTROL_AF_REGIONS * @see CaptureRequest#CONTROL_AWB_REGIONS + * @hide */ public static final Key<int[]> CONTROL_MAX_REGIONS = new Key<int[]>("android.control.maxRegions", int[].class); /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-exposure (AE); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p> + * + * @see CaptureRequest#CONTROL_AE_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AE = + new Key<Integer>("android.control.maxRegionsAe", int.class); + + /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-white balance (AWB); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p> + * + * @see CaptureRequest#CONTROL_AWB_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AWB = + new Key<Integer>("android.control.maxRegionsAwb", int.class); + + /** + * <p>List of the maximum number of regions that can be used for metering in + * auto-focus (AF); + * this corresponds to the the maximum number of elements in + * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p> + * + * @see CaptureRequest#CONTROL_AF_REGIONS + */ + public static final Key<Integer> CONTROL_MAX_REGIONS_AF = + new Key<Integer>("android.control.maxRegionsAf", int.class); + + /** * <p>The set of edge enhancement modes supported by this camera device.</p> * <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p> * <p>Full-capability camera devices must always support OFF and FAST.</p> * * @see CaptureRequest#EDGE_MODE */ - public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES = - new Key<byte[]>("android.edge.availableEdgeModes", byte[].class); + public static final Key<int[]> EDGE_AVAILABLE_EDGE_MODES = + new Key<int[]>("android.edge.availableEdgeModes", int[].class); /** * <p>Whether this camera device has a @@ -308,8 +492,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#HOT_PIXEL_MODE */ - public static final Key<byte[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES = - new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].class); + public static final Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES = + new Key<int[]>("android.hotPixel.availableHotPixelModes", int[].class); /** * <p>Supported resolutions for the JPEG thumbnail</p> @@ -376,8 +560,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE */ - public static final Key<byte[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = - new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class); + public static final Key<int[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION = + new Key<int[]>("android.lens.info.availableOpticalStabilization", int[].class); /** * <p>Optional. Hyperfocal distance for this lens.</p> @@ -403,6 +587,7 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>Dimensions of lens shading map.</p> * <p>The map should be on the order of 30-40 rows and columns, and * must be smaller than 64x64.</p> + * @hide */ public static final Key<android.util.Size> LENS_INFO_SHADING_MAP_SIZE = new Key<android.util.Size>("android.lens.info.shadingMapSize", android.util.Size.class); @@ -441,8 +626,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#NOISE_REDUCTION_MODE */ - public static final Key<byte[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES = - new Key<byte[]>("android.noiseReduction.availableNoiseReductionModes", byte[].class); + public static final Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES = + new Key<int[]>("android.noiseReduction.availableNoiseReductionModes", int[].class); /** * <p>If set to 1, the HAL will always split result @@ -471,7 +656,7 @@ public final class CameraCharacteristics extends CameraMetadata { * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p> * <p>This lists the upper bound of the number of output streams supported by * the camera device. Using more streams simultaneously may require more hardware and - * CPU resources that will consume more power. The image format for a output stream can + * CPU resources that will consume more power. The image format for an output stream can * be any supported format provided by android.scaler.availableStreamConfigurations. * The formats defined in android.scaler.availableStreamConfigurations can be catergorized * into the 3 stream types as below:</p> @@ -482,11 +667,79 @@ public final class CameraCharacteristics extends CameraMetadata { * <li>Processed (but not-stalling): any non-RAW format without a stall duration. * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> * </ul> + * @hide */ public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS = new Key<int[]>("android.request.maxNumOutputStreams", int[].class); /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any <code>RAW</code> formats.</p> + * <p>This value contains the max number of output simultaneous + * streams from the raw sensor.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any <code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>In particular, a <code>RAW</code> format is typically one of:</p> + * <ul> + * <li>ImageFormat#RAW_SENSOR</li> + * <li>Opaque <code>RAW</code></li> + * </ul> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_RAW = + new Key<Integer>("android.request.maxNumOutputRaw", int.class); + + /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any processed (but not-stalling) formats.</p> + * <p>This value contains the max number of output simultaneous + * streams for any processed (but not-stalling) formats.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>Processed (but not-stalling) is defined as any non-RAW format without a stall duration. + * Typically:</p> + * <ul> + * <li>ImageFormat#YUV_420_888</li> + * <li>ImageFormat#NV21</li> + * <li>ImageFormat#YV12</li> + * <li>Implementation-defined formats, i.e. StreamConfiguration#isOutputSupportedFor(Class)</li> + * </ul> + * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with + * a processed format -- it will return 0 for a non-stalling stream.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC = + new Key<Integer>("android.request.maxNumOutputProc", int.class); + + /** + * <p>The maximum numbers of different types of output streams + * that can be configured and used simultaneously by a camera device + * for any processed (and stalling) formats.</p> + * <p>This value contains the max number of output simultaneous + * streams for any processed (but not-stalling) formats.</p> + * <p>This lists the upper bound of the number of output streams supported by + * the camera device. Using more streams simultaneously may require more hardware and + * CPU resources that will consume more power. The image format for this kind of an output stream can + * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p> + * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations > 0. + * Typically only the <code>JPEG</code> format (ImageFormat#JPEG)</p> + * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with + * a processed format -- it will return a non-0 value for a stalling stream.</p> + * + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP + */ + public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING = + new Key<Integer>("android.request.maxNumOutputProcStalling", int.class); + + /** * <p>The maximum numbers of any type of input streams * that can be configured and used simultaneously by a camera device.</p> * <p>When set to 0, it means no input stream is supported.</p> @@ -557,7 +810,6 @@ public final class CameraCharacteristics extends CameraMetadata { * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL devices:</p> * <ul> * <li>MANUAL_SENSOR</li> - * <li>ZSL</li> * </ul> * <p>Other capabilities may be available on either FULL or LIMITED * devices, but the app. should query this field to be sure.</p> @@ -566,12 +818,12 @@ public final class CameraCharacteristics extends CameraMetadata { * @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE * @see #REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR - * @see #REQUEST_AVAILABLE_CAPABILITIES_GCAM + * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING * @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG */ - public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES = - new Key<Integer>("android.request.availableCapabilities", int.class); + public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = + new Key<int[]>("android.request.availableCapabilities", int[].class); /** * <p>A list of all keys that the camera device has available @@ -600,7 +852,7 @@ public final class CameraCharacteristics extends CameraMetadata { * value.</p> * <p>The following keys may return <code>null</code> unless they are enabled:</p> * <ul> - * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li> + * <li>android.statistics.lensShadingMap (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li> * </ul> * <p>(Those sometimes-null keys should nevertheless be listed here * if they are available.)</p> @@ -611,7 +863,6 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>TODO: This should be used by #getAvailableCaptureResultKeys.</p> * * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @hide */ @@ -1046,6 +1297,28 @@ public final class CameraCharacteristics extends CameraMetadata { new Key<android.hardware.camera2.params.StreamConfigurationMap>("android.scaler.streamConfigurationMap", android.hardware.camera2.params.StreamConfigurationMap.class); /** + * <p>The crop type that this camera device supports.</p> + * <p>When passing a non-centered crop region ({@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}) to a camera + * device that only supports CENTER_ONLY cropping, the camera device will move the + * crop region to the center of the sensor active array ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}) + * and keep the crop region width and height unchanged. The camera device will return the + * final used crop region in metadata result {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</p> + * <p>Camera devices that support FREEFORM cropping will support any crop region that + * is inside of the active array. The camera device will apply the same crop region and + * return the final used crop region in capture result metadata {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</p> + * <p>FULL capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL) will support + * FREEFORM cropping.</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SCALER_CROP_REGION + * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE + * @see #SCALER_CROPPING_TYPE_CENTER_ONLY + * @see #SCALER_CROPPING_TYPE_FREEFORM + */ + public static final Key<Integer> SCALER_CROPPING_TYPE = + new Key<Integer>("android.scaler.croppingType", int.class); + + /** * <p>Area of raw data which corresponds to only * active pixels.</p> * <p>It is smaller or equal to @@ -1057,8 +1330,8 @@ public final class CameraCharacteristics extends CameraMetadata { /** * <p>Range of valid sensitivities</p> */ - public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE = - new Key<int[]>("android.sensor.info.sensitivityRange", int[].class); + public static final Key<android.util.Range<Integer>> SENSOR_INFO_SENSITIVITY_RANGE = + new Key<android.util.Range<Integer>>("android.sensor.info.sensitivityRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Arrangement of color filters on sensor; @@ -1079,8 +1352,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE = - new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class); + public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE = + new Key<android.util.Range<Long>>("android.sensor.info.exposureTimeRange", new TypeReference<android.util.Range<Long>>() {{ }}); /** * <p>Maximum possible frame duration (minimum frame @@ -1104,8 +1377,8 @@ public final class CameraCharacteristics extends CameraMetadata { * array</p> * <p>Needed for FOV calculation for old API</p> */ - public static final Key<float[]> SENSOR_INFO_PHYSICAL_SIZE = - new Key<float[]>("android.sensor.info.physicalSize", float[].class); + public static final Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE = + new Key<android.util.SizeF>("android.sensor.info.physicalSize", android.util.SizeF.class); /** * <p>Dimensions of full pixel array, possibly @@ -1211,8 +1484,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 = - new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A per-device calibration transform matrix that maps from the @@ -1232,8 +1505,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 = - new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms color values from CIE XYZ color space to @@ -1254,8 +1527,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 = - new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms color values from CIE XYZ color space to @@ -1278,8 +1551,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 = - new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms white balanced camera colors from the reference @@ -1298,8 +1571,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1 */ - public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 = - new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix1", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A matrix that transforms white balanced camera colors from the reference @@ -1320,21 +1593,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2 */ - public static final Key<Rational[]> SENSOR_FORWARD_MATRIX2 = - new Key<Rational[]>("android.sensor.forwardMatrix2", Rational[].class); - - /** - * <p>Gain factor from electrons to raw units when - * ISO=100</p> - * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - * <p><b>Full capability</b> - - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> - * - * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL - */ - public static final Key<Rational> SENSOR_BASE_GAIN_FACTOR = - new Key<Rational>("android.sensor.baseGainFactor", Rational.class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2 = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix2", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>A fixed black level offset for each of the color filter arrangement @@ -1401,8 +1661,8 @@ public final class CameraCharacteristics extends CameraMetadata { * android.statistics.faceIds and * android.statistics.faceLandmarks outputs.</p> */ - public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = - new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class); + public static final Key<int[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES = + new Key<int[]>("android.statistics.info.availableFaceDetectModes", int[].class); /** * <p>Maximum number of simultaneously detectable @@ -1425,19 +1685,16 @@ public final class CameraCharacteristics extends CameraMetadata { /** * <p>Maximum number of supported points in the - * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, or - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, or {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.</p> + * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p> * <p>If the actual number of points provided by the application (in - * android.tonemap.curve*) is less than max, the camera device will + * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is less than max, the camera device will * resample the curve to its internal representation, using linear * interpolation.</p> * <p>The output curves in the result metadata may have a different number * of points than the input curves, and will represent the actual * hardware curves used as closely as possible when linearly interpolated.</p> * - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE */ public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS = new Key<Integer>("android.tonemap.maxCurvePoints", int.class); @@ -1450,8 +1707,8 @@ public final class CameraCharacteristics extends CameraMetadata { * * @see CaptureRequest#TONEMAP_MODE */ - public static final Key<byte[]> TONEMAP_AVAILABLE_TONE_MAP_MODES = - new Key<byte[]>("android.tonemap.availableToneMapModes", byte[].class); + public static final Key<int[]> TONEMAP_AVAILABLE_TONE_MAP_MODES = + new Key<int[]>("android.tonemap.availableToneMapModes", int[].class); /** * <p>A list of camera LEDs that are available on this system.</p> @@ -1467,15 +1724,16 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>A FULL device has the most support possible and will enable the * widest range of use cases such as:</p> * <ul> - * <li>30 FPS at maximum resolution (== sensor resolution)</li> - * <li>Per frame control</li> - * <li>Manual sensor control</li> - * <li>Zero Shutter Lag (ZSL)</li> + * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li> + * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li> + * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li> + * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li> * </ul> * <p>A LIMITED device may have some or none of the above characteristics. * To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p> * * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + * @see CameraCharacteristics#SYNC_MAX_LATENCY * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL */ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 77640d1..6f5099b 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -766,14 +766,62 @@ public interface CameraDevice extends AutoCloseable { } /** - * This method is called when an image capture has completed and the + * This method is called when an image capture makes partial forward progress; some + * (but not all) results from an image capture are available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple {@link #onCaptureProgressed} calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final {@link #onCaptureCompleted} call will always + * contain all the fields (in particular, the union of all the fields of all + * the partial results composing the total result).</p> + * + * <p>For each request, some result data might be available earlier than others. The typical + * delay between each partial result (per request) is a single frame interval. + * For performance-oriented use-cases, applications should query the metadata they need + * to make forward progress from the partial results and avoid waiting for the completed + * result.</p> + * + * <p>Each request will generate at least {@code 1} partial results, and at most + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p> + * + * <p>Depending on the request settings, the number of partial results per request + * will vary, although typically the partial count could be the same as long as the + * camera device subsystems enabled stay the same.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param partialResult The partial output metadata from the capture, which + * includes a subset of the {@link TotalCaptureResult} fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + */ + public void onCaptureProgressed(CameraDevice camera, + CaptureRequest request, CaptureResult partialResult) { + // default empty implementation + } + + /** + * This method is called when an image capture has fully completed and all the * result metadata is available. * + * <p>This callback will always fire after the last {@link #onCaptureProgressed}; + * in other words, no more partial results will be delivered once the completed result + * is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, consider + * using {@link #onCaptureProgressed} instead.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera The CameraDevice sending the callback. * @param request The request that was given to the CameraDevice - * @param result The output metadata from the capture, including the + * @param result The total output metadata from the capture, including the * final capture parameters and the state of the camera system during * capture. * @@ -783,7 +831,7 @@ public interface CameraDevice extends AutoCloseable { * @see #setRepeatingBurst */ public void onCaptureCompleted(CameraDevice camera, - CaptureRequest request, CaptureResult result) { + CaptureRequest request, TotalCaptureResult result) { // default empty implementation } @@ -796,6 +844,10 @@ public interface CameraDevice extends AutoCloseable { * the capture may have been pushed to their respective output * streams.</p> * + * <p>Some partial results may have been delivered before the capture fails; + * however after this callback fires, no more partial results will be delivered by + * {@link #onCaptureProgressed}.</p> + * * <p>The default implementation of this method does nothing.</p> * * @param camera @@ -821,24 +873,57 @@ public interface CameraDevice extends AutoCloseable { * when a capture sequence finishes and all {@link CaptureResult} * or {@link CaptureFailure} for it have been returned via this listener. * + * <p>In total, there will be at least one result/failure returned by this listener + * before this callback is invoked. If the capture sequence is aborted before any + * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p> + * + * <p>The default implementation does nothing.</p> + * * @param camera * The CameraDevice sending the callback. * @param sequenceId * A sequence ID returned by the {@link #capture} family of functions. - * @param lastFrameNumber + * @param frameNumber * The last frame number (returned by {@link CaptureResult#getFrameNumber} * or {@link CaptureFailure#getFrameNumber}) in the capture sequence. - * The last frame number may be equal to NO_FRAMES_CAPTURED if no images - * were captured for this sequence. This can happen, for example, when a - * repeating request or burst is cleared right after being set. * * @see CaptureResult#getFrameNumber() * @see CaptureFailure#getFrameNumber() * @see CaptureResult#getSequenceId() * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceAborted */ public void onCaptureSequenceCompleted(CameraDevice camera, - int sequenceId, int lastFrameNumber) { + int sequenceId, long frameNumber) { + // default empty implementation + } + + /** + * This method is called independently of the others in CaptureListener, + * when a capture sequence aborts before any {@link CaptureResult} + * or {@link CaptureFailure} for it have been returned via this listener. + * + * <p>Due to the asynchronous nature of the camera device, not all submitted captures + * are immediately processed. It is possible to clear out the pending requests + * by a variety of operations such as {@link CameraDevice#stopRepeating} or + * {@link CameraDevice#flush}. When such an event happens, + * {@link #onCaptureSequenceCompleted} will not be called.</p> + * + * <p>The default implementation does nothing.</p> + * + * @param camera + * The CameraDevice sending the callback. + * @param sequenceId + * A sequence ID returned by the {@link #capture} family of functions. + * + * @see CaptureResult#getFrameNumber() + * @see CaptureFailure#getFrameNumber() + * @see CaptureResult#getSequenceId() + * @see CaptureFailure#getSequenceId() + * @see #onCaptureSequenceCompleted + */ + public void onCaptureSequenceAborted(CameraDevice camera, + int sequenceId) { // default empty implementation } } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 03b342c..4a89fe7 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -84,9 +84,8 @@ public final class CameraManager { try { CameraBinderDecorator.throwOnError( CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); - } catch(CameraRuntimeException e) { - throw new IllegalStateException("Failed to setup camera vendor tags", - e.asChecked()); + } catch (CameraRuntimeException e) { + handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); } try { @@ -220,6 +219,7 @@ public final class CameraManager { private CameraDevice openCameraDeviceUserAsync(String cameraId, CameraDevice.StateListener listener, Handler handler) throws CameraAccessException { + CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; try { @@ -231,7 +231,8 @@ public final class CameraManager { new android.hardware.camera2.impl.CameraDevice( cameraId, listener, - handler); + handler, + characteristics); BinderHolder holder = new BinderHolder(); @@ -424,6 +425,18 @@ public final class CameraManager { return mDeviceIdList; } + private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) { + int problem = e.getReason(); + switch (problem) { + case CameraAccessException.CAMERA_DISCONNECTED: + String errorMsg = CameraAccessException.getDefaultMessage(problem); + Log.w(TAG, msg + ": " + errorMsg); + break; + default: + throw new IllegalStateException(msg, e.asChecked()); + } + } + // TODO: this class needs unit tests // TODO: extract class into top level private class CameraServiceListener extends ICameraServiceListener.Stub { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index a11390d..b3e165e 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -16,8 +16,7 @@ package android.hardware.camera2; -import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.utils.TypeReference; +import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -36,7 +35,7 @@ import java.util.List; * * <p> * All instances of CameraMetadata are immutable. The list of keys with {@link #getKeys()} - * never changes, nor do the values returned by any key with {@link #get} throughout + * never changes, nor do the values returned by any key with {@code #get} throughout * the lifetime of the object. * </p> * @@ -44,7 +43,10 @@ import java.util.List; * @see CameraManager * @see CameraCharacteristics **/ -public abstract class CameraMetadata { +public abstract class CameraMetadata<TKey> { + + private static final String TAG = "CameraMetadataAb"; + private static final boolean VERBOSE = false; /** * Set a camera metadata field to a value. The field definitions can be @@ -74,8 +76,15 @@ public abstract class CameraMetadata { * * @param key The metadata field to read. * @return The value of that key, or {@code null} if the field is not set. + * + * @hide */ - public abstract <T> T get(Key<T> key); + protected abstract <T> T getProtected(TKey key); + + /** + * @hide + */ + protected abstract Class<TKey> getKeyClass(); /** * Returns a list of the keys contained in this map. @@ -83,14 +92,16 @@ public abstract class CameraMetadata { * <p>The list returned is not modifiable, so any attempts to modify it will throw * a {@code UnsupportedOperationException}.</p> * - * <p>All values retrieved by a key from this list with {@link #get} are guaranteed to be + * <p>All values retrieved by a key from this list with {@code #get} are guaranteed to be * non-{@code null}. Each key is only listed once in the list. The order of the keys * is undefined.</p> * * @return List of the keys contained in this map. */ - public List<Key<?>> getKeys() { - return Collections.unmodifiableList(getKeysStatic(this.getClass(), this)); + @SuppressWarnings("unchecked") + public List<TKey> getKeys() { + Class<CameraMetadata<TKey>> thisClass = (Class<CameraMetadata<TKey>>) getClass(); + return Collections.unmodifiableList(getKeysStatic(thisClass, getKeyClass(), this)); } /** @@ -101,24 +112,31 @@ public abstract class CameraMetadata { * Optionally, if {@code instance} is not null, then filter out any keys with null values. * </p> */ - /*package*/ static ArrayList<Key<?>> getKeysStatic(Class<? extends CameraMetadata> type, - CameraMetadata instance) { - ArrayList<Key<?>> keyList = new ArrayList<Key<?>>(); + /*package*/ @SuppressWarnings("unchecked") + static <TKey> ArrayList<TKey> getKeysStatic( + Class<?> type, Class<TKey> keyClass, + CameraMetadata<TKey> instance) { + + if (VERBOSE) Log.v(TAG, "getKeysStatic for " + type); + + ArrayList<TKey> keyList = new ArrayList<TKey>(); Field[] fields = type.getDeclaredFields(); for (Field field : fields) { // Filter for Keys that are public - if (field.getType().isAssignableFrom(Key.class) && + if (field.getType().isAssignableFrom(keyClass) && (field.getModifiers() & Modifier.PUBLIC) != 0) { - Key<?> key; + + TKey key; try { - key = (Key<?>) field.get(instance); + key = (TKey) field.get(instance); } catch (IllegalAccessException e) { throw new AssertionError("Can't get IllegalAccessException", e); } catch (IllegalArgumentException e) { throw new AssertionError("Can't get IllegalArgumentException", e); } - if (instance == null || instance.get(key) != null) { + + if (instance == null || instance.getProtected(key) != null) { keyList.add(key); } } @@ -127,113 +145,6 @@ public abstract class CameraMetadata { return keyList; } - // TODO: make final or abstract - public static class Key<T> { - - private boolean mHasTag; - private int mTag; - private final Class<T> mType; - private final TypeReference<T> mTypeReference; - private final String mName; - - /** - * @hide - */ - public Key(String name, Class<T> type) { - if (name == null) { - throw new NullPointerException("Key needs a valid name"); - } else if (type == null) { - throw new NullPointerException("Type needs to be non-null"); - } - mName = name; - mType = type; - mTypeReference = TypeReference.createSpecializedTypeReference(type); - } - - /** - * @hide - */ - @SuppressWarnings("unchecked") - public Key(String name, TypeReference<T> typeReference) { - if (name == null) { - throw new NullPointerException("Key needs a valid name"); - } else if (typeReference == null) { - throw new NullPointerException("TypeReference needs to be non-null"); - } - mName = name; - mType = (Class<T>)typeReference.getRawType(); - mTypeReference = typeReference; - } - - public final String getName() { - return mName; - } - - @Override - public final int hashCode() { - return mName.hashCode() ^ mTypeReference.hashCode(); - } - - @Override - public final boolean equals(Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof Key)) { - return false; - } - - Key<?> lhs = (Key<?>)o; - return mName.equals(lhs.mName) && mTypeReference.equals(lhs.mTypeReference); - } - - /** - * <p> - * Get the tag corresponding to this key. This enables insertion into the - * native metadata. - * </p> - * - * <p>This value is looked up the first time, and cached subsequently.</p> - * - * @return The tag numeric value corresponding to the string - * - * @hide - */ - public final int getTag() { - if (!mHasTag) { - mTag = CameraMetadataNative.getTag(mName); - mHasTag = true; - } - return mTag; - } - - /** - * Get the raw class backing the type {@code T} for this key. - * - * <p>The distinction is only important if {@code T} is a generic, e.g. - * {@code Range<Integer>} since the nested type will be erased.</p> - * - * @hide - */ - public final Class<T> getType() { - // TODO: remove this; other places should use #getTypeReference() instead - return mType; - } - - /** - * Get the type reference backing the type {@code T} for this key. - * - * <p>The distinction is only important if {@code T} is a generic, e.g. - * {@code Range<Integer>} since the nested type will be retained.</p> - * - * @hide - */ - public final TypeReference<T> getTypeReference() { - return mTypeReference; - } - } - /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * The enum values below this point are generated from metadata * definitions in /system/media/camera/docs. Do not modify by hand or @@ -325,9 +236,17 @@ public abstract class CameraMetadata { /** * <p>The camera device can be manually controlled (3A algorithms such - * as auto exposure, and auto focus can be - * bypassed), this includes but is not limited to:</p> + * as auto exposure, and auto focus can be bypassed). + * The camera device supports basic manual control of the sensor image + * acquisition related stages. This means the following controls are + * guaranteed to be supported:</p> * <ul> + * <li>Manual frame duration control<ul> + * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li> + * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li> + * </ul> + * </li> * <li>Manual exposure control<ul> * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li> * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li> @@ -336,7 +255,6 @@ public abstract class CameraMetadata { * <li>Manual sensitivity control<ul> * <li>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</li> * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li> - * <li>{@link CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR android.sensor.baseGainFactor}</li> * </ul> * </li> * <li>Manual lens control<ul> @@ -355,11 +273,15 @@ public abstract class CameraMetadata { * <p>If any of the above 3A algorithms are enabled, then the camera * device will accurately report the values applied by 3A in the * result.</p> + * <p>A given camera device may also support additional manual sensor controls, + * but this capability only covers the above list of controls.</p> * * @see CaptureRequest#BLACK_LEVEL_LOCK - * @see CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR + * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CaptureRequest#SENSOR_EXPOSURE_TIME + * @see CaptureRequest#SENSOR_FRAME_DURATION * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE + * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE * @see CaptureRequest#SENSOR_SENSITIVITY * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -367,12 +289,12 @@ public abstract class CameraMetadata { public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2; /** - * <p>TODO: This should be @hide</p> + * <p>The camera device post-processing stages can be manually controlled. + * The camera device supports basic manual control of the image post-processing + * stages. This means the following controls are guaranteed to be supported:</p> * <ul> * <li>Manual tonemap control<ul> - * <li>{@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}</li> - * <li>{@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}</li> - * <li>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}</li> + * <li>{@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}</li> * <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li> * <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li> * </ul> @@ -383,8 +305,8 @@ public abstract class CameraMetadata { * </ul> * </li> * <li>Lens shading map information<ul> - * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}</li> - * <li>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}</li> + * <li>android.statistics.lensShadingMap</li> + * <li>android.lens.info.shadingMapSize</li> * </ul> * </li> * </ul> @@ -392,19 +314,17 @@ public abstract class CameraMetadata { * will accurately report the values applied by AWB in the result.</p> * <p>The camera device will also support everything in MANUAL_SENSOR * except manual lens control and manual flash control.</p> + * <p>A given camera device may also support additional post-processing + * controls, but this capability only covers the above list of controls.</p> * * @see CaptureRequest#COLOR_CORRECTION_GAINS * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ - public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3; + public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3; /** * <p>The camera device supports the Zero Shutter Lag use case.</p> @@ -446,6 +366,22 @@ public abstract class CameraMetadata { public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5; // + // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE + // + + /** + * <p>The camera device will only support centered crop regions.</p> + * @see CameraCharacteristics#SCALER_CROPPING_TYPE + */ + public static final int SCALER_CROPPING_TYPE_CENTER_ONLY = 0; + + /** + * <p>The camera device will support arbitrarily chosen crop regions.</p> + * @see CameraCharacteristics#SCALER_CROPPING_TYPE + */ + public static final int SCALER_CROPPING_TYPE_FREEFORM = 1; + + // // Enumeration values for CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT // @@ -1365,8 +1301,7 @@ public abstract class CameraMetadata { /** * <p>If the flash is available and charged, fire flash - * for this capture based on android.flash.firingPower and - * android.flash.firingTime.</p> + * for this capture.</p> * @see CaptureRequest#FLASH_MODE */ public static final int FLASH_MODE_SINGLE = 1; @@ -1624,17 +1559,14 @@ public abstract class CameraMetadata { /** * <p>Use the tone mapping curve specified in - * the android.tonemap.curve* entries.</p> + * the {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}* entries.</p> * <p>All color enhancement and tonemapping must be disabled, except * for applying the tonemapping curve specified by - * {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}, or - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}.</p> + * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p> * <p>Must not slow down frame rate relative to raw * sensor output.</p> * - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE */ public static final int TONEMAP_MODE_CONTRAST_CURVE = 0; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 54ffd6b..d4dfdd5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -16,7 +16,9 @@ package android.hardware.camera2; +import android.hardware.camera2.CameraCharacteristics.Key; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.utils.TypeReference; import android.os.Parcel; import android.os.Parcelable; import android.util.Rational; @@ -25,6 +27,7 @@ import android.view.Surface; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Objects; @@ -58,7 +61,98 @@ import java.util.Objects; * @see CameraDevice#setRepeatingRequest * @see CameraDevice#createCaptureRequest */ -public final class CaptureRequest extends CameraMetadata implements Parcelable { +public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> + implements Parcelable { + + /** + * A {@code Key} is used to do capture request field lookups with + * {@link CaptureResult#get} or to set fields with + * {@link CaptureRequest.Builder#set(Key, Object)}. + * + * <p>For example, to set the crop rectangle for the next capture: + * <code><pre> + * Rect cropRectangle = new Rect(0, 0, 640, 480); + * captureRequestBuilder.set(SCALER_CROP_REGION, cropRectangle); + * </pre></code> + * </p> + * + * <p>To enumerate over all possible keys for {@link CaptureResult}, see + * {@link CameraCharacteristics#getAvailableCaptureResultKeys}.</p> + * + * @see CaptureResult#get + * @see CameraCharacteristics#getAvailableCaptureResultKeys + */ + public final static class Key<T> { + private final CameraMetadataNative.Key<T> mKey; + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, Class<T> type) { + mKey = new CameraMetadataNative.Key<T>(name, type); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, TypeReference<T> typeReference) { + mKey = new CameraMetadataNative.Key<T>(name, typeReference); + } + + /** + * Return a camelCase, period separated name formatted like: + * {@code "root.section[.subsections].name"}. + * + * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."}; + * keys that are device/platform-specific are prefixed with {@code "com."}.</p> + * + * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would + * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device + * specific key might look like {@code "com.google.nexus.data.private"}.</p> + * + * @return String representation of the key name + */ + public String getName() { + return mKey.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return mKey.hashCode(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public final boolean equals(Object o) { + return o instanceof Key && ((Key<T>)o).mKey.equals(mKey); + } + + /** + * Visible for CameraMetadataNative implementation only; do not use. + * + * TODO: Make this private or remove it altogether. + * + * @hide + */ + public CameraMetadataNative.Key<T> getNativeKey() { + return mKey; + } + + @SuppressWarnings({ "unchecked" }) + /*package*/ Key(CameraMetadataNative.Key<?> nativeKey) { + mKey = (CameraMetadataNative.Key<T>) nativeKey; + } + } private final HashSet<Surface> mSurfaceSet; private final CameraMetadataNative mSettings; @@ -93,17 +187,58 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * Used by the Builder to create a mutable CaptureRequest. */ private CaptureRequest(CameraMetadataNative settings) { - mSettings = settings; + mSettings = CameraMetadataNative.move(settings); mSurfaceSet = new HashSet<Surface>(); } - @SuppressWarnings("unchecked") - @Override + /** + * Get a capture request field value. + * + * <p>The field definitions can be found in {@link CaptureRequest}.</p> + * + * <p>Querying the value for the same key more than once will return a value + * which is equal to the previous queried value.</p> + * + * @throws IllegalArgumentException if the key was not valid + * + * @param key The result field to read. + * @return The value of that key, or {@code null} if the field is not set. + */ public <T> T get(Key<T> key) { return mSettings.get(key); } /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected <T> T getProtected(Key<?> key) { + return (T) mSettings.get(key); + } + + /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected Class<Key<?>> getKeyClass() { + Object thisClass = Key.class; + return (Class<Key<?>>)thisClass; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Key<?>> getKeys() { + // Force the javadoc for this function to show up on the CaptureRequest page + return super.getKeys(); + } + + /** * Retrieve the tag for this request, if any. * * <p>This tag is not used for anything by the camera device, but can be @@ -402,30 +537,24 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = - new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>Gains applying to Bayer raw color channels for * white-balance.</p> - * <p>The 4-channel white-balance gains are defined in - * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain - * for green pixels on even rows of the output, and <code>G_odd</code> - * is the gain for green pixels on the odd rows. if a HAL - * does not support a separate gain for even/odd green channels, - * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to - * <code>G_even</code> in the output result metadata.</p> - * <p>This array is either set by the camera device when the request - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or - * directly by the application in the request when the - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> - * <p>The output should be the gains actually applied by the camera device to - * the current frame.</p> + * <p>These per-channel gains are either set by the camera device + * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not + * TRANSFORM_MATRIX, or directly by the application in the + * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is + * TRANSFORM_MATRIX.</p> + * <p>The gains in the result metadata are the gains actually + * applied by the camera device to the current frame.</p> * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<float[]> COLOR_CORRECTION_GAINS = - new Key<float[]>("android.colorCorrection.gains", float[].class); + public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS = + new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class); /** * <p>The desired setting for the camera device's auto-exposure @@ -558,26 +687,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { /** * <p>List of areas to use for * metering.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AE_REGIONS = - new Key<int[]>("android.control.aeRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Range over which fps can be adjusted to @@ -587,8 +717,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = - new Key<int[]>("android.control.aeTargetFpsRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Whether the camera device will trigger a precapture @@ -633,26 +763,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { /** * <p>List of areas to use for focus * estimation.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific focus area - * needs to be used by the camera device. If the focusing region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AF_REGIONS = - new Key<int[]>("android.control.afRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Whether the camera device will trigger autofocus for this request.</p> @@ -719,27 +850,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { /** * <p>List of areas to use for illuminant * estimation.</p> - * <p>Only used in AUTO mode.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area - * needs to be used by the camera device. If the AWB region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AWB_REGIONS = - new Key<int[]>("android.control.awbRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Information to the camera device 3A (auto-exposure, @@ -933,8 +1064,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { new Key<Integer>("android.hotPixel.mode", int.class); /** + * <p>A location object to use when generating image GPS metadata.</p> + */ + public static final Key<android.location.Location> JPEG_GPS_LOCATION = + new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class); + + /** * <p>GPS coordinates to include in output JPEG * EXIF</p> + * @hide */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); @@ -942,6 +1080,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { /** * <p>32 characters describing GPS algorithm to * include in EXIF</p> + * @hide */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); @@ -949,6 +1088,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { /** * <p>Time GPS fix was made to include in * EXIF</p> + * @hide */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); @@ -981,6 +1121,12 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * but the captured JPEG will still be a valid image.</p> * <p>When a jpeg image capture is issued, the thumbnail size selected should have * the same aspect ratio as the jpeg image.</p> + * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect + * ratio, the camera device creates the thumbnail by cropping it from the primary image. + * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has + * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to + * generate the thumbnail image. The thumbnail image will always have a smaller Field + * Of View (FOV) than the primary image when aspect ratios differ.</p> */ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE = new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class); @@ -1126,8 +1272,11 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * output, cropping to a smaller region if necessary to * maintain the stream's aspect ratio.</p> * <p>HAL2.x uses only (x, y, width)</p> - * <p>Any additional per-stream cropping must be done to - * maximize the final pixel area of the stream.</p> + * <p>The crop region is applied after the RAW to other color space (e.g. YUV) + * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, + * it is not croppable. The crop region will be ignored by raw streams.</p> + * <p>For non-raw streams, any additional per-stream cropping will + * be done to maximize the final pixel area of the stream.</p> * <p>For example, if the crop region is set to a 4:3 aspect * ratio, then 4:3 streams should use the exact crop * region. 16:9 streams should further crop vertically @@ -1294,8 +1443,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>When set to OFF mode, no lens shading correction will be applied by the * camera device, and an identity lens shading map data will be provided * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens - * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, - * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>, + * the output android.statistics.lensShadingMap for this case will be an identity map * shown below:</p> * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1307,11 +1456,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>When set to other modes, lens shading correction will be applied by the * camera device. Applications can request lens shading map data by setting * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide - * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified - * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * lens shading map data in android.statistics.lensShadingMap, with size specified + * by android.lens.info.shadingMapSize; the returned shading map data will be the one + * applied by the camera device for this capture request.</p> + * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability + * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in + * AUTO modes({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF and {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} <code>!=</code> OFF), + * to get best results, it is recommended that the applications wait for the AE and AWB to + * be converged before using the returned shading map data.</p> * - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @see #SHADING_MODE_OFF * @see #SHADING_MODE_FAST @@ -1352,10 +1507,8 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>Whether the camera device will output the lens * shading map in output result metadata.</p> * <p>When set to ON, - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * android.statistics.lensShadingMap must be provided in * the output result metadata.</p> - * - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON */ @@ -1366,10 +1519,10 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); @@ -1378,10 +1531,10 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>Tonemapping / contrast / gamma curve for the green * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); @@ -1391,7 +1544,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> * <p>Each channel's curve is defined by an array of control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * <pre><code>android.tonemap.curveRed = * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> * <p>These are sorted in order of increasing <code>Pin</code>; it is always @@ -1407,15 +1560,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> * <p>Linear mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, @@ -1423,7 +1576,7 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * </code></pre> * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, @@ -1431,14 +1584,67 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * </code></pre> * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} + * is CONTRAST_CURVE.</p> + * <p>The tonemapCurve consist of three curves for each of red, green, and blue + * channels respectively. The following example uses the red channel as an + * example. The same logic applies to green and blue channel. + * Each channel's curve is defined by an array of control points:</p> + * <pre><code>curveRed = + * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), + * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072), + * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), + * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), + * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130), + * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), + * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE + */ + public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE = + new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class); + + /** * <p>High-level global contrast/gamma/tonemapping control.</p> * <p>When switching to an application-defined contrast curve by setting * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined @@ -1454,18 +1660,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable { * <p>This must be set to a valid mode in * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p> * <p>When using either FAST or HIGH_QUALITY, the camera device will - * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}. * These values are always available, and as close as possible to the * actually used nonlinear/nonglobal transforms.</p> - * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's + * <p>If a request is sent with CONTRAST_CURVE with the camera device's * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be * roughly the same.</p> * * @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index f91fcb9..7d07c92 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -17,13 +17,16 @@ package android.hardware.camera2; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.params.Face; +import android.hardware.camera2.utils.TypeReference; +import android.util.Log; import android.util.Rational; +import java.util.List; + /** - * <p>The results of a single image capture from the image sensor.</p> + * <p>The subset of the results of a single image capture from the image sensor.</p> * - * <p>Contains the final configuration for the capture hardware (sensor, lens, + * <p>Contains a subset of the final configuration for the capture hardware (sensor, lens, * flash), the processing pipeline, the control algorithms, and the output * buffers.</p> * @@ -33,10 +36,106 @@ import android.util.Rational; * capture. The result also includes additional metadata about the state of the * camera device during the capture.</p> * - * <p>{@link CameraCharacteristics} objects are immutable.</p> + * <p>Not all properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()} + * are necessarily available. Some results are {@link CaptureResult partial} and will + * not have every key set. Only {@link TotalCaptureResult total} results are guaranteed to have + * every key available that was enabled by the request.</p> + * + * <p>{@link CaptureResult} objects are immutable.</p> * */ -public final class CaptureResult extends CameraMetadata { +public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { + + private static final String TAG = "CaptureResult"; + private static final boolean VERBOSE = false; + + /** + * A {@code Key} is used to do capture result field lookups with + * {@link CaptureResult#get}. + * + * <p>For example, to get the timestamp corresponding to the exposure of the first row: + * <code><pre> + * long timestamp = captureResult.get(CaptureResult.SENSOR_TIMESTAMP); + * </pre></code> + * </p> + * + * <p>To enumerate over all possible keys for {@link CaptureResult}, see + * {@link CameraCharacteristics#getAvailableCaptureResultKeys}.</p> + * + * @see CaptureResult#get + * @see CameraCharacteristics#getAvailableCaptureResultKeys + */ + public final static class Key<T> { + private final CameraMetadataNative.Key<T> mKey; + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, Class<T> type) { + mKey = new CameraMetadataNative.Key<T>(name, type); + } + + /** + * Visible for testing and vendor extensions only. + * + * @hide + */ + public Key(String name, TypeReference<T> typeReference) { + mKey = new CameraMetadataNative.Key<T>(name, typeReference); + } + + /** + * Return a camelCase, period separated name formatted like: + * {@code "root.section[.subsections].name"}. + * + * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."}; + * keys that are device/platform-specific are prefixed with {@code "com."}.</p> + * + * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would + * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device + * specific key might look like {@code "com.google.nexus.data.private"}.</p> + * + * @return String representation of the key name + */ + public String getName() { + return mKey.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return mKey.hashCode(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public final boolean equals(Object o) { + return o instanceof Key && ((Key<T>)o).mKey.equals(mKey); + } + + /** + * Visible for CameraMetadataNative implementation only; do not use. + * + * TODO: Make this private or remove it altogether. + * + * @hide + */ + public CameraMetadataNative.Key<T> getNativeKey() { + return mKey; + } + + @SuppressWarnings({ "unchecked" }) + /*package*/ Key(CameraMetadataNative.Key<?> nativeKey) { + mKey = (CameraMetadataNative.Key<T>) nativeKey; + } + } private final CameraMetadataNative mResults; private final CaptureRequest mRequest; @@ -55,7 +154,10 @@ public final class CaptureResult extends CameraMetadata { throw new IllegalArgumentException("parent was null"); } - mResults = results; + mResults = CameraMetadataNative.move(results); + if (mResults.isEmpty()) { + throw new AssertionError("Results must not be empty"); + } mRequest = parent; mSequenceId = sequenceId; } @@ -68,26 +170,103 @@ public final class CaptureResult extends CameraMetadata { return new CameraMetadataNative(mResults); } - @Override + /** + * Creates a request-less result. + * + * <p><strong>For testing only.</strong></p> + * @hide + */ + public CaptureResult(CameraMetadataNative results, int sequenceId) { + if (results == null) { + throw new IllegalArgumentException("results was null"); + } + + mResults = CameraMetadataNative.move(results); + if (mResults.isEmpty()) { + throw new AssertionError("Results must not be empty"); + } + + mRequest = null; + mSequenceId = sequenceId; + } + + /** + * Get a capture result field value. + * + * <p>The field definitions can be found in {@link CaptureResult}.</p> + * + * <p>Querying the value for the same key more than once will return a value + * which is equal to the previous queried value.</p> + * + * @throws IllegalArgumentException if the key was not valid + * + * @param key The result field to read. + * @return The value of that key, or {@code null} if the field is not set. + */ public <T> T get(Key<T> key) { - return mResults.get(key); + T value = mResults.get(key); + if (VERBOSE) Log.v(TAG, "#get for Key = " + key.getName() + ", returned value = " + value); + return value; + } + + /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected <T> T getProtected(Key<?> key) { + return (T) mResults.get(key); + } + + /** + * {@inheritDoc} + * @hide + */ + @SuppressWarnings("unchecked") + @Override + protected Class<Key<?>> getKeyClass() { + Object thisClass = Key.class; + return (Class<Key<?>>)thisClass; + } + + /** + * Dumps the native metadata contents to logcat. + * + * <p>Visibility for testing/debugging only. The results will not + * include any synthesized keys, as they are invisible to the native layer.</p> + * + * @hide + */ + public void dumpToLog() { + mResults.dumpToLog(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<Key<?>> getKeys() { + // Force the javadoc for this function to show up on the CaptureResult page + return super.getKeys(); } /** * Get the request associated with this result. * - * <p>Whenever a request is successfully captured, with - * {@link CameraDevice.CaptureListener#onCaptureCompleted}, - * the {@code result}'s {@code getRequest()} will return that {@code request}. + * <p>Whenever a request has been fully or partially captured, with + * {@link CameraDevice.CaptureListener#onCaptureCompleted} or + * {@link CameraDevice.CaptureListener#onCaptureProgressed}, the {@code result}'s + * {@code getRequest()} will return that {@code request}. * </p> * - * <p>In particular, + * <p>For example, * <code><pre>cameraDevice.capture(someRequest, new CaptureListener() { * {@literal @}Override * void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) { * assert(myResult.getRequest.equals(myRequest) == true); * } - * }; + * }, null); * </code></pre> * </p> * @@ -110,6 +289,7 @@ public final class CaptureResult extends CameraMetadata { * @return int frame number */ public int getFrameNumber() { + // TODO: @hide REQUEST_FRAME_COUNT return get(REQUEST_FRAME_COUNT); } @@ -123,6 +303,7 @@ public final class CaptureResult extends CameraMetadata { * @return int The ID for the sequence of requests that this capture result is a part of * * @see CameraDevice.CaptureListener#onCaptureSequenceCompleted + * @see CameraDevice.CaptureListener#onCaptureSequenceAborted */ public int getSequenceId() { return mSequenceId; @@ -202,30 +383,24 @@ public final class CaptureResult extends CameraMetadata { * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM = - new Key<Rational[]>("android.colorCorrection.transform", Rational[].class); + public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM = + new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class); /** * <p>Gains applying to Bayer raw color channels for * white-balance.</p> - * <p>The 4-channel white-balance gains are defined in - * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain - * for green pixels on even rows of the output, and <code>G_odd</code> - * is the gain for green pixels on the odd rows. if a HAL - * does not support a separate gain for even/odd green channels, - * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to - * <code>G_even</code> in the output result metadata.</p> - * <p>This array is either set by the camera device when the request - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or - * directly by the application in the request when the - * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p> - * <p>The output should be the gains actually applied by the camera device to - * the current frame.</p> + * <p>These per-channel gains are either set by the camera device + * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not + * TRANSFORM_MATRIX, or directly by the application in the + * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is + * TRANSFORM_MATRIX.</p> + * <p>The gains in the result metadata are the gains actually + * applied by the camera device to the current frame.</p> * * @see CaptureRequest#COLOR_CORRECTION_MODE */ - public static final Key<float[]> COLOR_CORRECTION_GAINS = - new Key<float[]>("android.colorCorrection.gains", float[].class); + public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS = + new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class); /** * <p>The desired setting for the camera device's auto-exposure @@ -358,26 +533,27 @@ public final class CaptureResult extends CameraMetadata { /** * <p>List of areas to use for * metering.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> * <p>If all regions have 0 weight, then no specific metering area * needs to be used by the camera device. If the metering region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AE_REGIONS = - new Key<int[]>("android.control.aeRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Range over which fps can be adjusted to @@ -387,8 +563,8 @@ public final class CaptureResult extends CameraMetadata { * * @see CaptureRequest#SENSOR_EXPOSURE_TIME */ - public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE = - new Key<int[]>("android.control.aeTargetFpsRange", int[].class); + public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE = + new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }}); /** * <p>Whether the camera device will trigger a precapture @@ -631,26 +807,27 @@ public final class CaptureResult extends CameraMetadata { /** * <p>List of areas to use for focus * estimation.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific focus area - * needs to be used by the camera device. If the focusing region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AF_REGIONS = - new Key<int[]>("android.control.afRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Whether the camera device will trigger autofocus for this request.</p> @@ -1114,27 +1291,27 @@ public final class CaptureResult extends CameraMetadata { /** * <p>List of areas to use for illuminant * estimation.</p> - * <p>Only used in AUTO mode.</p> - * <p>Each area is a rectangle plus weight: xmin, ymin, - * xmax, ymax, weight. The rectangle is defined to be inclusive of the - * specified coordinates.</p> * <p>The coordinate system is based on the active pixel array, * with (0,0) being the top-left pixel in the active pixel array, and * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1, * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the - * bottom-right pixel in the active pixel array. The weight - * should be nonnegative.</p> - * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area - * needs to be used by the camera device. If the AWB region is - * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device - * will ignore the sections outside the region and output the - * used sections in the frame metadata.</p> + * bottom-right pixel in the active pixel array.</p> + * <p>The weight must range from 0 to 1000, and represents a weight + * for every pixel in the area. This means that a large metering area + * with the same weight as a smaller area will have more effect in + * the metering result. Metering areas can partially overlap and the + * camera device will add the weights in the overlap region.</p> + * <p>If all regions have 0 weight, then no specific metering area + * needs to be used by the camera device. If the metering region is + * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata, + * the camera device will ignore the sections outside the region and output the + * used sections in the result metadata.</p> * * @see CaptureRequest#SCALER_CROP_REGION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ - public static final Key<int[]> CONTROL_AWB_REGIONS = - new Key<int[]>("android.control.awbRegions", int[].class); + public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS = + new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class); /** * <p>Information to the camera device 3A (auto-exposure, @@ -1475,8 +1652,15 @@ public final class CaptureResult extends CameraMetadata { new Key<Integer>("android.hotPixel.mode", int.class); /** + * <p>A location object to use when generating image GPS metadata.</p> + */ + public static final Key<android.location.Location> JPEG_GPS_LOCATION = + new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class); + + /** * <p>GPS coordinates to include in output JPEG * EXIF</p> + * @hide */ public static final Key<double[]> JPEG_GPS_COORDINATES = new Key<double[]>("android.jpeg.gpsCoordinates", double[].class); @@ -1484,6 +1668,7 @@ public final class CaptureResult extends CameraMetadata { /** * <p>32 characters describing GPS algorithm to * include in EXIF</p> + * @hide */ public static final Key<String> JPEG_GPS_PROCESSING_METHOD = new Key<String>("android.jpeg.gpsProcessingMethod", String.class); @@ -1491,6 +1676,7 @@ public final class CaptureResult extends CameraMetadata { /** * <p>Time GPS fix was made to include in * EXIF</p> + * @hide */ public static final Key<Long> JPEG_GPS_TIMESTAMP = new Key<Long>("android.jpeg.gpsTimestamp", long.class); @@ -1523,6 +1709,12 @@ public final class CaptureResult extends CameraMetadata { * but the captured JPEG will still be a valid image.</p> * <p>When a jpeg image capture is issued, the thumbnail size selected should have * the same aspect ratio as the jpeg image.</p> + * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect + * ratio, the camera device creates the thumbnail by cropping it from the primary image. + * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has + * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to + * generate the thumbnail image. The thumbnail image will always have a smaller Field + * Of View (FOV) than the primary image when aspect ratios differ.</p> */ public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE = new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class); @@ -1612,8 +1804,8 @@ public final class CaptureResult extends CameraMetadata { * <p>If variable focus not supported, can still report * fixed depth of field range</p> */ - public static final Key<float[]> LENS_FOCUS_RANGE = - new Key<float[]>("android.lens.focusRange", float[].class); + public static final Key<android.util.Pair<Float,Float>> LENS_FOCUS_RANGE = + new Key<android.util.Pair<Float,Float>>("android.lens.focusRange", new TypeReference<android.util.Pair<Float,Float>>() {{ }}); /** * <p>Sets whether the camera device uses optical image stabilization (OIS) @@ -1749,8 +1941,11 @@ public final class CaptureResult extends CameraMetadata { * output, cropping to a smaller region if necessary to * maintain the stream's aspect ratio.</p> * <p>HAL2.x uses only (x, y, width)</p> - * <p>Any additional per-stream cropping must be done to - * maximize the final pixel area of the stream.</p> + * <p>The crop region is applied after the RAW to other color space (e.g. YUV) + * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, + * it is not croppable. The crop region will be ignored by raw streams.</p> + * <p>For non-raw streams, any additional per-stream cropping will + * be done to maximize the final pixel area of the stream.</p> * <p>For example, if the crop region is set to a 4:3 aspect * ratio, then 4:3 streams should use the exact crop * region. 16:9 streams should further crop vertically @@ -1885,21 +2080,6 @@ public final class CaptureResult extends CameraMetadata { new Key<Long>("android.sensor.timestamp", long.class); /** - * <p>The temperature of the sensor, sampled at the time - * exposure began for this frame.</p> - * <p>The thermal diode being queried should be inside the sensor PCB, or - * somewhere close to it.</p> - * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> - * <p><b>Full capability</b> - - * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> - * - * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL - */ - public static final Key<Float> SENSOR_TEMPERATURE = - new Key<Float>("android.sensor.temperature", float.class); - - /** * <p>The estimated camera neutral color in the native sensor colorspace at * the time of capture.</p> * <p>This value gives the neutral color point encoded as an RGB value in the @@ -1992,8 +2172,8 @@ public final class CaptureResult extends CameraMetadata { * <p>When set to OFF mode, no lens shading correction will be applied by the * camera device, and an identity lens shading map data will be provided * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens - * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>, - * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map + * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>, + * the output android.statistics.lensShadingMap for this case will be an identity map * shown below:</p> * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -2005,11 +2185,17 @@ public final class CaptureResult extends CameraMetadata { * <p>When set to other modes, lens shading correction will be applied by the * camera device. Applications can request lens shading map data by setting * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide - * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified - * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * lens shading map data in android.statistics.lensShadingMap, with size specified + * by android.lens.info.shadingMapSize; the returned shading map data will be the one + * applied by the camera device for this capture request.</p> + * <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability + * of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in + * AUTO modes({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} <code>!=</code> OFF and {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} <code>!=</code> OFF), + * to get best results, it is recommended that the applications wait for the AE and AWB to + * be converged before using the returned shading map data.</p> * - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @see CaptureRequest#CONTROL_AE_MODE + * @see CaptureRequest#CONTROL_AWB_MODE * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE * @see #SHADING_MODE_OFF * @see #SHADING_MODE_FAST @@ -2099,13 +2285,59 @@ public final class CaptureResult extends CameraMetadata { * The map is assumed to be bilinearly interpolated between the sample points.</p> * <p>The channel order is [R, Geven, Godd, B], where Geven is the green * channel for the even rows of a Bayer pattern, and Godd is the odd rows. + * The shading map is stored in a fully interleaved format.</p> + * <p>The shading map should have on the order of 30-40 rows and columns, + * and must be smaller than 64x64.</p> + * <p>As an example, given a very small map defined as:</p> + * <pre><code>width,height = [ 4, 3 ] + * values = + * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, + * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3, + * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0, + * 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.25, 1.2, + * 1.3, 1.2, 1.2, 1.3, 1.2, 1.15, 1.1, 1.2, + * 1.2, 1.1, 1.0, 1.2, 1.3, 1.15, 1.2, 1.3 ] + * </code></pre> + * <p>The low-resolution scaling map images for each channel are + * (displayed using nearest-neighbor interpolation):</p> + * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" /> + * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" /> + * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" /> + * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p> + * <p>As a visualization only, inverting the full-color map to recover an + * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p> + * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * + * @see CaptureRequest#COLOR_CORRECTION_MODE + */ + public static final Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP = + new Key<android.hardware.camera2.params.LensShadingMap>("android.statistics.lensShadingCorrectionMap", android.hardware.camera2.params.LensShadingMap.class); + + /** + * <p>The shading map is a low-resolution floating-point map + * that lists the coefficients used to correct for vignetting, for each + * Bayer color channel.</p> + * <p>The least shaded section of the image should have a gain factor + * of 1; all other sections should have gains above 1.</p> + * <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map + * must take into account the colorCorrection settings.</p> + * <p>The shading map is for the entire active pixel array, and is not + * affected by the crop region specified in the request. Each shading map + * entry is the value of the shading compensation map over a specific + * pixel on the sensor. Specifically, with a (N x M) resolution shading + * map, and an active pixel array size (W x H), shading map entry + * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at + * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels. + * The map is assumed to be bilinearly interpolated between the sample points.</p> + * <p>The channel order is [R, Geven, Godd, B], where Geven is the green + * channel for the even rows of a Bayer pattern, and Godd is the odd rows. * The shading map is stored in a fully interleaved format, and its size - * is provided in the camera static metadata by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p> + * is provided in the camera static metadata by android.lens.info.shadingMapSize.</p> * <p>The shading map should have on the order of 30-40 rows and columns, * and must be smaller than 64x64.</p> * <p>As an example, given a very small map defined as:</p> - * <pre><code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ] - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} = + * <pre><code>android.lens.info.shadingMapSize = [ 4, 3 ] + * android.statistics.lensShadingMap = * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3, * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0, @@ -2124,8 +2356,7 @@ public final class CaptureResult extends CameraMetadata { * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> * * @see CaptureRequest#COLOR_CORRECTION_MODE - * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP + * @hide */ public static final Key<float[]> STATISTICS_LENS_SHADING_MAP = new Key<float[]>("android.statistics.lensShadingMap", float[].class); @@ -2225,17 +2456,15 @@ public final class CaptureResult extends CameraMetadata { * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE */ - public static final Key<int[]> STATISTICS_HOT_PIXEL_MAP = - new Key<int[]>("android.statistics.hotPixelMap", int[].class); + public static final Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP = + new Key<android.graphics.Point[]>("android.statistics.hotPixelMap", android.graphics.Point[].class); /** * <p>Whether the camera device will output the lens * shading map in output result metadata.</p> * <p>When set to ON, - * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in + * android.statistics.lensShadingMap must be provided in * the output result metadata.</p> - * - * @see CaptureResult#STATISTICS_LENS_SHADING_MAP * @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF * @see #STATISTICS_LENS_SHADING_MAP_MODE_ON */ @@ -2246,10 +2475,10 @@ public final class CaptureResult extends CameraMetadata { * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_BLUE = new Key<float[]>("android.tonemap.curveBlue", float[].class); @@ -2258,10 +2487,10 @@ public final class CaptureResult extends CameraMetadata { * <p>Tonemapping / contrast / gamma curve for the green * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> - * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p> + * <p>See android.tonemap.curveRed for more details.</p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_GREEN = new Key<float[]>("android.tonemap.curveGreen", float[].class); @@ -2271,7 +2500,7 @@ public final class CaptureResult extends CameraMetadata { * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> * <p>Each channel's curve is defined by an array of control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = + * <pre><code>android.tonemap.curveRed = * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ] * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> * <p>These are sorted in order of increasing <code>Pin</code>; it is always @@ -2287,15 +2516,15 @@ public final class CaptureResult extends CameraMetadata { * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> * <p>Linear mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ] * </code></pre> * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> * <p>Invert mapping:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ] + * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ] * </code></pre> * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> * <p>Gamma 1/2.2 mapping, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812, * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072, * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685, @@ -2303,7 +2532,7 @@ public final class CaptureResult extends CameraMetadata { * </code></pre> * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> - * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ + * <pre><code>android.tonemap.curveRed = [ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845, * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130, * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721, @@ -2311,14 +2540,67 @@ public final class CaptureResult extends CameraMetadata { * </code></pre> * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> * - * @see CaptureRequest#TONEMAP_CURVE_RED * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS * @see CaptureRequest#TONEMAP_MODE + * @hide */ public static final Key<float[]> TONEMAP_CURVE_RED = new Key<float[]>("android.tonemap.curveRed", float[].class); /** + * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} + * is CONTRAST_CURVE.</p> + * <p>The tonemapCurve consist of three curves for each of red, green, and blue + * channels respectively. The following example uses the red channel as an + * example. The same logic applies to green and blue channel. + * Each channel's curve is defined by an array of control points:</p> + * <pre><code>curveRed = + * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ] + * 2 <= N <= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre> + * <p>These are sorted in order of increasing <code>Pin</code>; it is always + * guaranteed that input values 0.0 and 1.0 are included in the list to + * define a complete mapping. For input values between control points, + * the camera device must linearly interpolate between the control + * points.</p> + * <p>Each curve can have an independent number of points, and the number + * of points can be less than max (that is, the request doesn't have to + * always provide a curve with number of points equivalent to + * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>A few examples, and their corresponding graphical mappings; these + * only specify the red channel and the precision is limited to 4 + * digits, for conciseness.</p> + * <p>Linear mapping:</p> + * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ] + * </code></pre> + * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p> + * <p>Invert mapping:</p> + * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ] + * </code></pre> + * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p> + * <p>Gamma 1/2.2 mapping, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812), + * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072), + * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685), + * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p> + * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p> + * <pre><code>curveRed = [ + * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845), + * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130), + * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721), + * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ] + * </code></pre> + * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p> + * + * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS + * @see CaptureRequest#TONEMAP_MODE + */ + public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE = + new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class); + + /** * <p>High-level global contrast/gamma/tonemapping control.</p> * <p>When switching to an application-defined contrast curve by setting * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined @@ -2334,18 +2616,15 @@ public final class CaptureResult extends CameraMetadata { * <p>This must be set to a valid mode in * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p> * <p>When using either FAST or HIGH_QUALITY, the camera device will - * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, - * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}. + * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}. * These values are always available, and as close as possible to the * actually used nonlinear/nonglobal transforms.</p> - * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's + * <p>If a request is sent with CONTRAST_CURVE with the camera device's * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be * roughly the same.</p> * * @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES - * @see CaptureRequest#TONEMAP_CURVE_BLUE - * @see CaptureRequest#TONEMAP_CURVE_GREEN - * @see CaptureRequest#TONEMAP_CURVE_RED + * @see CaptureRequest#TONEMAP_CURVE * @see CaptureRequest#TONEMAP_MODE * @see #TONEMAP_MODE_CONTRAST_CURVE * @see #TONEMAP_MODE_FAST diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java new file mode 100644 index 0000000..54568ed --- /dev/null +++ b/core/java/android/hardware/camera2/DngCreator.java @@ -0,0 +1,368 @@ +/* + * Copyright 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.hardware.camera2; + +import android.graphics.Bitmap; +import android.graphics.ImageFormat; +import android.hardware.camera2.impl.CameraMetadataNative; +import android.location.Location; +import android.media.ExifInterface; +import android.media.Image; +import android.util.Size; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file. + * + * <p> + * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR} + * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw + * pixel data that is otherwise generated by an application. The DNG metadata tags will be + * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly. + * </p> + * + * <p> + * The DNG file format is a cross-platform file format that is used to store pixel data from + * camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be + * defined in a user-defined colorspace, and have associated metadata that allow for this + * pixel data to be converted to the standard CIE XYZ colorspace during post-processing. + * </p> + * + * <p> + * For more information on the DNG file format and associated metadata, please refer to the + * <a href= + * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf"> + * Adobe DNG 1.4.0.0 specification</a>. + * </p> + */ +public final class DngCreator implements AutoCloseable { + + /** + * Create a new DNG object. + * + * <p> + * It is not necessary to call any set methods to write a well-formatted DNG file. + * </p> + * <p> + * DNG metadata tags will be generated from the corresponding parameters in the + * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides + * all previous tags set. + * </p> + * + * @param characteristics an object containing the static + * {@link android.hardware.camera2.CameraCharacteristics}. + * @param metadata a metadata object to generate tags from. + */ + public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) { + if (characteristics == null || metadata == null) { + throw new NullPointerException("Null argument to DngCreator constructor"); + } + nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy()); + } + + /** + * Set the orientation value to write. + * + * <p> + * This will be written as the TIFF "Orientation" tag {@code (0x0112)}. + * Calling this will override any prior settings for this tag. + * </p> + * + * @param orientation the orientation value to set, one of: + * <ul> + * <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li> + * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li> + * </ul> + * @return this {@link #DngCreator} object. + */ + public DngCreator setOrientation(int orientation) { + + if (orientation < ExifInterface.ORIENTATION_UNDEFINED || + orientation > ExifInterface.ORIENTATION_ROTATE_270) { + throw new IllegalArgumentException("Orientation " + orientation + + " is not a valid EXIF orientation value"); + } + nativeSetOrientation(orientation); + return this; + } + + /** + * Set the thumbnail image. + * + * <p> + * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel. + * The alpha channel will be discarded. + * </p> + * + * <p> + * The given bitmap should not be altered while this object is in use. + * </p> + * + * @param pixels a {@link android.graphics.Bitmap} of pixel data. + * @return this {@link #DngCreator} object. + */ + public DngCreator setThumbnail(Bitmap pixels) { + if (pixels == null) { + throw new NullPointerException("Null argument to setThumbnail"); + } + + Bitmap.Config config = pixels.getConfig(); + + if (config != Bitmap.Config.ARGB_8888) { + pixels = pixels.copy(Bitmap.Config.ARGB_8888, false); + if (pixels == null) { + throw new IllegalArgumentException("Unsupported Bitmap format " + config); + } + nativeSetThumbnailBitmap(pixels); + } + + return this; + } + + /** + * Set the thumbnail image. + * + * <p> + * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image. + * </p> + * + * <p> + * The given image should not be altered while this object is in use. + * </p> + * + * @param pixels an {@link android.media.Image} object with the format + * {@link android.graphics.ImageFormat#YUV_420_888}. + * @return this {@link #DngCreator} object. + */ + public DngCreator setThumbnail(Image pixels) { + if (pixels == null) { + throw new NullPointerException("Null argument to setThumbnail"); + } + + int format = pixels.getFormat(); + if (format != ImageFormat.YUV_420_888) { + throw new IllegalArgumentException("Unsupported image format " + format); + } + + Image.Plane[] planes = pixels.getPlanes(); + nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), + planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(), + planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(), + planes[1].getRowStride(), planes[1].getPixelStride()); + + return this; + } + + + /** + * Set image location metadata. + * + * <p> + * The given location object must contain at least a valid time, latitude, and longitude + * (equivalent to the values returned by {@link android.location.Location#getTime()}, + * {@link android.location.Location#getLatitude()}, and + * {@link android.location.Location#getLongitude()} methods). + * </p> + * + * @param location an {@link android.location.Location} object to set. + * @return this {@link #DngCreator} object. + * + * @throws java.lang.IllegalArgumentException if the given location object doesn't + * contain enough information to set location metadata. + */ + public DngCreator setLocation(Location location) { + /*TODO*/ + return this; + } + + /** + * Set the user description string to write. + * + * <p> + * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}. + * </p> + * + * @param description the user description string. + * @return this {@link #DngCreator} object. + */ + public DngCreator setDescription(String description) { + /*TODO*/ + return this; + } + + /** + * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with + * the currently configured metadata. + * + * <p> + * Raw pixel data must have 16 bits per pixel, and the input must contain at least + * {@code offset + 2 * width * height)} bytes. The width and height of + * the input are taken from the width and height set in the {@link DngCreator} metadata tags, + * and will typically be equal to the width and height of + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. + * The pixel layout in the input is determined from the reported color filter arrangement (CFA) + * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient + * metadata is available to write a well-formatted DNG file, an + * {@link java.lang.IllegalStateException} will be thrown. + * </p> + * + * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. + * @param size the {@link Size} of the image to write, in pixels. + * @param pixels an {@link java.io.InputStream} of pixel data to write. + * @param offset the offset of the raw image in bytes. This indicates how many bytes will + * be skipped in the input before any pixel data is read. + * + * @throws IOException if an error was encountered in the input or output stream. + * @throws java.lang.IllegalStateException if not enough metadata information has been + * set to write a well-formatted DNG file. + * @throws java.lang.IllegalArgumentException if the size passed in does not match the + */ + public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset) + throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + nativeWriteInputStream(dngOutput, pixels, offset); + } + + /** + * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with + * the currently configured metadata. + * + * <p> + * Raw pixel data must have 16 bits per pixel, and the input must contain at least + * {@code offset + 2 * width * height)} bytes. The width and height of + * the input are taken from the width and height set in the {@link DngCreator} metadata tags, + * and will typically be equal to the width and height of + * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. + * The pixel layout in the input is determined from the reported color filter arrangement (CFA) + * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient + * metadata is available to write a well-formatted DNG file, an + * {@link java.lang.IllegalStateException} will be thrown. + * </p> + * + * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. + * @param size the {@link Size} of the image to write, in pixels. + * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write. + * @param offset the offset of the raw image in bytes. This indicates how many bytes will + * be skipped in the input before any pixel data is read. + * + * @throws IOException if an error was encountered in the input or output stream. + * @throws java.lang.IllegalStateException if not enough metadata information has been + * set to write a well-formatted DNG file. + */ + public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset) + throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + nativeWriteByteBuffer(dngOutput, pixels, offset); + } + + /** + * Write the pixel data to a DNG file with the currently configured metadata. + * + * <p> + * For this method to succeed, the {@link android.media.Image} input must contain + * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an + * {@link java.lang.IllegalArgumentException} will be thrown. + * </p> + * + * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. + * @param pixels an {@link android.media.Image} to write. + * + * @throws java.io.IOException if an error was encountered in the output stream. + * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used. + * @throws java.lang.IllegalStateException if not enough metadata information has been + * set to write a well-formatted DNG file. + */ + public void writeImage(OutputStream dngOutput, Image pixels) throws IOException { + if (dngOutput == null || pixels == null) { + throw new NullPointerException("Null argument to writeImage"); + } + + int format = pixels.getFormat(); + if (format != ImageFormat.RAW_SENSOR) { + throw new IllegalArgumentException("Unsupported image format " + format); + } + + Image.Plane[] planes = pixels.getPlanes(); + nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), + planes[0].getRowStride(), planes[0].getPixelStride()); + } + + @Override + public void close() { + nativeDestroy(); + } + + @Override + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * This field is used by native code, do not access or modify. + */ + private long mNativeContext; + + private static native void nativeClassInit(); + + private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics, + CameraMetadataNative nativeResult); + + private synchronized native void nativeDestroy(); + + private synchronized native void nativeSetOrientation(int orientation); + + private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap); + + private synchronized native void nativeSetThumbnailImage(int width, int height, + ByteBuffer yBuffer, int yRowStride, + int yPixStride, ByteBuffer uBuffer, + int uRowStride, int uPixStride, + ByteBuffer vBuffer, int vRowStride, + int vPixStride); + + private synchronized native void nativeWriteImage(OutputStream out, int width, int height, + ByteBuffer rawBuffer, int rowStride, + int pixStride) throws IOException; + + private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer, + long offset) throws IOException; + + private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream, + long offset) throws IOException; + + static { + nativeClassInit(); + } +} diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java new file mode 100644 index 0000000..2647959 --- /dev/null +++ b/core/java/android/hardware/camera2/TotalCaptureResult.java @@ -0,0 +1,84 @@ +/* + * 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.hardware.camera2; + +import android.hardware.camera2.impl.CameraMetadataNative; + +import java.util.Collections; +import java.util.List; + +/** + * <p>The total assembled results of a single image capture from the image sensor.</p> + * + * <p>Contains the final configuration for the capture hardware (sensor, lens, + * flash), the processing pipeline, the control algorithms, and the output + * buffers.</p> + * + * <p>A {@code TotalCaptureResult} is produced by a {@link CameraDevice} after processing a + * {@link CaptureRequest}. All properties listed for capture requests can also + * be queried on the capture result, to determine the final values used for + * capture. The result also includes additional metadata about the state of the + * camera device during the capture.</p> + * + * <p>All properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()} + * are available (that is {@link CaptureResult#get} will return non-{@code null}, if and only if + * that key that was enabled by the request. A few keys such as + * {@link CaptureResult#STATISTICS_FACES} are disabled by default unless enabled with a switch (such + * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on + * a case-by-case basis.</p> + * + * <p>{@link TotalCaptureResult} objects are immutable.</p> + * + * @see CameraDevice.CaptureListener#onCaptureCompleted + */ +public final class TotalCaptureResult extends CaptureResult { + + /** + * Takes ownership of the passed-in properties object + * @hide + */ + public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) { + super(results, parent, sequenceId); + } + + /** + * Creates a request-less result. + * + * <p><strong>For testing only.</strong></p> + * @hide + */ + public TotalCaptureResult(CameraMetadataNative results, int sequenceId) { + super(results, sequenceId); + } + + /** + * Get the read-only list of partial results that compose this total result. + * + * <p>The list is returned is unmodifiable; attempting to modify it will result in a + * {@code UnsupportedOperationException} being thrown.</p> + * + * <p>The list size will be inclusive between {@code 1} and + * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order + * of when {@link CameraDevice.CaptureListener#onCaptureProgressed} was invoked.</p> + * + * @return unmodifiable list of partial results + */ + public List<CaptureResult> getPartialResults() { + // TODO + return Collections.unmodifiableList(null); + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index b082a70..9a4c531 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -20,10 +20,13 @@ import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; +import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.LongParcelable; @@ -72,6 +75,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); private final String mCameraId; + private final CameraCharacteristics mCharacteristics; /** * A list tracking request and its expected last frame. @@ -150,13 +154,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } }; - public CameraDevice(String cameraId, StateListener listener, Handler handler) { + public CameraDevice(String cameraId, StateListener listener, Handler handler, + CameraCharacteristics characteristics) { if (cameraId == null || listener == null || handler == null) { throw new IllegalArgumentException("Null argument given"); } mCameraId = cameraId; mDeviceListener = listener; mDeviceHandler = handler; + mCharacteristics = characteristics; final int MAX_TAG_LEN = 23; String tag = String.format("CameraDevice-JV-%s", mCameraId); @@ -359,7 +365,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder.getListener().onCaptureSequenceCompleted( CameraDevice.this, requestId, - (int)lastFrameNumber); + lastFrameNumber); } } }; @@ -717,7 +723,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder.getListener().onCaptureSequenceCompleted( CameraDevice.this, requestId, - (int)lastFrameNumber); + lastFrameNumber); } } }; @@ -850,11 +856,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras) throws RemoteException { + int requestId = resultExtras.getRequestId(); if (DEBUG) { Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id " + requestId); } + + + // TODO: Handle CameraCharacteristics access from CaptureResult correctly. + result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE, + getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE)); + final CaptureListenerHolder holder; synchronized (mLock) { holder = CameraDevice.this.mCaptureListenerMap.get(requestId); @@ -888,12 +901,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId()); - final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); + Runnable resultDispatch = null; // Either send a partial result or the final capture completed result if (quirkIsPartialResult) { + final CaptureResult resultAsCapture = + new CaptureResult(result, request, requestId); + // Partial result resultDispatch = new Runnable() { @Override @@ -907,6 +923,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } }; } else { + final TotalCaptureResult resultAsCapture = + new TotalCaptureResult(result, request, requestId); + // Final capture result resultDispatch = new Runnable() { @Override @@ -958,4 +977,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return (mRemoteDevice == null); } } + + private CameraCharacteristics getCharacteristics() { + return mCharacteristics; + } } diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 27cfd38..83aee5d 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -20,7 +20,7 @@ import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.marshal.Marshaler; import android.hardware.camera2.marshal.MarshalQueryable; @@ -31,6 +31,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform import android.hardware.camera2.marshal.impl.MarshalQueryableEnum; import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle; import android.hardware.camera2.marshal.impl.MarshalQueryableNativeByteToInteger; +import android.hardware.camera2.marshal.impl.MarshalQueryablePair; import android.hardware.camera2.marshal.impl.MarshalQueryableParcelable; import android.hardware.camera2.marshal.impl.MarshalQueryablePrimitive; import android.hardware.camera2.marshal.impl.MarshalQueryableRange; @@ -43,28 +44,210 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration; import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Face; +import android.hardware.camera2.params.LensShadingMap; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfigurationDuration; import android.hardware.camera2.params.StreamConfigurationMap; +import android.hardware.camera2.params.TonemapCurve; +import android.hardware.camera2.utils.TypeReference; +import android.location.Location; +import android.location.LocationManager; import android.os.Parcelable; import android.os.Parcel; import android.util.Log; +import android.util.Pair; +import android.util.Size; +import com.android.internal.util.Preconditions; + +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; /** * Implementation of camera metadata marshal/unmarshal across Binder to * the camera service */ -public class CameraMetadataNative extends CameraMetadata implements Parcelable { +public class CameraMetadataNative implements Parcelable { + + public static class Key<T> { + private boolean mHasTag; + private int mTag; + private final Class<T> mType; + private final TypeReference<T> mTypeReference; + private final String mName; + + /** + * Visible for testing only. + * + * <p>Use the CameraCharacteristics.Key, CaptureResult.Key, or CaptureRequest.Key + * for application code or vendor-extended keys.</p> + */ + public Key(String name, Class<T> type) { + if (name == null) { + throw new NullPointerException("Key needs a valid name"); + } else if (type == null) { + throw new NullPointerException("Type needs to be non-null"); + } + mName = name; + mType = type; + mTypeReference = TypeReference.createSpecializedTypeReference(type); + } + + /** + * Visible for testing only. + * + * <p>Use the CameraCharacteristics.Key, CaptureResult.Key, or CaptureRequest.Key + * for application code or vendor-extended keys.</p> + */ + @SuppressWarnings("unchecked") + public Key(String name, TypeReference<T> typeReference) { + if (name == null) { + throw new NullPointerException("Key needs a valid name"); + } else if (typeReference == null) { + throw new NullPointerException("TypeReference needs to be non-null"); + } + mName = name; + mType = (Class<T>)typeReference.getRawType(); + mTypeReference = typeReference; + } + + /** + * Return a camelCase, period separated name formatted like: + * {@code "root.section[.subsections].name"}. + * + * <p>Built-in keys exposed by the Android SDK are always prefixed with {@code "android."}; + * keys that are device/platform-specific are prefixed with {@code "com."}.</p> + * + * <p>For example, {@code CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP} would + * have a name of {@code "android.scaler.streamConfigurationMap"}; whereas a device + * specific key might look like {@code "com.google.nexus.data.private"}.</p> + * + * @return String representation of the key name + */ + public final String getName() { + return mName; + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return mName.hashCode() ^ mTypeReference.hashCode(); + } + + /** + * Compare this key against other native keys, request keys, result keys, and + * characteristics keys. + * + * <p>Two keys are considered equal if their name and type reference are equal.</p> + * + * <p>Note that the equality against non-native keys is one-way. A native key may be equal + * to a result key; but that same result key will not be equal to a native key.</p> + */ + @SuppressWarnings("rawtypes") + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + + Key<?> lhs; + + if (o instanceof CaptureResult.Key) { + lhs = ((CaptureResult.Key)o).getNativeKey(); + } else if (o instanceof CaptureRequest.Key) { + lhs = ((CaptureRequest.Key)o).getNativeKey(); + } else if (o instanceof CameraCharacteristics.Key) { + lhs = ((CameraCharacteristics.Key)o).getNativeKey(); + } else if ((o instanceof Key)) { + lhs = (Key<?>)o; + } else { + return false; + } + + return mName.equals(lhs.mName) && mTypeReference.equals(lhs.mTypeReference); + } + + /** + * <p> + * Get the tag corresponding to this key. This enables insertion into the + * native metadata. + * </p> + * + * <p>This value is looked up the first time, and cached subsequently.</p> + * + * @return The tag numeric value corresponding to the string + */ + public final int getTag() { + if (!mHasTag) { + mTag = CameraMetadataNative.getTag(mName); + mHasTag = true; + } + return mTag; + } + + /** + * Get the raw class backing the type {@code T} for this key. + * + * <p>The distinction is only important if {@code T} is a generic, e.g. + * {@code Range<Integer>} since the nested type will be erased.</p> + */ + public final Class<T> getType() { + // TODO: remove this; other places should use #getTypeReference() instead + return mType; + } + + /** + * Get the type reference backing the type {@code T} for this key. + * + * <p>The distinction is only important if {@code T} is a generic, e.g. + * {@code Range<Integer>} since the nested type will be retained.</p> + */ + public final TypeReference<T> getTypeReference() { + return mTypeReference; + } + } private static final String TAG = "CameraMetadataJV"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); // this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h public static final int NATIVE_JPEG_FORMAT = 0x21; + private static final String CELLID_PROCESS = "CELLID"; + private static final String GPS_PROCESS = "GPS"; + + private static String translateLocationProviderToProcess(final String provider) { + if (provider == null) { + return null; + } + switch(provider) { + case LocationManager.GPS_PROVIDER: + return GPS_PROCESS; + case LocationManager.NETWORK_PROVIDER: + return CELLID_PROCESS; + default: + return null; + } + } + + private static String translateProcessToLocationProvider(final String process) { + if (process == null) { + return null; + } + switch(process) { + case GPS_PROCESS: + return LocationManager.GPS_PROVIDER; + case CELLID_PROCESS: + return LocationManager.NETWORK_PROVIDER; + default: + return null; + } + } + public CameraMetadataNative() { super(); mMetadataPtr = nativeAllocate(); @@ -84,6 +267,20 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { } } + /** + * Move the contents from {@code other} into a new camera metadata instance.</p> + * + * <p>After this call, {@code other} will become empty.</p> + * + * @param other the previous metadata instance which will get pilfered + * @return a new metadata instance with the values from {@code other} moved into it + */ + public static CameraMetadataNative move(CameraMetadataNative other) { + CameraMetadataNative newObject = new CameraMetadataNative(); + newObject.swap(other); + return newObject; + } + public static final Parcelable.Creator<CameraMetadataNative> CREATOR = new Parcelable.Creator<CameraMetadataNative>() { @Override @@ -109,11 +306,39 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { nativeWriteToParcel(dest); } - @Override + /** + * @hide + */ + public <T> T get(CameraCharacteristics.Key<T> key) { + return get(key.getNativeKey()); + } + + /** + * @hide + */ + public <T> T get(CaptureResult.Key<T> key) { + return get(key.getNativeKey()); + } + + /** + * @hide + */ + public <T> T get(CaptureRequest.Key<T> key) { + return get(key.getNativeKey()); + } + + /** + * Look-up a metadata field value by its key. + * + * @param key a non-{@code null} key instance + * @return the field corresponding to the {@code key}, or {@code null} if no value was set + */ public <T> T get(Key<T> key) { - T value = getOverride(key); - if (value != null) { - return value; + Preconditions.checkNotNull(key, "key must not be null"); + + Pair<T, Boolean> override = getOverride(key); + if (override.second) { + return override.first; } return getBase(key); @@ -152,6 +377,18 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { setBase(key, value); } + public <T> void set(CaptureRequest.Key<T> key, T value) { + set(key.getNativeKey(), value); + } + + public <T> void set(CaptureResult.Key<T> key, T value) { + set(key.getNativeKey(), value); + } + + public <T> void set(CameraCharacteristics.Key<T> key, T value) { + set(key.getNativeKey(), value); + } + // Keep up-to-date with camera_metadata.h /** * @hide @@ -188,6 +425,18 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { mMetadataPtr = 0; // set it to 0 again to prevent eclipse from making this field final } + private <T> T getBase(CameraCharacteristics.Key<T> key) { + return getBase(key.getNativeKey()); + } + + private <T> T getBase(CaptureResult.Key<T> key) { + return getBase(key.getNativeKey()); + } + + private <T> T getBase(CaptureRequest.Key<T> key) { + return getBase(key.getNativeKey()); + } + private <T> T getBase(Key<T> key) { int tag = key.getTag(); byte[] values = readValues(tag); @@ -199,23 +448,44 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return marshaler.unmarshal(buffer); } - // Need overwrite some metadata that has different definitions between native // and managed sides. @SuppressWarnings("unchecked") - private <T> T getOverride(Key<T> key) { + private <T> Pair<T, Boolean> getOverride(Key<T> key) { + T value = null; + boolean override = true; + if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) { - return (T) getAvailableFormats(); + value = (T) getAvailableFormats(); } else if (key.equals(CaptureResult.STATISTICS_FACES)) { - return (T) getFaces(); + value = (T) getFaces(); } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { - return (T) getFaceRectangles(); + value = (T) getFaceRectangles(); } else if (key.equals(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)) { - return (T) getStreamConfigurationMap(); + value = (T) getStreamConfigurationMap(); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) { + value = (T) getMaxRegions(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) { + value = (T) getMaxNumOutputs(key); + } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) { + value = (T) getTonemapCurve(); + } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) { + value = (T) getGpsLocation(); + } else if (key.equals(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)) { + value = (T) getLensShadingMap(); + } else { + override = false; } - // For other keys, get() falls back to getBase() - return null; + return Pair.create(value, override); } private int[] getAvailableFormats() { @@ -331,6 +601,62 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return fixedFaceRectangles; } + private LensShadingMap getLensShadingMap() { + float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP); + if (lsmArray == null) { + Log.w(TAG, "getLensShadingMap - Lens shading map was null."); + return null; + } + Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE); + LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth()); + return map; + } + + private Location getGpsLocation() { + String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); + Location l = new Location(translateProcessToLocationProvider(processingMethod)); + + double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); + Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP); + + if (timeStamp != null) { + l.setTime(timeStamp); + } else { + Log.w(TAG, "getGpsLocation - No timestamp for GPS location."); + } + + if (coords != null) { + l.setLatitude(coords[0]); + l.setLongitude(coords[1]); + l.setAltitude(coords[2]); + } else { + Log.w(TAG, "getGpsLocation - No coordinates for GPS location"); + } + + return l; + } + + private boolean setGpsLocation(Location l) { + if (l == null) { + return false; + } + + double[] coords = { l.getLatitude(), l.getLongitude(), l.getAltitude() }; + String processMethod = translateLocationProviderToProcess(l.getProvider()); + long timestamp = l.getTime(); + + set(CaptureRequest.JPEG_GPS_TIMESTAMP, timestamp); + set(CaptureRequest.JPEG_GPS_COORDINATES, coords); + + if (processMethod == null) { + Log.w(TAG, "setGpsLocation - No process method, Location is not from a GPS or NETWORK" + + "provider"); + } else { + setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, processMethod); + } + return true; + } + private StreamConfigurationMap getStreamConfigurationMap() { StreamConfiguration[] configurations = getBase( CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS); @@ -342,6 +668,75 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return new StreamConfigurationMap(configurations, minFrameDurations, stallDurations); } + private <T> Integer getMaxRegions(Key<T> key) { + final int AE = 0; + final int AWB = 1; + final int AF = 2; + + // The order of the elements is: (AE, AWB, AF) + int[] maxRegions = getBase(CameraCharacteristics.CONTROL_MAX_REGIONS); + + if (maxRegions == null) { + return null; + } + + if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) { + return maxRegions[AE]; + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) { + return maxRegions[AWB]; + } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) { + return maxRegions[AF]; + } else { + throw new AssertionError("Invalid key " + key); + } + } + + private <T> Integer getMaxNumOutputs(Key<T> key) { + final int RAW = 0; + final int PROC = 1; + final int PROC_STALLING = 2; + + // The order of the elements is: (raw, proc+nonstalling, proc+stalling) + int[] maxNumOutputs = getBase(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS); + + if (maxNumOutputs == null) { + return null; + } + + if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) { + return maxNumOutputs[RAW]; + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) { + return maxNumOutputs[PROC]; + } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) { + return maxNumOutputs[PROC_STALLING]; + } else { + throw new AssertionError("Invalid key " + key); + } + } + + private <T> TonemapCurve getTonemapCurve() { + float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED); + float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN); + float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE); + if (red == null || green == null || blue == null) { + return null; + } + TonemapCurve tc = new TonemapCurve(red, green, blue); + return tc; + } + + private <T> void setBase(CameraCharacteristics.Key<T> key, T value) { + setBase(key.getNativeKey(), value); + } + + private <T> void setBase(CaptureResult.Key<T> key, T value) { + setBase(key.getNativeKey(), value); + } + + private <T> void setBase(CaptureRequest.Key<T> key, T value) { + setBase(key.getNativeKey(), value); + } + private <T> void setBase(Key<T> key, T value) { int tag = key.getTag(); @@ -369,8 +764,11 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return setAvailableFormats((int[]) value); } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) { return setFaceRectangles((Rect[]) value); + } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) { + return setTonemapCurve((TonemapCurve) value); + } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) { + return setGpsLocation((Location) value); } - // For other keys, set() falls back to setBase(). return false; } @@ -424,6 +822,24 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return true; } + private <T> boolean setTonemapCurve(TonemapCurve tc) { + if (tc == null) { + return false; + } + + float[][] curve = new float[3][]; + for (int i = TonemapCurve.CHANNEL_RED; i <= TonemapCurve.CHANNEL_BLUE; i++) { + int pointCount = tc.getPointCount(i); + curve[i] = new float[pointCount * TonemapCurve.POINT_SIZE]; + tc.copyColorCurve(i, curve[i], 0); + } + setBase(CaptureRequest.TONEMAP_CURVE_RED, curve[0]); + setBase(CaptureRequest.TONEMAP_CURVE_GREEN, curve[1]); + setBase(CaptureRequest.TONEMAP_CURVE_BLUE, curve[2]); + + return true; + } + private long mMetadataPtr; // native CameraMetadata* private native long nativeAllocate(); @@ -440,6 +856,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { private native synchronized byte[] nativeReadValues(int tag); private native synchronized void nativeWriteValues(int tag, byte[] src); + private native synchronized void nativeDump() throws IOException; // dump to ALOGD private static native int nativeGetTagFromKey(String keyName) throws IllegalArgumentException; @@ -531,6 +948,22 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { return nativeReadValues(tag); } + /** + * Dumps the native metadata contents to logcat. + * + * <p>Visibility for testing/debugging only. The results will not + * include any synthesized keys, as they are invisible to the native layer.</p> + * + * @hide + */ + public void dumpToLog() { + try { + nativeDump(); + } catch (IOException e) { + Log.wtf(TAG, "Dump logging failed", e); + } + } + @Override protected void finalize() throws Throwable { try { @@ -574,6 +1007,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { new MarshalQueryableString(), new MarshalQueryableReprocessFormatsMap(), new MarshalQueryableRange(), + new MarshalQueryablePair(), new MarshalQueryableMeteringRectangle(), new MarshalQueryableColorSpaceTransform(), new MarshalQueryableStreamConfiguration(), @@ -599,5 +1033,4 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable { nativeClassInit(); registerAllMarshalers(); } - } diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java new file mode 100644 index 0000000..0a9935d --- /dev/null +++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java @@ -0,0 +1,158 @@ +/* + * 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.hardware.camera2.marshal.impl; + +import android.hardware.camera2.marshal.Marshaler; +import android.hardware.camera2.marshal.MarshalQueryable; +import android.hardware.camera2.marshal.MarshalRegistry; +import android.hardware.camera2.utils.TypeReference; +import android.util.Pair; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; + +/** + * Marshal {@link Pair} to/from any native type + */ +public class MarshalQueryablePair<T1, T2> + implements MarshalQueryable<Pair<T1, T2>> { + + private class MarshalerPair extends Marshaler<Pair<T1, T2>> { + private final Class<? super Pair<T1, T2>> mClass; + private final Constructor<Pair<T1, T2>> mConstructor; + /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */ + private final Marshaler<T1> mNestedTypeMarshalerFirst; + /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */ + private final Marshaler<T2> mNestedTypeMarshalerSecond; + + @SuppressWarnings("unchecked") + protected MarshalerPair(TypeReference<Pair<T1, T2>> typeReference, + int nativeType) { + super(MarshalQueryablePair.this, typeReference, nativeType); + + mClass = typeReference.getRawType(); + + /* + * Lookup the actual type arguments, e.g. Pair<Integer, Float> --> [Integer, Float] + * and then get the marshalers for that managed type. + */ + ParameterizedType paramType; + try { + paramType = (ParameterizedType) typeReference.getType(); + } catch (ClassCastException e) { + throw new AssertionError("Raw use of Pair is not supported", e); + } + + // Get type marshaler for T1 + { + Type actualTypeArgument = paramType.getActualTypeArguments()[0]; + + TypeReference<?> actualTypeArgToken = + TypeReference.createSpecializedTypeReference(actualTypeArgument); + + mNestedTypeMarshalerFirst = (Marshaler<T1>)MarshalRegistry.getMarshaler( + actualTypeArgToken, mNativeType); + } + // Get type marshaler for T2 + { + Type actualTypeArgument = paramType.getActualTypeArguments()[1]; + + TypeReference<?> actualTypeArgToken = + TypeReference.createSpecializedTypeReference(actualTypeArgument); + + mNestedTypeMarshalerSecond = (Marshaler<T2>)MarshalRegistry.getMarshaler( + actualTypeArgToken, mNativeType); + } + try { + mConstructor = (Constructor<Pair<T1, T2>>)mClass.getConstructor( + Object.class, Object.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + @Override + public void marshal(Pair<T1, T2> value, ByteBuffer buffer) { + if (value.first == null) { + throw new UnsupportedOperationException("Pair#first must not be null"); + } else if (value.second == null) { + throw new UnsupportedOperationException("Pair#second must not be null"); + } + + mNestedTypeMarshalerFirst.marshal(value.first, buffer); + mNestedTypeMarshalerSecond.marshal(value.second, buffer); + } + + @Override + public Pair<T1, T2> unmarshal(ByteBuffer buffer) { + T1 first = mNestedTypeMarshalerFirst.unmarshal(buffer); + T2 second = mNestedTypeMarshalerSecond.unmarshal(buffer); + + try { + return mConstructor.newInstance(first, second); + } catch (InstantiationException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (IllegalArgumentException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } + } + + @Override + public int getNativeSize() { + int firstSize = mNestedTypeMarshalerFirst.getNativeSize(); + int secondSize = mNestedTypeMarshalerSecond.getNativeSize(); + + if (firstSize != NATIVE_SIZE_DYNAMIC && secondSize != NATIVE_SIZE_DYNAMIC) { + return firstSize + secondSize; + } else { + return NATIVE_SIZE_DYNAMIC; + } + } + + @Override + public int calculateMarshalSize(Pair<T1, T2> value) { + int nativeSize = getNativeSize(); + + if (nativeSize != NATIVE_SIZE_DYNAMIC) { + return nativeSize; + } else { + int firstSize = mNestedTypeMarshalerFirst.calculateMarshalSize(value.first); + int secondSize = mNestedTypeMarshalerSecond.calculateMarshalSize(value.second); + + return firstSize + secondSize; + } + } + } + + @Override + public Marshaler<Pair<T1, T2>> createMarshaler(TypeReference<Pair<T1, T2>> managedType, + int nativeType) { + return new MarshalerPair(managedType, nativeType); + } + + @Override + public boolean isTypeMappingSupported(TypeReference<Pair<T1, T2>> managedType, int nativeType) { + return (Pair.class.equals(managedType.getRawType())); + } + +} diff --git a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java index fa8c8ea..b4289db 100644 --- a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java +++ b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java @@ -139,8 +139,8 @@ public final class ColorSpaceTransform { throw new IllegalArgumentException("row out of range"); } - int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR]; - int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR]; + int numerator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_NUMERATOR]; + int denominator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_DENOMINATOR]; return new Rational(numerator, denominator); } @@ -162,7 +162,7 @@ public final class ColorSpaceTransform { public void copyElements(Rational[] destination, int offset) { checkArgumentNonnegative(offset, "offset must not be negative"); checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT) { + if (destination.length - offset < COUNT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } @@ -197,7 +197,7 @@ public final class ColorSpaceTransform { public void copyElements(int[] destination, int offset) { checkArgumentNonnegative(offset, "offset must not be negative"); checkNotNull(destination, "destination must not be null"); - if (destination.length + offset < COUNT_INT) { + if (destination.length - offset < COUNT_INT) { throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); } diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java index b328f57..9bbc33a 100644 --- a/core/java/android/hardware/camera2/params/LensShadingMap.java +++ b/core/java/android/hardware/camera2/params/LensShadingMap.java @@ -28,7 +28,7 @@ import java.util.Arrays; /** * Immutable class for describing a {@code 4 x N x M} lens shading map of floats. * - * @see CameraCharacteristics#LENS_SHADING_MAP + * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP */ public final class LensShadingMap { @@ -62,12 +62,12 @@ public final class LensShadingMap { public LensShadingMap(final float[] elements, final int rows, final int columns) { mRows = checkArgumentPositive(rows, "rows must be positive"); - mColumns = checkArgumentPositive(rows, "columns must be positive"); + mColumns = checkArgumentPositive(columns, "columns must be positive"); mElements = checkNotNull(elements, "elements must not be null"); if (elements.length != getGainFactorCount()) { throw new IllegalArgumentException("elements must be " + getGainFactorCount() + - " length"); + " length, received " + elements.length); } // Every element must be finite and >= 1.0f @@ -242,4 +242,4 @@ public final class LensShadingMap { private final int mRows; private final int mColumns; private final float[] mElements; -}; +} diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java index a26c57d..93fd053 100644 --- a/core/java/android/hardware/camera2/params/MeteringRectangle.java +++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.hardware.camera2.params; import android.util.Size; @@ -25,22 +26,50 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.utils.HashCodeHelpers; /** - * An immutable class to represent a rectangle {@code (x,y, width, height)} with an - * additional weight component. - * - * </p>The rectangle is defined to be inclusive of the specified coordinates.</p> - * - * <p>When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel + * An immutable class to represent a rectangle {@code (x, y, width, height)} with an additional + * weight component. + * <p> + * The rectangle is defined to be inclusive of the specified coordinates. + * </p> + * <p> + * When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel * array, with {@code (0,0)} being the top-left pixel in the * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and * {@code (android.sensor.info.activeArraySize.width - 1, - * android.sensor.info.activeArraySize.height - 1)} - * being the bottom-right pixel in the active pixel array. + * android.sensor.info.activeArraySize.height - 1)} being the bottom-right pixel in the active pixel + * array. + * </p> + * <p> + * The weight must range from {@value #METERING_WEIGHT_MIN} to {@value #METERING_WEIGHT_MAX} + * inclusively, and represents a weight for every pixel in the area. This means that a large + * metering area with the same weight as a smaller area will have more effect in the metering + * result. Metering areas can partially overlap and the camera device will add the weights in the + * overlap rectangle. + * </p> + * <p> + * If all rectangles have 0 weight, then no specific metering area needs to be used by the camera + * device. If the metering rectangle is outside the used android.scaler.cropRegion returned in + * capture result metadata, the camera device will ignore the sections outside the rectangle and + * output the used sections in the result metadata. * </p> - * - * <p>The metering weight is nonnegative.</p> */ public final class MeteringRectangle { + /** + * The minimum value of valid metering weight. + */ + public static final int METERING_WEIGHT_MIN = 0; + + /** + * The maximum value of valid metering weight. + */ + public static final int METERING_WEIGHT_MAX = 1000; + + /** + * Weights set to this value will cause the camera device to ignore this rectangle. + * If all metering rectangles are weighed with 0, the camera device will choose its own metering + * rectangles. + */ + public static final int METERING_WEIGHT_DONT_CARE = 0; private final int mX; private final int mY; @@ -55,16 +84,17 @@ public final class MeteringRectangle { * @param y coordinate >= 0 * @param width width >= 0 * @param height height >= 0 - * @param meteringWeight weight >= 0 - * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @param meteringWeight weight between {@value #METERING_WEIGHT_MIN} and + * {@value #METERING_WEIGHT_MAX} inclusively + * @throws IllegalArgumentException if any of the parameters were negative */ public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) { mX = checkArgumentNonnegative(x, "x must be nonnegative"); mY = checkArgumentNonnegative(y, "y must be nonnegative"); mWidth = checkArgumentNonnegative(width, "width must be nonnegative"); mHeight = checkArgumentNonnegative(height, "height must be nonnegative"); - mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative"); + mWeight = checkArgumentInRange( + meteringWeight, METERING_WEIGHT_MIN, METERING_WEIGHT_MAX, "meteringWeight"); } /** @@ -74,7 +104,7 @@ public final class MeteringRectangle { * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0 * @param meteringWeight weight >= 0 * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws IllegalArgumentException if any of the parameters were negative * @throws NullPointerException if any of the arguments were null */ public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) { @@ -94,7 +124,7 @@ public final class MeteringRectangle { * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0 * @param meteringWeight weight >= 0 * - * @throws IllegalArgumentException if any of the parameters were non-negative + * @throws IllegalArgumentException if any of the parameters were negative * @throws NullPointerException if any of the arguments were null */ public MeteringRectangle(Rect rect, int meteringWeight) { @@ -210,7 +240,7 @@ public final class MeteringRectangle { && mY == other.mY && mWidth == other.mWidth && mHeight == other.mHeight - && mWidth == other.mWidth); + && mWeight == other.mWeight); } /** diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java index a71a74d..723eda1 100644 --- a/core/java/android/hardware/hdmi/HdmiCec.java +++ b/core/java/android/hardware/hdmi/HdmiCec.java @@ -194,6 +194,8 @@ public final class HdmiCec { DEVICE_RECORDER, // ADDR_RECORDER_3 DEVICE_TUNER, // ADDR_TUNER_4 DEVICE_PLAYBACK, // ADDR_PLAYBACK_3 + DEVICE_RESERVED, + DEVICE_RESERVED, DEVICE_TV, // ADDR_SPECIFIC_USE }; @@ -210,6 +212,8 @@ public final class HdmiCec { "Recorder_3", "Tuner_4", "Playback_3", + "Reserved_1", + "Reserved_2", "Secondary_TV", }; diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java index cd86cd8..dcb3624 100644 --- a/core/java/android/hardware/hdmi/HdmiCecClient.java +++ b/core/java/android/hardware/hdmi/HdmiCecClient.java @@ -69,44 +69,28 @@ public final class HdmiCecClient { * Send <Active Source> message. */ public void sendActiveSource() { - try { - mService.sendActiveSource(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendActiveSource threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Inactive Source> message. */ public void sendInactiveSource() { - try { - mService.sendInactiveSource(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendInactiveSource threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Text View On> message. */ public void sendTextViewOn() { - try { - mService.sendTextViewOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendTextViewOn threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** * Send <Image View On> message. */ public void sendImageViewOn() { - try { - mService.sendImageViewOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "sendImageViewOn threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** @@ -116,11 +100,7 @@ public final class HdmiCecClient { * {@link HdmiCec#ADDR_TV}. */ public void sendGiveDevicePowerStatus(int address) { - try { - mService.sendGiveDevicePowerStatus(mBinder, address); - } catch (RemoteException e) { - Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e); - } + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); } /** @@ -133,11 +113,7 @@ public final class HdmiCecClient { * @return true if TV is on; otherwise false. */ public boolean isTvOn() { - try { - return mService.isTvOn(mBinder); - } catch (RemoteException e) { - Log.e(TAG, "isTvOn threw exception ", e); - } - return false; + Log.w(TAG, "In transition to HdmiControlManager. Will not work."); + return true; } } diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java index 10b058c..03c46d8 100644 --- a/core/java/android/hardware/hdmi/HdmiCecManager.java +++ b/core/java/android/hardware/hdmi/HdmiCecManager.java @@ -45,15 +45,7 @@ public final class HdmiCecManager { * @return {@link HdmiCecClient} instance. {@code null} on failure. */ public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) { - if (mService == null) { - return null; - } - try { - IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener)); - return HdmiCecClient.create(mService, b); - } catch (RemoteException e) { - return null; - } + return HdmiCecClient.create(mService, null); } private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) { diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java index ddaf870..62fa279 100644 --- a/core/java/android/hardware/hdmi/HdmiCecMessage.java +++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java @@ -46,7 +46,7 @@ public final class HdmiCecMessage implements Parcelable { public HdmiCecMessage(int source, int destination, int opcode, byte[] params) { mSource = source; mDestination = destination; - mOpcode = opcode; + mOpcode = opcode & 0xFF; mParams = Arrays.copyOf(params, params.length); } @@ -123,6 +123,7 @@ public final class HdmiCecMessage implements Parcelable { * @param p HdmiCecMessage object to read the Rating from * @return a new HdmiCecMessage created from the data in the parcel */ + @Override public HdmiCecMessage createFromParcel(Parcel p) { int source = p.readInt(); int destination = p.readInt(); @@ -131,6 +132,7 @@ public final class HdmiCecMessage implements Parcelable { p.readByteArray(params); return new HdmiCecMessage(source, destination, opcode, params); } + @Override public HdmiCecMessage[] newArray(int size) { return new HdmiCecMessage[size]; } @@ -139,11 +141,40 @@ public final class HdmiCecMessage implements Parcelable { @Override public String toString() { StringBuffer s = new StringBuffer(); - s.append(String.format("src: %d dst: %d op: %2X params: ", mSource, mDestination, mOpcode)); - for (byte data : mParams) { - s.append(String.format("%02X ", data)); + s.append(String.format("<%s> src: %d, dst: %d", + opcodeToString(mOpcode), mSource, mDestination)); + if (mParams.length > 0) { + s.append(", params:"); + for (byte data : mParams) { + s.append(String.format(" %02X", data)); + } } return s.toString(); } + + private static String opcodeToString(int opcode) { + switch (opcode) { + case HdmiCec.MESSAGE_FEATURE_ABORT: + return "Feature Abort"; + case HdmiCec.MESSAGE_CEC_VERSION: + return "CEC Version"; + case HdmiCec.MESSAGE_REQUEST_ARC_INITIATION: + return "Request ARC Initiation"; + case HdmiCec.MESSAGE_REQUEST_ARC_TERMINATION: + return "Request ARC Termination"; + case HdmiCec.MESSAGE_REPORT_ARC_INITIATED: + return "Report ARC Initiated"; + case HdmiCec.MESSAGE_REPORT_ARC_TERMINATED: + return "Report ARC Terminated"; + case HdmiCec.MESSAGE_TEXT_VIEW_ON: + return "Text View On"; + case HdmiCec.MESSAGE_ACTIVE_SOURCE: + return "Active Source"; + case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS: + return "Give Device Power Status"; + default: + return String.format("Opcode: %02X", opcode); + } + } } diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java index 83da29a..f0bd237 100644 --- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java +++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java @@ -90,7 +90,7 @@ public final class HdmiPlaybackClient { public void queryDisplayStatus(DisplayStatusCallback callback) { // TODO: PendingResult. try { - mService.oneTouchPlay(getCallbackWrapper(callback)); + mService.queryDisplayStatus(getCallbackWrapper(callback)); } catch (RemoteException e) { Log.e(TAG, "queryDisplayStatus threw exception ", e); } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 06d8e4a..857e335 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -69,6 +69,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; final WeakReference<AbstractInputMethodService> mTarget; + final Context mContext; final HandlerCaller mCaller; final WeakReference<InputMethod> mInputMethod; final int mTargetSdkVersion; @@ -111,8 +112,8 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { mTarget = new WeakReference<AbstractInputMethodService>(context); - mCaller = new HandlerCaller(context.getApplicationContext(), null, - this, true /*asyncHandler*/); + mContext = context.getApplicationContext(); + mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/); mInputMethod = new WeakReference<InputMethod>(inputMethod); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; } @@ -186,7 +187,7 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs)msg.obj; inputMethod.createSession(new InputMethodSessionCallbackWrapper( - mCaller.mContext, (InputChannel)args.arg1, + mContext, (InputChannel)args.arg1, (IInputSessionCallback)args.arg2)); args.recycle(); return; diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4bccaf1..3417de1 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,6 +39,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.view.Gravity; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -679,7 +680,7 @@ public class InputMethodService extends AbstractInputMethodService { mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, - false); + WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); if (mHardwareAccelerated) { mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index a9bace1..795117e 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -37,6 +37,8 @@ public class SoftInputWindow extends Dialog { final Callback mCallback; final KeyEvent.Callback mKeyEventCallback; final KeyEvent.DispatcherState mDispatcherState; + final int mWindowType; + final int mGravity; final boolean mTakesFocus; private final Rect mBounds = new Rect(); @@ -64,12 +66,14 @@ public class SoftInputWindow extends Dialog { */ public SoftInputWindow(Context context, String name, int theme, Callback callback, KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState, - boolean takesFocus) { + int windowType, int gravity, boolean takesFocus) { super(context, theme); mName = name; mCallback = callback; mKeyEventCallback = keyEventCallback; mDispatcherState = dispatcherState; + mWindowType = windowType; + mGravity = gravity; mTakesFocus = takesFocus; initDockWindow(); } @@ -97,47 +101,6 @@ public class SoftInputWindow extends Dialog { } /** - * Get the size of the DockWindow. - * - * @return If the DockWindow sticks to the top or bottom of the screen, the - * return value is the height of the DockWindow, and its width is - * equal to the width of the screen; If the DockWindow sticks to the - * left or right of the screen, the return value is the width of the - * DockWindow, and its height is equal to the height of the screen. - */ - public int getSize() { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - - if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { - return lp.height; - } else { - return lp.width; - } - } - - /** - * Set the size of the DockWindow. - * - * @param size If the DockWindow sticks to the top or bottom of the screen, - * <var>size</var> is the height of the DockWindow, and its width is - * equal to the width of the screen; If the DockWindow sticks to the - * left or right of the screen, <var>size</var> is the width of the - * DockWindow, and its height is equal to the height of the screen. - */ - public void setSize(int size) { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - - if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { - lp.width = -1; - lp.height = size; - } else { - lp.width = size; - lp.height = -1; - } - getWindow().setAttributes(lp); - } - - /** * Set which boundary of the screen the DockWindow sticks to. * * @param gravity The boundary of the screen to stick. See {#link @@ -147,18 +110,22 @@ public class SoftInputWindow extends Dialog { */ public void setGravity(int gravity) { WindowManager.LayoutParams lp = getWindow().getAttributes(); - - boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM); - lp.gravity = gravity; + updateWidthHeight(lp); + getWindow().setAttributes(lp); + } - boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM); + public int getGravity() { + return getWindow().getAttributes().gravity; + } - if (oldIsVertical != newIsVertical) { - int tmp = lp.width; - lp.width = lp.height; - lp.height = tmp; - getWindow().setAttributes(lp); + private void updateWidthHeight(WindowManager.LayoutParams lp) { + if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + } else { + lp.width = WindowManager.LayoutParams.WRAP_CONTENT; + lp.height = WindowManager.LayoutParams.MATCH_PARENT; } } @@ -201,14 +168,11 @@ public class SoftInputWindow extends Dialog { private void initDockWindow() { WindowManager.LayoutParams lp = getWindow().getAttributes(); - lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD; + lp.type = mWindowType; lp.setTitle(mName); - lp.gravity = Gravity.BOTTOM; - lp.width = -1; - // Let the input method window's orientation follow sensor based rotation - // Turn this off for now, it is very problematic. - //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + lp.gravity = mGravity; + updateWidthHeight(lp); getWindow().setAttributes(lp); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2f2aba3..a48a388 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -40,6 +40,7 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.Protocol; import java.net.InetAddress; @@ -807,11 +808,34 @@ public class ConnectivityManager { * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. */ public int startUsingNetworkFeature(int networkType, String feature) { - try { - return mService.startUsingNetworkFeature(networkType, feature, - new Binder()); - } catch (RemoteException e) { - return -1; + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " + + feature); + return PhoneConstants.APN_REQUEST_FAILED; + } + + NetworkRequest request = null; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) { + Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); + renewRequestLocked(l); + if (l.currentNetwork != null) { + return PhoneConstants.APN_ALREADY_ACTIVE; + } else { + return PhoneConstants.APN_REQUEST_STARTED; + } + } + + request = requestNetworkForFeatureLocked(netCap); + } + if (request != null) { + Log.d(TAG, "starting startUsingNeworkFeature for request " + request); + return PhoneConstants.APN_REQUEST_STARTED; + } else { + Log.d(TAG, " request Failed"); + return PhoneConstants.APN_REQUEST_FAILED; } } @@ -831,11 +855,172 @@ public class ConnectivityManager { * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. */ public int stopUsingNetworkFeature(int networkType, String feature) { - try { - return mService.stopUsingNetworkFeature(networkType, feature); - } catch (RemoteException e) { + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " + + feature); return -1; } + + NetworkRequest request = removeRequestForFeature(netCap); + if (request != null) { + Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature); + releaseNetworkRequest(request); + } + return 1; + } + + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + if (networkType == TYPE_MOBILE) { + int cap = -1; + if ("enableMMS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_MMS; + } else if ("enableSUPL".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_SUPL; + } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_DUN; + } else if ("enableHIPRI".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_INTERNET; + } else if ("enableFOTA".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_FOTA; + } else if ("enableIMS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_IMS; + } else if ("enableCBS".equals(feature)) { + cap = NetworkCapabilities.NET_CAPABILITY_CBS; + } else { + return null; + } + NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + netCap.addNetworkCapability(cap); + return netCap; + } else if (networkType == TYPE_WIFI) { + if ("p2p".equals(feature)) { + NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P); + return netCap; + } + } + return null; + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + if (netCap == null) return TYPE_NONE; + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + return TYPE_MOBILE_CBS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { + return TYPE_MOBILE_IMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { + return TYPE_MOBILE_FOTA; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { + return TYPE_MOBILE_DUN; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { + return TYPE_MOBILE_SUPL; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { + return TYPE_MOBILE_MMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return TYPE_MOBILE_HIPRI; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) { + return TYPE_WIFI_P2P; + } + return TYPE_NONE; + } + + private static class LegacyRequest { + NetworkCapabilities networkCapabilities; + NetworkRequest networkRequest; + int expireSequenceNumber; + Network currentNetwork; + int delay = -1; + NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() { + @Override + public void onAvailable(NetworkRequest request, Network network) { + currentNetwork = network; + Log.d(TAG, "startUsingNetworkFeature got Network:" + network); + network.bindProcessForHostResolution(); + } + @Override + public void onLost(NetworkRequest request, Network network) { + if (network.equals(currentNetwork)) { + currentNetwork = null; + network.unbindProcessForHostResolution(); + } + Log.d(TAG, "startUsingNetworkFeature lost Network:" + network); + } + }; + } + + private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests = + new HashMap<NetworkCapabilities, LegacyRequest>(); + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) return l.networkRequest; + } + return null; + } + + private void renewRequestLocked(LegacyRequest l) { + l.expireSequenceNumber++; + Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber); + sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + int ourSeqNum = -1; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l == null) return; + ourSeqNum = l.expireSequenceNumber; + if (l.expireSequenceNumber == sequenceNum) { + releaseNetworkRequest(l.networkRequest); + sLegacyRequests.remove(netCap); + } + } + Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum); + } + + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + int delay = -1; + int type = legacyTypeForNetworkCapabilities(netCap); + try { + delay = mService.getRestoreDefaultNetworkDelay(type); + } catch (RemoteException e) {} + LegacyRequest l = new LegacyRequest(); + l.networkCapabilities = netCap; + l.delay = delay; + l.expireSequenceNumber = 0; + l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0, + REQUEST, type); + if (l.networkRequest == null) return null; + sLegacyRequests.put(netCap, l); + sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay); + return l.networkRequest; + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + if (delay >= 0) { + Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay); + Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap); + sCallbackHandler.sendMessageDelayed(msg, delay); + } + } + + private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.remove(netCap); + if (l == null) return null; + return l.networkRequest; + } } /** @@ -1782,8 +1967,10 @@ public class ConnectivityManager { public static final int CALLBACK_RELEASED = BASE + 8; /** @hide */ public static final int CALLBACK_EXIT = BASE + 9; + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = BASE + 10; - private static class CallbackHandler extends Handler { + private class CallbackHandler extends Handler { private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap; private final AtomicInteger mRefCount; private static final String TAG = "ConnectivityManager.CallbackHandler"; @@ -1903,6 +2090,10 @@ public class ConnectivityManager { getLooper().quit(); break; } + case EXPIRE_LEGACY_REQUEST: { + expireRequest((NetworkCapabilities)message.obj, message.arg1); + break; + } } } @@ -1954,8 +2145,9 @@ public class ConnectivityManager { private final static int LISTEN = 1; private final static int REQUEST = 2; - private NetworkRequest somethingForNetwork(NetworkCapabilities need, - NetworkCallbackListener networkCallbackListener, int timeoutSec, int action) { + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, + NetworkCallbackListener networkCallbackListener, int timeoutSec, int action, + int legacyType) { NetworkRequest networkRequest = null; if (networkCallbackListener == null) { throw new IllegalArgumentException("null NetworkCallbackListener"); @@ -1968,7 +2160,7 @@ public class ConnectivityManager { new Binder()); } else { networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler), - timeoutSec, new Binder()); + timeoutSec, new Binder(), legacyType); } if (networkRequest != null) { synchronized(sNetworkCallbackListener) { @@ -1998,7 +2190,7 @@ public class ConnectivityManager { */ public NetworkRequest requestNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener) { - return somethingForNetwork(need, networkCallbackListener, 0, REQUEST); + return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE); } /** @@ -2021,7 +2213,8 @@ public class ConnectivityManager { */ public NetworkRequest requestNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener, int timeoutSec) { - return somethingForNetwork(need, networkCallbackListener, timeoutSec, REQUEST); + return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST, + TYPE_NONE); } /** @@ -2099,7 +2292,7 @@ public class ConnectivityManager { */ public NetworkRequest listenForNetwork(NetworkCapabilities need, NetworkCallbackListener networkCallbackListener) { - return somethingForNetwork(need, networkCallbackListener, 0, LISTEN); + return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE); } /** diff --git a/core/java/android/net/ConnectivityServiceProtocol.java b/core/java/android/net/ConnectivityServiceProtocol.java deleted file mode 100644 index 74096b4..0000000 --- a/core/java/android/net/ConnectivityServiceProtocol.java +++ /dev/null @@ -1,70 +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.net; - -import static com.android.internal.util.Protocol.BASE_CONNECTIVITY_SERVICE; - -/** - * Describes the Internal protocols used to communicate with ConnectivityService. - * @hide - */ -public class ConnectivityServiceProtocol { - - private static final int BASE = BASE_CONNECTIVITY_SERVICE; - - private ConnectivityServiceProtocol() {} - - /** - * This is a contract between ConnectivityService and various bearers. - * A NetworkFactory is an abstract entity that creates NetworkAgent objects. - * The bearers register with ConnectivityService using - * ConnectivityManager.registerNetworkFactory, where they pass in a Messenger - * to be used to deliver the following Messages. - */ - public static class NetworkFactoryProtocol { - private NetworkFactoryProtocol() {} - /** - * Pass a network request to the bearer. If the bearer believes it can - * satisfy the request it should connect to the network and create a - * NetworkAgent. Once the NetworkAgent is fully functional it will - * register itself with ConnectivityService using registerNetworkAgent. - * If the bearer cannot immediately satisfy the request (no network, - * user disabled the radio, lower-scored network) it should remember - * any NetworkRequests it may be able to satisfy in the future. It may - * disregard any that it will never be able to service, for example - * those requiring a different bearer. - * msg.obj = NetworkRequest - * msg.arg1 = score - the score of the any network currently satisfying this - * request. If this bearer knows in advance it cannot - * exceed this score it should not try to connect, holding the request - * for the future. - * Note that subsequent events may give a different (lower - * or higher) score for this request, transmitted to each - * NetworkFactory through additional CMD_REQUEST_NETWORK msgs - * with the same NetworkRequest but an updated score. - * Also, network conditions may change for this bearer - * allowing for a better score in the future. - */ - public static final int CMD_REQUEST_NETWORK = BASE; - - /** - * Cancel a network request - * msg.obj = NetworkRequest - */ - public static final int CMD_CANCEL_REQUEST = BASE + 1; - } -} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index baec36a..5f1ff3e 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -158,7 +158,7 @@ interface IConnectivityManager in NetworkCapabilities nc, int score); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, int timeoutSec, in IBinder binder); + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation); @@ -170,4 +170,6 @@ interface IConnectivityManager in PendingIntent operation); void releaseNetworkRequest(in NetworkRequest networkRequest); + + int getRestoreDefaultNetworkDelay(int networkType); } diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index e489e05..64516e6 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -94,11 +94,26 @@ public class Network implements Parcelable { mNetId = netId; } + private void connectToHost(Socket socket, String host, int port) throws IOException { + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = getAllByName(host); + // Try all but last address ignoring exceptions. + for (int i = 0; i < hostAddresses.length - 1; i++) { + try { + socket.connect(new InetSocketAddress(hostAddresses[i], port)); + return; + } catch (IOException e) { + } + } + // Try last address. Do throw exceptions. + socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port)); + } + @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { Socket socket = createSocket(); socket.bind(new InetSocketAddress(localHost, localPort)); - socket.connect(new InetSocketAddress(host, port)); + connectToHost(socket, host, port); return socket; } @@ -121,7 +136,7 @@ public class Network implements Parcelable { @Override public Socket createSocket(String host, int port) throws IOException { Socket socket = createSocket(); - socket.connect(new InetSocketAddress(host, port)); + connectToHost(socket, host, port); return socket; } diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index c2b06a2..7e8b1f1 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -24,85 +24,39 @@ import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; -import android.util.SparseArray; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** - * A Utility class for handling NetworkRequests. - * - * Created by bearer-specific code to handle tracking requests, scores, - * network data and handle communicating with ConnectivityService. Two - * abstract methods: connect and disconnect are used to act on the - * underlying bearer code. Connect is called when we have a NetworkRequest - * and our score is better than the current handling network's score, while - * disconnect is used when ConnectivityService requests a disconnect. + * A Utility class for handling for communicating between bearer-specific + * code and ConnectivityService. * * A bearer may have more than one NetworkAgent if it can simultaneously * support separate networks (IMS / Internet / MMS Apns on cellular, or - * perhaps connections with different SSID or P2P for Wi-Fi). The bearer - * code should pass its NetworkAgents the NetworkRequests each NetworkAgent - * can handle, demultiplexing for different network types. The bearer code - * can also filter out requests it can never handle. + * perhaps connections with different SSID or P2P for Wi-Fi). * - * Each NetworkAgent needs to be given a score and NetworkCapabilities for - * their potential network. While disconnected, the NetworkAgent will check - * each time its score changes or a NetworkRequest changes to see if - * the NetworkAgent can provide a higher scored network for a NetworkRequest - * that the NetworkAgent's NetworkCapabilties can satisfy. This condition will - * trigger a connect request via connect(). After connection, connection data - * should be given to the NetworkAgent by the bearer, including LinkProperties - * NetworkCapabilties and NetworkInfo. After that the NetworkAgent will register - * with ConnectivityService and forward the data on. * @hide */ public abstract class NetworkAgent extends Handler { - private final SparseArray<NetworkRequestAndScore> mNetworkRequests = new SparseArray<>(); - private boolean mConnectionRequested = false; - - private AsyncChannel mAsyncChannel; + private volatile AsyncChannel mAsyncChannel; private final String LOG_TAG; private static final boolean DBG = true; private static final boolean VDBG = true; - // TODO - this class shouldn't cache data or it runs the risk of getting out of sync - // Make the API require each of these when any is updated so we have the data we need, - // without caching. - private LinkProperties mLinkProperties; - private NetworkInfo mNetworkInfo; - private NetworkCapabilities mNetworkCapabilities; - private int mNetworkScore; - private boolean mRegistered = false; private final Context mContext; - private AtomicBoolean mHasRequests = new AtomicBoolean(false); - - // TODO - add a name member for logging purposes. - - protected final Object mLockObj = new Object(); - + private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>(); private static final int BASE = Protocol.BASE_NETWORK_AGENT; /** - * Sent by self to queue up a new/modified request. - * obj = NetworkRequestAndScore - */ - private static final int CMD_ADD_REQUEST = BASE + 1; - - /** - * Sent by self to queue up the removal of a request. - * obj = NetworkRequest - */ - private static final int CMD_REMOVE_REQUEST = BASE + 2; - - /** * Sent by ConnectivityService to the NetworkAgent to inform it of * suspected connectivity problems on its network. The NetworkAgent * should take steps to verify and correct connectivity. */ - public static final int CMD_SUSPECT_BAD = BASE + 3; + public static final int CMD_SUSPECT_BAD = BASE; /** * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to @@ -110,84 +64,63 @@ public abstract class NetworkAgent extends Handler { * Sent when the NetworkInfo changes, mainly due to change of state. * obj = NetworkInfo */ - public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 4; + public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkCapabilties. * obj = NetworkCapabilities */ - public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 5; + public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * NetworkProperties. * obj = NetworkProperties */ - public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 6; + public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; /** * Sent by the NetworkAgent to ConnectivityService to pass the current * network score. - * arg1 = network score int + * obj = network score Integer */ - public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 7; + public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; - public NetworkAgent(Looper looper, Context context, String logTag) { + public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni, + NetworkCapabilities nc, LinkProperties lp, int score) { super(looper); LOG_TAG = logTag; mContext = context; - } - - /** - * When conditions are right, register with ConnectivityService. - * Connditions include having a well defined network and a request - * that justifies it. The NetworkAgent will remain registered until - * disconnected. - * TODO - this should have all data passed in rather than caching - */ - private void registerSelf() { - synchronized(mLockObj) { - if (!mRegistered && mConnectionRequested && - mNetworkInfo != null && mNetworkInfo.isConnected() && - mNetworkCapabilities != null && - mLinkProperties != null && - mNetworkScore != 0) { - if (DBG) log("Registering NetworkAgent"); - mRegistered = true; - ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(mNetworkInfo), - new LinkProperties(mLinkProperties), - new NetworkCapabilities(mNetworkCapabilities), mNetworkScore); - } else if (DBG && !mRegistered) { - String err = "Not registering due to "; - if (mConnectionRequested == false) err += "no Connect requested "; - if (mNetworkInfo == null) err += "null NetworkInfo "; - if (mNetworkInfo != null && mNetworkInfo.isConnected() == false) { - err += "NetworkInfo disconnected "; - } - if (mLinkProperties == null) err += "null LinkProperties "; - if (mNetworkCapabilities == null) err += "null NetworkCapabilities "; - if (mNetworkScore == 0) err += "null NetworkScore"; - log(err); - } + if (ni == null || nc == null || lp == null) { + throw new IllegalArgumentException(); } + + if (DBG) log("Registering NetworkAgent"); + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni), + new LinkProperties(lp), new NetworkCapabilities(nc), score); } @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { - synchronized (mLockObj) { - if (mAsyncChannel != null) { - log("Received new connection while already connected!"); - } else { - if (DBG) log("NetworkAgent fully connected"); - mAsyncChannel = new AsyncChannel(); - mAsyncChannel.connected(null, this, msg.replyTo); - mAsyncChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, - AsyncChannel.STATUS_SUCCESSFUL); + if (mAsyncChannel != null) { + log("Received new connection while already connected!"); + } else { + if (DBG) log("NetworkAgent fully connected"); + AsyncChannel ac = new AsyncChannel(); + ac.connected(null, this, msg.replyTo); + ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED, + AsyncChannel.STATUS_SUCCESSFUL); + synchronized (mPreConnectedQueue) { + mAsyncChannel = ac; + for (Message m : mPreConnectedQueue) { + ac.sendMessage(m); + } + mPreConnectedQueue.clear(); } } break; @@ -199,201 +132,69 @@ public abstract class NetworkAgent extends Handler { } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (DBG) log("NetworkAgent channel lost"); - disconnect(); - clear(); + // let the client know CS is done with us. + unwanted(); + synchronized (mPreConnectedQueue) { + mAsyncChannel = null; + } break; } case CMD_SUSPECT_BAD: { log("Unhandled Message " + msg); break; } - case CMD_ADD_REQUEST: { - handleAddRequest(msg); - break; - } - case CMD_REMOVE_REQUEST: { - handleRemoveRequest(msg); - break; - } - } - } - - private void clear() { - synchronized(mLockObj) { - mNetworkRequests.clear(); - mHasRequests.set(false); - mConnectionRequested = false; - mAsyncChannel = null; - mRegistered = false; - } - } - - private static class NetworkRequestAndScore { - NetworkRequest req; - int score; - - NetworkRequestAndScore(NetworkRequest networkRequest, int score) { - req = networkRequest; - this.score = score; - } - } - - private void handleAddRequest(Message msg) { - NetworkRequestAndScore n = (NetworkRequestAndScore)msg.obj; - // replaces old request, updating score - mNetworkRequests.put(n.req.requestId, n); - mHasRequests.set(true); - evalScores(); - } - - private void handleRemoveRequest(Message msg) { - NetworkRequest networkRequest = (NetworkRequest)msg.obj; - - if (mNetworkRequests.get(networkRequest.requestId) != null) { - mNetworkRequests.remove(networkRequest.requestId); - if (mNetworkRequests.size() == 0) mHasRequests.set(false); - evalScores(); } } - /** - * called to go through our list of requests and see if we're - * good enough to try connecting. - * - * Only does connects - we disconnect when requested via - * CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection - * between modules (bearer or ConnectivityService dies) or more commonly - * when the NetworkInfo reports to ConnectivityService it is disconnected. - */ - private void evalScores() { - if (mConnectionRequested) { - if (VDBG) log("evalScores - already trying - size=" + mNetworkRequests.size()); - // already trying - return; - } - if (VDBG) log("evalScores!"); - for (int i=0; i < mNetworkRequests.size(); i++) { - int score = mNetworkRequests.valueAt(i).score; - if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore); - if (score < mNetworkScore) { - // have a request that has a lower scored network servicing it - // (or no network) than we could provide, so lets connect! - mConnectionRequested = true; - connect(); - return; + private void queueOrSendMessage(int what, Object obj) { + synchronized (mPreConnectedQueue) { + if (mAsyncChannel != null) { + mAsyncChannel.sendMessage(what, obj); + } else { + Message msg = Message.obtain(); + msg.what = what; + msg.obj = obj; + mPreConnectedQueue.add(msg); } } } - public void addNetworkRequest(NetworkRequest networkRequest, int score) { - if (DBG) log("adding NetworkRequest " + networkRequest + " with score " + score); - sendMessage(obtainMessage(CMD_ADD_REQUEST, - new NetworkRequestAndScore(networkRequest, score))); - } - - public void removeNetworkRequest(NetworkRequest networkRequest) { - if (DBG) log("removing NetworkRequest " + networkRequest); - sendMessage(obtainMessage(CMD_REMOVE_REQUEST, networkRequest)); - } - /** * Called by the bearer code when it has new LinkProperties data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). */ public void sendLinkProperties(LinkProperties linkProperties) { - linkProperties = new LinkProperties(linkProperties); - synchronized(mLockObj) { - mLinkProperties = linkProperties; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, linkProperties); - } else { - registerSelf(); - } - } + queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties)); } /** * Called by the bearer code when it has new NetworkInfo data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). */ public void sendNetworkInfo(NetworkInfo networkInfo) { - networkInfo = new NetworkInfo(networkInfo); - synchronized(mLockObj) { - mNetworkInfo = networkInfo; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_INFO_CHANGED, networkInfo); - } else { - registerSelf(); - } - } + queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo)); } /** * Called by the bearer code when it has new NetworkCapabilities data. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy in anticipation of registering. This call - * may also prompt registration if it causes the NetworkAgent to meet - * the conditions (fully configured, connected, satisfys a request and - * has sufficient score). - * Note that if these capabilities make the network non-useful, - * ConnectivityServce will tear this network down. */ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { - networkCapabilities = new NetworkCapabilities(networkCapabilities); - synchronized(mLockObj) { - mNetworkCapabilities = networkCapabilities; - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, networkCapabilities); - } else { - registerSelf(); - } - } - } - - public NetworkCapabilities getNetworkCapabilities() { - synchronized(mLockObj) { - return new NetworkCapabilities(mNetworkCapabilities); - } + queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, + new NetworkCapabilities(networkCapabilities)); } /** * Called by the bearer code when it has a new score for this network. - * If we're a registered NetworkAgent, this new data will get forwarded on, - * otherwise we store a copy. */ - public synchronized void sendNetworkScore(int score) { - synchronized(mLockObj) { - mNetworkScore = score; - evalScores(); - if (mAsyncChannel != null) { - mAsyncChannel.sendMessage(EVENT_NETWORK_SCORE_CHANGED, mNetworkScore); - } else { - registerSelf(); - } - } - } - - public boolean hasRequests() { - return mHasRequests.get(); + public void sendNetworkScore(int score) { + queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score)); } - public boolean isConnectionRequested() { - synchronized(mLockObj) { - return mConnectionRequested; - } - } - - - abstract protected void connect(); - abstract protected void disconnect(); + /** + * Called when ConnectivityService has indicated they no longer want this network. + * The parent factory should (previously) have received indication of the change + * as well, either canceling NetworkRequests or altering their score such that this + * network won't be immediately requested again. + */ + abstract protected void unwanted(); protected void log(String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java new file mode 100644 index 0000000..a20e8e7 --- /dev/null +++ b/core/java/android/net/NetworkFactory.java @@ -0,0 +1,275 @@ +/* + * 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.net; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * @hide + **/ +public class NetworkFactory extends Handler { + private static final boolean DBG = true; + + private static final int BASE = Protocol.BASE_NETWORK_FACTORY; + /** + * Pass a network request to the bearer. If the bearer believes it can + * satisfy the request it should connect to the network and create a + * NetworkAgent. Once the NetworkAgent is fully functional it will + * register itself with ConnectivityService using registerNetworkAgent. + * If the bearer cannot immediately satisfy the request (no network, + * user disabled the radio, lower-scored network) it should remember + * any NetworkRequests it may be able to satisfy in the future. It may + * disregard any that it will never be able to service, for example + * those requiring a different bearer. + * msg.obj = NetworkRequest + * msg.arg1 = score - the score of the any network currently satisfying this + * request. If this bearer knows in advance it cannot + * exceed this score it should not try to connect, holding the request + * for the future. + * Note that subsequent events may give a different (lower + * or higher) score for this request, transmitted to each + * NetworkFactory through additional CMD_REQUEST_NETWORK msgs + * with the same NetworkRequest but an updated score. + * Also, network conditions may change for this bearer + * allowing for a better score in the future. + */ + public static final int CMD_REQUEST_NETWORK = BASE; + + /** + * Cancel a network request + * msg.obj = NetworkRequest + */ + public static final int CMD_CANCEL_REQUEST = BASE + 1; + + /** + * Internally used to set our best-guess score. + * msg.arg1 = new score + */ + private static final int CMD_SET_SCORE = BASE + 2; + + /** + * Internally used to set our current filter for coarse bandwidth changes with + * technology changes. + * msg.obj = new filter + */ + private static final int CMD_SET_FILTER = BASE + 3; + + private final Context mContext; + private final String LOG_TAG; + + private final SparseArray<NetworkRequestInfo> mNetworkRequests = + new SparseArray<NetworkRequestInfo>(); + + private int mScore; + private NetworkCapabilities mCapabilityFilter; + + private int mRefCount = 0; + private Messenger mMessenger = null; + + public NetworkFactory(Looper looper, Context context, String logTag, + NetworkCapabilities filter) { + super(looper); + LOG_TAG = logTag; + mContext = context; + mCapabilityFilter = filter; + } + + public void register() { + if (DBG) log("Registering NetworkFactory"); + if (mMessenger == null) { + mMessenger = new Messenger(this); + ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG); + } + } + + public void unregister() { + if (DBG) log("Unregistering NetworkFactory"); + if (mMessenger != null) { + ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger); + mMessenger = null; + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_REQUEST_NETWORK: { + handleAddRequest((NetworkRequest)msg.obj, msg.arg1); + break; + } + case CMD_CANCEL_REQUEST: { + handleRemoveRequest((NetworkRequest) msg.obj); + break; + } + case CMD_SET_SCORE: { + handleSetScore(msg.arg1); + break; + } + case CMD_SET_FILTER: { + handleSetFilter((NetworkCapabilities) msg.obj); + break; + } + } + } + + private class NetworkRequestInfo { + public final NetworkRequest request; + public int score; + public boolean requested; // do we have a request outstanding, limited by score + + public NetworkRequestInfo(NetworkRequest request, int score) { + this.request = request; + this.score = score; + this.requested = false; + } + } + + private void handleAddRequest(NetworkRequest request, int score) { + NetworkRequestInfo n = mNetworkRequests.get(request.requestId); + if (n == null) { + n = new NetworkRequestInfo(request, score); + mNetworkRequests.put(n.request.requestId, n); + } else { + n.score = score; + } + if (DBG) log("got request " + request + " with score " + score); + if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + + evalRequest(n); + } + + private void handleRemoveRequest(NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request.requestId); + if (n != null && n.requested) { + mNetworkRequests.remove(request.requestId); + releaseNetworkFor(n.request); + } + } + + private void handleSetScore(int score) { + mScore = score; + evalRequests(); + } + + private void handleSetFilter(NetworkCapabilities netCap) { + mCapabilityFilter = netCap; + evalRequests(); + } + + /** + * Overridable function to provide complex filtering. + * Called for every request every time a new NetworkRequest is seen + * and whenever the filterScore or filterNetworkCapabilities change. + * + * acceptRequest can be overriden to provide complex filter behavior + * for the incoming requests + * + * For output, this class will call {@link #needNetworkFor} and + * {@link #releaseNetworkFor} for every request that passes the filters. + * If you don't need to see every request, you can leave the base + * implementations of those two functions and instead override + * {@link #startNetwork} and {@link #stopNetwork}. + * + * If you want to see every score fluctuation on every request, set + * your score filter to a very high number and watch {@link #needNetworkFor}. + * + * @return {@code true} to accept the request. + */ + public boolean acceptRequest(NetworkRequest request, int score) { + return true; + } + + private void evalRequest(NetworkRequestInfo n) { + if (n.requested == false && n.score < mScore && + n.request.networkCapabilities.satisfiedByNetworkCapabilities( + mCapabilityFilter) && acceptRequest(n.request, n.score)) { + needNetworkFor(n.request, n.score); + n.requested = true; + } else if (n.requested == true && + (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities( + mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) { + releaseNetworkFor(n.request); + n.requested = false; + } + } + + private void evalRequests() { + for (int i = 0; i < mNetworkRequests.size(); i++) { + NetworkRequestInfo n = mNetworkRequests.valueAt(i); + + evalRequest(n); + } + } + + // override to do simple mode (request independent) + protected void startNetwork() { } + protected void stopNetwork() { } + + // override to do fancier stuff + protected void needNetworkFor(NetworkRequest networkRequest, int score) { + if (++mRefCount == 1) startNetwork(); + } + + protected void releaseNetworkFor(NetworkRequest networkRequest) { + if (--mRefCount == 0) stopNetwork(); + } + + + public void addNetworkRequest(NetworkRequest networkRequest, int score) { + sendMessage(obtainMessage(CMD_REQUEST_NETWORK, + new NetworkRequestInfo(networkRequest, score))); + } + + public void removeNetworkRequest(NetworkRequest networkRequest) { + sendMessage(obtainMessage(CMD_CANCEL_REQUEST, networkRequest)); + } + + public void setScoreFilter(int score) { + sendMessage(obtainMessage(CMD_SET_SCORE, score, 0)); + } + + public void setCapabilityFilter(NetworkCapabilities netCap) { + sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap))); + } + + protected void log(String s) { + Log.d(LOG_TAG, s); + } +} diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 9e656ee..d279412 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -188,6 +188,15 @@ public class NetworkInfo implements Parcelable { } /** + * @hide + */ + public void setType(int type) { + synchronized (this) { + mNetworkType = type; + } + } + + /** * Return a network-type-specific integer describing the subtype * of the network. * @return the network subtype @@ -198,7 +207,10 @@ public class NetworkInfo implements Parcelable { } } - void setSubtype(int subtype, String subtypeName) { + /** + * @hide + */ + public void setSubtype(int subtype, String subtypeName) { synchronized (this) { mSubtype = subtype; mSubtypeName = subtypeName; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 480cb05..47377e9 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -47,19 +47,19 @@ public class NetworkRequest implements Parcelable { public final int requestId; /** - * Set for legacy requests and the default. + * Set for legacy requests and the default. Set to TYPE_NONE for none. * Causes CONNECTIVITY_ACTION broadcasts to be sent. * @hide */ - public final boolean needsBroadcasts; + public final int legacyType; /** * @hide */ - public NetworkRequest(NetworkCapabilities nc, boolean needsBroadcasts, int rId) { + public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) { requestId = rId; networkCapabilities = nc; - this.needsBroadcasts = needsBroadcasts; + this.legacyType = legacyType; } /** @@ -68,7 +68,7 @@ public class NetworkRequest implements Parcelable { public NetworkRequest(NetworkRequest that) { networkCapabilities = new NetworkCapabilities(that.networkCapabilities); requestId = that.requestId; - needsBroadcasts = that.needsBroadcasts; + this.legacyType = that.legacyType; } // implement the Parcelable interface @@ -77,16 +77,16 @@ public class NetworkRequest implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(networkCapabilities, flags); - dest.writeInt(needsBroadcasts ? 1 : 0); + dest.writeInt(legacyType); dest.writeInt(requestId); } public static final Creator<NetworkRequest> CREATOR = new Creator<NetworkRequest>() { public NetworkRequest createFromParcel(Parcel in) { NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null); - boolean needsBroadcasts = (in.readInt() == 1); + int legacyType = in.readInt(); int requestId = in.readInt(); - NetworkRequest result = new NetworkRequest(nc, needsBroadcasts, requestId); + NetworkRequest result = new NetworkRequest(nc, legacyType, requestId); return result; } public NetworkRequest[] newArray(int size) { @@ -95,14 +95,14 @@ public class NetworkRequest implements Parcelable { }; public String toString() { - return "NetworkRequest [ id=" + requestId + ", needsBroadcasts=" + needsBroadcasts + + return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType + ", " + networkCapabilities.toString() + " ]"; } public boolean equals(Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; - return (that.needsBroadcasts == this.needsBroadcasts && + return (that.legacyType == this.legacyType && that.requestId == this.requestId && ((that.networkCapabilities == null && this.networkCapabilities == null) || (that.networkCapabilities != null && @@ -110,7 +110,7 @@ public class NetworkRequest implements Parcelable { } public int hashCode() { - return requestId + (needsBroadcasts ? 1013 : 2026) + + return requestId + (legacyType * 1013) + (networkCapabilities.hashCode() * 1051); } } diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java index b0449224..cabda5d 100644 --- a/core/java/android/nfc/cardemulation/AidGroup.java +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -2,6 +2,7 @@ package android.nfc.cardemulation; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -21,6 +22,8 @@ import android.util.Log; * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class * requires the AIDs to be input as a hexadecimal string, with an even amount of * hexadecimal characters, e.g. "F014811481". + * + * @hide */ public final class AidGroup implements Parcelable { /** @@ -30,7 +33,7 @@ public final class AidGroup implements Parcelable { static final String TAG = "AidGroup"; - final ArrayList<String> aids; + final List<String> aids; final String category; final String description; @@ -40,7 +43,7 @@ public final class AidGroup implements Parcelable { * @param aids The list of AIDs present in the group * @param category The category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT} */ - public AidGroup(ArrayList<String> aids, String category) { + public AidGroup(List<String> aids, String category) { if (aids == null || aids.size() == 0) { throw new IllegalArgumentException("No AIDS in AID group."); } @@ -72,7 +75,7 @@ public final class AidGroup implements Parcelable { /** * @return the list of AIDs in this group */ - public ArrayList<String> getAids() { + public List<String> getAids() { return aids; } @@ -121,11 +124,6 @@ public final class AidGroup implements Parcelable { } }; - /** - * @hide - * Note: description is not serialized, since it's not localized - * and resource identifiers don't make sense to persist. - */ static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { String category = parser.getAttributeValue(null, "category"); ArrayList<String> aids = new ArrayList<String>(); @@ -152,9 +150,6 @@ public final class AidGroup implements Parcelable { } } - /** - * @hide - */ public void writeAsXml(XmlSerializer out) throws IOException { out.attribute(null, "category", category); for (String aid : aids) { diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index e24a22a..4b9e890 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -303,12 +303,13 @@ public final class CardEmulation { } /** - * Registers a group of AIDs for the specified service. + * Registers a list of AIDs for a specific category for the + * specified service. * - * <p>If an AID group for that category was previously + * <p>If a list of AIDs for that category was previously * registered for this service (either statically * through the manifest, or dynamically by using this API), - * that AID group will be replaced with this one. + * that list of AIDs will be replaced with this one. * * <p>Note that you can only register AIDs for a service that * is running under the same UID as the caller of this API. Typically @@ -317,10 +318,13 @@ public final class CardEmulation { * be shared between packages using shared UIDs. * * @param service The component name of the service - * @param aidGroup The group of AIDs to be registered + * @param category The category of AIDs to be registered + * @param aids A list containing the AIDs to be registered * @return whether the registration was successful. */ - public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) { + public boolean registerAidsForService(ComponentName service, String category, + List<String> aids) { + AidGroup aidGroup = new AidGroup(aids, category); try { return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup); } catch (RemoteException e) { @@ -341,21 +345,24 @@ public final class CardEmulation { } /** - * Retrieves the currently registered AID group for the specified + * Retrieves the currently registered AIDs for the specified * category for a service. * - * <p>Note that this will only return AID groups that were dynamically - * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)} - * method. It will *not* return AID groups that were statically registered + * <p>Note that this will only return AIDs that were dynamically + * registered using {@link #registerAidsForService(ComponentName, String, List)} + * method. It will *not* return AIDs that were statically registered * in the manifest. * * @param service The component name of the service - * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT} - * @return The AID group, or null if it couldn't be found + * @param category The category for which the AIDs were registered, + * e.g. {@link #CATEGORY_PAYMENT} + * @return The list of AIDs registered for this category, or null if it couldn't be found. */ - public AidGroup getAidGroupForService(ComponentName service, String category) { + public List<String> getAidsForService(ComponentName service, String category) { try { - return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service, + category); + return (group != null ? group.getAids() : null); } catch (RemoteException e) { recoverService(); if (sService == null) { @@ -363,7 +370,9 @@ public final class CardEmulation { return null; } try { - return sService.getAidGroupForService(UserHandle.myUserId(), service, category); + AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service, + category); + return (group != null ? group.getAids() : null); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); return null; @@ -372,21 +381,21 @@ public final class CardEmulation { } /** - * Removes a registered AID group for the specified category for the + * Removes a previously registered list of AIDs for the specified category for the * service provided. * - * <p>Note that this will only remove AID groups that were dynamically - * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)} - * method. It will *not* remove AID groups that were statically registered in - * the manifest. If a dynamically registered AID group is removed using + * <p>Note that this will only remove AIDs that were dynamically + * registered using the {@link #registerAidsForService(ComponentName, String, List)} + * method. It will *not* remove AIDs that were statically registered in + * the manifest. If dynamically registered AIDs are removed using * this method, and a statically registered AID group for the same category * exists in the manifest, the static AID group will become active again. * * @param service The component name of the service - * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT} + * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT} * @return whether the group was successfully removed. */ - public boolean removeAidGroupForService(ComponentName service, String category) { + public boolean removeAidsForService(ComponentName service, String category) { try { return sService.removeAidGroupForService(UserHandle.myUserId(), service, category); } catch (RemoteException e) { diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/BaseBundle.java index e11f170..c2a45ba 100644 --- a/core/java/android/os/CommonBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -18,17 +18,16 @@ package android.os; import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; import java.io.Serializable; import java.util.ArrayList; -import java.util.List; +import java.util.Map; import java.util.Set; /** * A mapping from String values to various types. */ -abstract class CommonBundle implements Parcelable, Cloneable { +public class BaseBundle { private static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -64,7 +63,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * inside of the Bundle. * @param capacity Initial size of the ArrayMap. */ - CommonBundle(ClassLoader loader, int capacity) { + BaseBundle(ClassLoader loader, int capacity) { mMap = capacity > 0 ? new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>(); mClassLoader = loader == null ? getClass().getClassLoader() : loader; @@ -73,7 +72,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Constructs a new, empty Bundle. */ - CommonBundle() { + BaseBundle() { this((ClassLoader) null, 0); } @@ -83,11 +82,11 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param parcelledData a Parcel containing a Bundle */ - CommonBundle(Parcel parcelledData) { + BaseBundle(Parcel parcelledData) { readFromParcelInner(parcelledData); } - CommonBundle(Parcel parcelledData, int length) { + BaseBundle(Parcel parcelledData, int length) { readFromParcelInner(parcelledData, length); } @@ -98,7 +97,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param loader An explicit ClassLoader to use when instantiating objects * inside of the Bundle. */ - CommonBundle(ClassLoader loader) { + BaseBundle(ClassLoader loader) { this(loader, 0); } @@ -108,7 +107,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param capacity the initial capacity of the Bundle */ - CommonBundle(int capacity) { + BaseBundle(int capacity) { this((ClassLoader) null, capacity); } @@ -118,7 +117,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param b a Bundle to be copied. */ - CommonBundle(CommonBundle b) { + BaseBundle(BaseBundle b) { if (b.mParcelledData != null) { if (b.mParcelledData == EMPTY_PARCEL) { mParcelledData = EMPTY_PARCEL; @@ -149,7 +148,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @hide */ - String getPairValue() { + public String getPairValue() { unparcel(); int size = mMap.size(); if (size > 1) { @@ -229,7 +228,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * @hide */ - boolean isParcelled() { + public boolean isParcelled() { return mParcelledData != null; } @@ -238,7 +237,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @return the number of mappings as an int. */ - int size() { + public int size() { unparcel(); return mMap.size(); } @@ -246,7 +245,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Returns true if the mapping of this Bundle is empty, false otherwise. */ - boolean isEmpty() { + public boolean isEmpty() { unparcel(); return mMap.isEmpty(); } @@ -254,7 +253,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { /** * Removes all elements from the mapping of this Bundle. */ - void clear() { + public void clear() { unparcel(); mMap.clear(); } @@ -266,7 +265,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String key * @return true if the key is part of the mapping, false otherwise */ - boolean containsKey(String key) { + public boolean containsKey(String key) { unparcel(); return mMap.containsKey(key); } @@ -277,7 +276,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String key * @return an Object, or null */ - Object get(String key) { + public Object get(String key) { unparcel(); return mMap.get(key); } @@ -287,28 +286,38 @@ abstract class CommonBundle implements Parcelable, Cloneable { * * @param key a String key */ - void remove(String key) { + public void remove(String key) { unparcel(); mMap.remove(key); } /** - * Inserts all mappings from the given PersistableBundle into this CommonBundle. + * Inserts all mappings from the given PersistableBundle into this BaseBundle. * * @param bundle a PersistableBundle */ - void putAll(PersistableBundle bundle) { + public void putAll(PersistableBundle bundle) { unparcel(); bundle.unparcel(); mMap.putAll(bundle.mMap); } /** + * Inserts all mappings from the given Map into this BaseBundle. + * + * @param map a Map + */ + void putAll(Map map) { + unparcel(); + mMap.putAll(map); + } + + /** * Returns a Set containing the Strings used as keys in this Bundle. * * @return a Set of String keys */ - Set<String> keySet() { + public Set<String> keySet() { unparcel(); return mMap.keySet(); } @@ -368,7 +377,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int, or null */ - void putInt(String key, int value) { + public void putInt(String key, int value) { unparcel(); mMap.put(key, value); } @@ -380,7 +389,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long */ - void putLong(String key, long value) { + public void putLong(String key, long value) { unparcel(); mMap.put(key, value); } @@ -404,7 +413,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double */ - void putDouble(String key, double value) { + public void putDouble(String key, double value) { unparcel(); mMap.put(key, value); } @@ -416,7 +425,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String, or null */ - void putString(String key, String value) { + public void putString(String key, String value) { unparcel(); mMap.put(key, value); } @@ -536,7 +545,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value an int array object, or null */ - void putIntArray(String key, int[] value) { + public void putIntArray(String key, int[] value) { unparcel(); mMap.put(key, value); } @@ -548,7 +557,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a long array object, or null */ - void putLongArray(String key, long[] value) { + public void putLongArray(String key, long[] value) { unparcel(); mMap.put(key, value); } @@ -572,7 +581,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a double array object, or null */ - void putDoubleArray(String key, double[] value) { + public void putDoubleArray(String key, double[] value) { unparcel(); mMap.put(key, value); } @@ -584,7 +593,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @param value a String array object, or null */ - void putStringArray(String key, String[] value) { + public void putStringArray(String key, String[] value) { unparcel(); mMap.put(key, value); } @@ -602,18 +611,6 @@ abstract class CommonBundle implements Parcelable, Cloneable { } /** - * Inserts a PersistableBundle value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a Bundle object, or null - */ - void putPersistableBundle(String key, PersistableBundle value) { - unparcel(); - mMap.put(key, value); - } - - /** * Returns the value associated with the given key, or false if * no mapping of the desired type exists for the given key. * @@ -780,7 +777,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return an int value */ - int getInt(String key) { + public int getInt(String key) { unparcel(); return getInt(key, 0); } @@ -793,7 +790,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return an int value */ - int getInt(String key, int defaultValue) { + public int getInt(String key, int defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -814,7 +811,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return a long value */ - long getLong(String key) { + public long getLong(String key) { unparcel(); return getLong(key, 0L); } @@ -827,7 +824,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a long value */ - long getLong(String key, long defaultValue) { + public long getLong(String key, long defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -882,7 +879,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String * @return a double value */ - double getDouble(String key) { + public double getDouble(String key) { unparcel(); return getDouble(key, 0.0); } @@ -895,7 +892,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param defaultValue Value to return if key does not exist * @return a double value */ - double getDouble(String key, double defaultValue) { + public double getDouble(String key, double defaultValue) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -917,7 +914,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String value, or null */ - String getString(String key) { + public String getString(String key) { unparcel(); final Object o = mMap.get(key); try { @@ -937,7 +934,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @return the String value associated with the given key, or defaultValue * if no valid String object is currently mapped to that key. */ - String getString(String key, String defaultValue) { + public String getString(String key, String defaultValue) { final String s = getString(key); return (s == null) ? defaultValue : s; } @@ -981,28 +978,6 @@ abstract class CommonBundle implements Parcelable, Cloneable { * value is explicitly associated with the key. * * @param key a String, or null - * @return a Bundle value, or null - */ - PersistableBundle getPersistableBundle(String key) { - unparcel(); - Object o = mMap.get(key); - if (o == null) { - return null; - } - try { - return (PersistableBundle) o; - } catch (ClassCastException e) { - typeWarning(key, o, "Bundle", e); - return null; - } - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a Serializable value, or null */ Serializable getSerializable(String key) { @@ -1181,7 +1156,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return an int[] value, or null */ - int[] getIntArray(String key) { + public int[] getIntArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1203,7 +1178,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a long[] value, or null */ - long[] getLongArray(String key) { + public long[] getLongArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1247,7 +1222,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a double[] value, or null */ - double[] getDoubleArray(String key) { + public double[] getDoubleArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { @@ -1269,7 +1244,7 @@ abstract class CommonBundle implements Parcelable, Cloneable { * @param key a String, or null * @return a String[] value, or null */ - String[] getStringArray(String key) { + public String[] getStringArray(String key) { unparcel(); Object o = mMap.get(key); if (o == null) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index af45fa0..e627d49 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -931,6 +931,14 @@ public abstract class BatteryStats implements Parcelable { } } + /** + * Don't allow any more batching in to the current history event. This + * is called when printing partial histories, so to ensure that the next + * history event will go in to a new batch after what was printed in the + * last partial history. + */ + public abstract void commitCurrentHistoryBatchLocked(); + public abstract int getHistoryTotalSize(); public abstract int getHistoryUsedSize(); @@ -2443,8 +2451,10 @@ public abstract class BatteryStats implements Parcelable { pw.print(prefix); pw.print(" Capacity: "); printmAh(pw, helper.getPowerProfile().getBatteryCapacity()); pw.print(", Computed drain: "); printmAh(pw, helper.getComputedPower()); - pw.print(", Min drain: "); printmAh(pw, helper.getMinDrainedPower()); - pw.print(", Max drain: "); printmAh(pw, helper.getMaxDrainedPower()); + pw.print(", actual drain: "); printmAh(pw, helper.getMinDrainedPower()); + if (helper.getMinDrainedPower() != helper.getMaxDrainedPower()) { + pw.print("-"); printmAh(pw, helper.getMaxDrainedPower()); + } pw.println(); for (int i=0; i<sippers.size(); i++) { BatterySipper bs = sippers.get(i); @@ -3301,7 +3311,8 @@ public abstract class BatteryStats implements Parcelable { if (rec.time >= histStart) { if (histStart >= 0 && !printed) { if (rec.cmd == HistoryItem.CMD_CURRENT_TIME - || rec.cmd == HistoryItem.CMD_RESET) { + || rec.cmd == HistoryItem.CMD_RESET + || rec.cmd == HistoryItem.CMD_START) { printed = true; hprinter.printNextItem(pw, rec, baseTime, checkin, (flags&DUMP_VERBOSE) != 0); @@ -3351,7 +3362,10 @@ public abstract class BatteryStats implements Parcelable { } hprinter.printNextItem(pw, rec, baseTime, checkin, (flags&DUMP_VERBOSE) != 0); - } else if (rec.eventCode != HistoryItem.EVENT_NONE) { + } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) { + // This is an attempt to aggregate the previous state and generate + // fake events to reflect that state at the point where we start + // printing real events. It doesn't really work right, so is turned off. if (tracker == null) { tracker = new HistoryEventTracker(); } @@ -3360,6 +3374,7 @@ public abstract class BatteryStats implements Parcelable { } } if (histStart >= 0) { + commitCurrentHistoryBatchLocked(); pw.print(checkin ? "NEXT: " : " NEXT: "); pw.println(lastTime+1); } } diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index c85e418..e42c3fe 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -28,14 +28,14 @@ import java.util.Set; * A mapping from String values to various Parcelable types. * */ -public final class Bundle extends CommonBundle { +public final class Bundle extends BaseBundle implements Cloneable, Parcelable { public static final Bundle EMPTY; static final Parcel EMPTY_PARCEL; static { EMPTY = new Bundle(); EMPTY.mMap = ArrayMap.EMPTY; - EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; + EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL; } private boolean mHasFds = false; @@ -125,14 +125,6 @@ public final class Bundle extends CommonBundle { } /** - * @hide - */ - @Override - public String getPairValue() { - return super.getPairValue(); - } - - /** * Changes the ClassLoader this Bundle uses when instantiating objects. * * @param loader An explicit ClassLoader to use when instantiating objects @@ -168,32 +160,6 @@ public final class Bundle extends CommonBundle { } /** - * @hide - */ - @Override - public boolean isParcelled() { - return super.isParcelled(); - } - - /** - * Returns the number of mappings contained in this Bundle. - * - * @return the number of mappings as an int. - */ - @Override - public int size() { - return super.size(); - } - - /** - * Returns true if the mapping of this Bundle is empty, false otherwise. - */ - @Override - public boolean isEmpty() { - return super.isEmpty(); - } - - /** * Removes all elements from the mapping of this Bundle. */ @Override @@ -205,39 +171,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns true if the given key is contained in the mapping - * of this Bundle. - * - * @param key a String key - * @return true if the key is part of the mapping, false otherwise - */ - @Override - public boolean containsKey(String key) { - return super.containsKey(key); - } - - /** - * Returns the entry with the given key as an object. - * - * @param key a String key - * @return an Object, or null - */ - @Override - public Object get(String key) { - return super.get(key); - } - - /** - * Removes any entry with the given key from the mapping of this Bundle. - * - * @param key a String key - */ - @Override - public void remove(String key) { - super.remove(key); - } - - /** * Inserts all mappings from the given Bundle into this Bundle. * * @param bundle a Bundle @@ -253,25 +186,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts all mappings from the given PersistableBundle into this Bundle. - * - * @param bundle a PersistableBundle - */ - public void putAll(PersistableBundle bundle) { - super.putAll(bundle); - } - - /** - * Returns a Set containing the Strings used as keys in this Bundle. - * - * @return a Set of String keys - */ - @Override - public Set<String> keySet() { - return super.keySet(); - } - - /** * Reports whether the bundle contains any parcelled file descriptors. */ public boolean hasFileDescriptors() { @@ -384,30 +298,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts an int value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value an int, or null - */ - @Override - public void putInt(String key, int value) { - super.putInt(key, value); - } - - /** - * Inserts a long value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a long - */ - @Override - public void putLong(String key, long value) { - super.putLong(key, value); - } - - /** * Inserts a float value into the mapping of this Bundle, replacing * any existing value for the given key. * @@ -420,30 +310,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a double value into the mapping of this Bundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a double - */ - @Override - public void putDouble(String key, double value) { - super.putDouble(key, value); - } - - /** - * Inserts a String value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String, or null - */ - @Override - public void putString(String key, String value) { - super.putString(key, value); - } - - /** * Inserts a CharSequence value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -616,30 +482,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts an int array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value an int array object, or null - */ - @Override - public void putIntArray(String key, int[] value) { - super.putIntArray(key, value); - } - - /** - * Inserts a long array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a long array object, or null - */ - @Override - public void putLongArray(String key, long[] value) { - super.putLongArray(key, value); - } - - /** * Inserts a float array value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -652,30 +494,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a double array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a double array object, or null - */ - @Override - public void putDoubleArray(String key, double[] value) { - super.putDoubleArray(key, value); - } - - /** - * Inserts a String array value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String array object, or null - */ - @Override - public void putStringArray(String key, String[] value) { - super.putStringArray(key, value); - } - - /** * Inserts a CharSequence array value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -700,17 +518,6 @@ public final class Bundle extends CommonBundle { } /** - * Inserts a PersistableBundle value into the mapping of this Bundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a Bundle object, or null - */ - public void putPersistableBundle(String key, PersistableBundle value) { - super.putPersistableBundle(key, value); - } - - /** * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -846,56 +653,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns the value associated with the given key, or 0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return an int value - */ - @Override - public int getInt(String key) { - return super.getInt(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return an int value - */ - @Override - public int getInt(String key, int defaultValue) { - return super.getInt(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0L if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a long value - */ - @Override - public long getLong(String key) { - return super.getLong(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a long value - */ - @Override - public long getLong(String key, long defaultValue) { - return super.getLong(key, defaultValue); - } - - /** * Returns the value associated with the given key, or 0.0f if * no mapping of the desired type exists for the given key. * @@ -921,58 +678,6 @@ public final class Bundle extends CommonBundle { } /** - * Returns the value associated with the given key, or 0.0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a double value - */ - @Override - public double getDouble(String key) { - return super.getDouble(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a double value - */ - @Override - public double getDouble(String key, double defaultValue) { - return super.getDouble(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String value, or null - */ - @Override - public String getString(String key) { - return super.getString(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String, or null - * @param defaultValue Value to return if key does not exist - * @return the String value associated with the given key, or defaultValue - * if no valid String object is currently mapped to that key. - */ - @Override - public String getString(String key, String defaultValue) { - return super.getString(key, defaultValue); - } - - /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. @@ -1027,18 +732,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return a PersistableBundle value, or null - */ - public PersistableBundle getPersistableBundle(String key) { - return super.getPersistableBundle(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a Parcelable value, or null */ public <T extends Parcelable> T getParcelable(String key) { @@ -1232,32 +925,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return an int[] value, or null - */ - @Override - public int[] getIntArray(String key) { - return super.getIntArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a long[] value, or null - */ - @Override - public long[] getLongArray(String key) { - return super.getLongArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a float[] value, or null */ @Override @@ -1271,32 +938,6 @@ public final class Bundle extends CommonBundle { * value is explicitly associated with the key. * * @param key a String, or null - * @return a double[] value, or null - */ - @Override - public double[] getDoubleArray(String key) { - return super.getDoubleArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String[] value, or null - */ - @Override - public String[] getStringArray(String key) { - return super.getStringArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null * @return a CharSequence[] value, or null */ @Override diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e98a26b..e84b695 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -191,6 +191,10 @@ public class Environment { return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName); } + public File[] buildExternalStorageAppMediaDirsForVold(String packageName) { + return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName); + } + public File[] buildExternalStorageAppObbDirs(String packageName) { return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName); } diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index c2cd3be..c01f688 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -17,7 +17,14 @@ package android.os; import android.util.ArrayMap; - +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; import java.util.Set; /** @@ -25,14 +32,16 @@ import java.util.Set; * restored. * */ -public final class PersistableBundle extends CommonBundle { +public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, + XmlUtils.WriteMapCallback { + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; public static final PersistableBundle EMPTY; static final Parcel EMPTY_PARCEL; static { EMPTY = new PersistableBundle(); EMPTY.mMap = ArrayMap.EMPTY; - EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL; + EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL; } /** @@ -43,31 +52,6 @@ public final class PersistableBundle extends CommonBundle { } /** - * Constructs a PersistableBundle whose data is stored as a Parcel. The data - * will be unparcelled on first contact, using the assigned ClassLoader. - * - * @param parcelledData a Parcel containing a PersistableBundle - */ - PersistableBundle(Parcel parcelledData) { - super(parcelledData); - } - - /* package */ PersistableBundle(Parcel parcelledData, int length) { - super(parcelledData, length); - } - - /** - * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for - * instantiating Parcelable and Serializable objects. - * - * @param loader An explicit ClassLoader to use when instantiating objects - * inside of the PersistableBundle. - */ - public PersistableBundle(ClassLoader loader) { - super(loader); - } - - /** * Constructs a new, empty PersistableBundle sized to hold the given number of * elements. The PersistableBundle will grow as needed. * @@ -88,41 +72,50 @@ public final class PersistableBundle extends CommonBundle { } /** - * Make a PersistableBundle for a single key/value pair. + * Constructs a PersistableBundle containing the mappings passed in. * - * @hide + * @param map a Map containing only those items that can be persisted. + * @throws IllegalArgumentException if any element of #map cannot be persisted. */ - public static PersistableBundle forPair(String key, String value) { - PersistableBundle b = new PersistableBundle(1); - b.putString(key, value); - return b; - } + private PersistableBundle(Map<String, Object> map) { + super(); - /** - * @hide - */ - @Override - public String getPairValue() { - return super.getPairValue(); + // First stuff everything in. + putAll(map); + + // Now verify each item throwing an exception if there is a violation. + Set<String> keys = map.keySet(); + Iterator<String> iterator = keys.iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + Object value = map.get(key); + if (value instanceof Map) { + // Fix up any Maps by replacing them with PersistableBundles. + putPersistableBundle(key, new PersistableBundle((Map<String, Object>) value)); + } else if (!(value instanceof Integer) && !(value instanceof Long) && + !(value instanceof Double) && !(value instanceof String) && + !(value instanceof int[]) && !(value instanceof long[]) && + !(value instanceof double[]) && !(value instanceof String[]) && + !(value instanceof PersistableBundle) && (value != null)) { + throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key + + " value=" + value); + } + } } - /** - * Changes the ClassLoader this PersistableBundle uses when instantiating objects. - * - * @param loader An explicit ClassLoader to use when instantiating objects - * inside of the PersistableBundle. - */ - @Override - public void setClassLoader(ClassLoader loader) { - super.setClassLoader(loader); + /* package */ PersistableBundle(Parcel parcelledData, int length) { + super(parcelledData, length); } /** - * Return the ClassLoader currently associated with this PersistableBundle. + * Make a PersistableBundle for a single key/value pair. + * + * @hide */ - @Override - public ClassLoader getClassLoader() { - return super.getClassLoader(); + public static PersistableBundle forPair(String key, String value) { + PersistableBundle b = new PersistableBundle(1); + b.putString(key, value); + return b; } /** @@ -135,188 +128,6 @@ public final class PersistableBundle extends CommonBundle { } /** - * @hide - */ - @Override - public boolean isParcelled() { - return super.isParcelled(); - } - - /** - * Returns the number of mappings contained in this PersistableBundle. - * - * @return the number of mappings as an int. - */ - @Override - public int size() { - return super.size(); - } - - /** - * Returns true if the mapping of this PersistableBundle is empty, false otherwise. - */ - @Override - public boolean isEmpty() { - return super.isEmpty(); - } - - /** - * Removes all elements from the mapping of this PersistableBundle. - */ - @Override - public void clear() { - super.clear(); - } - - /** - * Returns true if the given key is contained in the mapping - * of this PersistableBundle. - * - * @param key a String key - * @return true if the key is part of the mapping, false otherwise - */ - @Override - public boolean containsKey(String key) { - return super.containsKey(key); - } - - /** - * Returns the entry with the given key as an object. - * - * @param key a String key - * @return an Object, or null - */ - @Override - public Object get(String key) { - return super.get(key); - } - - /** - * Removes any entry with the given key from the mapping of this PersistableBundle. - * - * @param key a String key - */ - @Override - public void remove(String key) { - super.remove(key); - } - - /** - * Inserts all mappings from the given PersistableBundle into this Bundle. - * - * @param bundle a PersistableBundle - */ - public void putAll(PersistableBundle bundle) { - super.putAll(bundle); - } - - /** - * Returns a Set containing the Strings used as keys in this PersistableBundle. - * - * @return a Set of String keys - */ - @Override - public Set<String> keySet() { - return super.keySet(); - } - - /** - * Inserts an int value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value an int, or null - */ - @Override - public void putInt(String key, int value) { - super.putInt(key, value); - } - - /** - * Inserts a long value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a long - */ - @Override - public void putLong(String key, long value) { - super.putLong(key, value); - } - - /** - * Inserts a double value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. - * - * @param key a String, or null - * @param value a double - */ - @Override - public void putDouble(String key, double value) { - super.putDouble(key, value); - } - - /** - * Inserts a String value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String, or null - */ - @Override - public void putString(String key, String value) { - super.putString(key, value); - } - - /** - * Inserts an int array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value an int array object, or null - */ - @Override - public void putIntArray(String key, int[] value) { - super.putIntArray(key, value); - } - - /** - * Inserts a long array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a long array object, or null - */ - @Override - public void putLongArray(String key, long[] value) { - super.putLongArray(key, value); - } - - /** - * Inserts a double array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a double array object, or null - */ - @Override - public void putDoubleArray(String key, double[] value) { - super.putDoubleArray(key, value); - } - - /** - * Inserts a String array value into the mapping of this PersistableBundle, replacing - * any existing value for the given key. Either key or value may be null. - * - * @param key a String, or null - * @param value a String array object, or null - */ - @Override - public void putStringArray(String key, String[] value) { - super.putStringArray(key, value); - } - - /** * Inserts a PersistableBundle value into the mapping of this Bundle, replacing * any existing value for the given key. Either key or value may be null. * @@ -324,109 +135,8 @@ public final class PersistableBundle extends CommonBundle { * @param value a Bundle object, or null */ public void putPersistableBundle(String key, PersistableBundle value) { - super.putPersistableBundle(key, value); - } - - /** - * Returns the value associated with the given key, or 0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return an int value - */ - @Override - public int getInt(String key) { - return super.getInt(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return an int value - */ - @Override - public int getInt(String key, int defaultValue) { - return super.getInt(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0L if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a long value - */ - @Override - public long getLong(String key) { - return super.getLong(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a long value - */ - @Override - public long getLong(String key, long defaultValue) { - return super.getLong(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or 0.0 if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @return a double value - */ - @Override - public double getDouble(String key) { - return super.getDouble(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String - * @param defaultValue Value to return if key does not exist - * @return a double value - */ - @Override - public double getDouble(String key, double defaultValue) { - return super.getDouble(key, defaultValue); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String value, or null - */ - @Override - public String getString(String key) { - return super.getString(key); - } - - /** - * Returns the value associated with the given key, or defaultValue if - * no mapping of the desired type exists for the given key. - * - * @param key a String, or null - * @param defaultValue Value to return if key does not exist - * @return the String value associated with the given key, or defaultValue - * if no valid String object is currently mapped to that key. - */ - @Override - public String getString(String key, String defaultValue) { - return super.getString(key, defaultValue); + unparcel(); + mMap.put(key, value); } /** @@ -437,61 +147,18 @@ public final class PersistableBundle extends CommonBundle { * @param key a String, or null * @return a Bundle value, or null */ - @Override public PersistableBundle getPersistableBundle(String key) { - return super.getPersistableBundle(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return an int[] value, or null - */ - @Override - public int[] getIntArray(String key) { - return super.getIntArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a long[] value, or null - */ - @Override - public long[] getLongArray(String key) { - return super.getLongArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a double[] value, or null - */ - @Override - public double[] getDoubleArray(String key) { - return super.getDoubleArray(key); - } - - /** - * Returns the value associated with the given key, or null if - * no mapping of the desired type exists for the given key or a null - * value is explicitly associated with the key. - * - * @param key a String, or null - * @return a String[] value, or null - */ - @Override - public String[] getStringArray(String key) { - return super.getStringArray(key); + unparcel(); + Object o = mMap.get(key); + if (o == null) { + return null; + } + try { + return (PersistableBundle) o; + } catch (ClassCastException e) { + typeWarning(key, o, "Bundle", e); + return null; + } } public static final Parcelable.Creator<PersistableBundle> CREATOR = @@ -507,6 +174,38 @@ public final class PersistableBundle extends CommonBundle { } }; + /** @hide */ + @Override + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException { + if (v instanceof PersistableBundle) { + out.startTag(null, TAG_PERSISTABLEMAP); + out.attribute(null, "name", name); + ((PersistableBundle) v).saveToXml(out); + out.endTag(null, TAG_PERSISTABLEMAP); + } else { + throw new XmlPullParserException("Unknown Object o=" + v); + } + } + + /** @hide */ + public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + unparcel(); + XmlUtils.writeMapXml(mMap, out, this); + } + + /** @hide */ + static class MyReadMapCallback implements XmlUtils.ReadMapCallback { + @Override + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException { + if (TAG_PERSISTABLEMAP.equals(tag)) { + return restoreFromXml(in); + } + throw new XmlPullParserException("Unknown tag=" + tag); + } + } + /** * Report the nature of this Parcelable's contents */ @@ -524,19 +223,27 @@ public final class PersistableBundle extends CommonBundle { public void writeToParcel(Parcel parcel, int flags) { final boolean oldAllowFds = parcel.pushAllowFds(false); try { - super.writeToParcelInner(parcel, flags); + writeToParcelInner(parcel, flags); } finally { parcel.restoreAllowFds(oldAllowFds); } } - /** - * Reads the Parcel contents into this PersistableBundle, typically in order for - * it to be passed through an IBinder connection. - * @param parcel The parcel to overwrite this bundle from. - */ - public void readFromParcel(Parcel parcel) { - super.readFromParcelInner(parcel); + /** @hide */ + public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException, + XmlPullParserException { + final int outerDepth = in.getDepth(); + final String startTag = in.getName(); + final String[] tagName = new String[1]; + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && + (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + return new PersistableBundle((Map<String, Object>) + XmlUtils.readThisMapXml(in, startTag, tagName, new MyReadMapCallback())); + } + } + return EMPTY; } @Override diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 57ed979..474192f 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -70,6 +70,8 @@ public final class Trace { public static final long TRACE_TAG_DALVIK = 1L << 14; /** @hide */ public static final long TRACE_TAG_RS = 1L << 15; + /** @hide */ + public static final long TRACE_TAG_BIONIC = 1L << 16; private static final long TRACE_TAG_NOT_READY = 1L << 63; private static final int MAX_SECTION_NAME_LEN = 127; diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4963991..68b91cb 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -58,24 +58,6 @@ import java.util.concurrent.atomic.AtomicInteger; * argument of {@link android.content.Context#STORAGE_SERVICE}. */ public class StorageManager { - - /// Consts to match the password types in cryptfs.h - /** Master key is encrypted with a password. - */ - public static final int CRYPT_TYPE_PASSWORD = 0; - - /** Master key is encrypted with the default password. - */ - public static final int CRYPT_TYPE_DEFAULT = 1; - - /** Master key is encrypted with a pattern. - */ - public static final int CRYPT_TYPE_PATTERN = 2; - - /** Master key is encrypted with a pin. - */ - public static final int CRYPT_TYPE_PIN = 3; - private static final String TAG = "StorageManager"; private final ContentResolver mResolver; @@ -663,4 +645,14 @@ public class StorageManager { return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES, DEFAULT_FULL_THRESHOLD_BYTES); } + + /// Consts to match the password types in cryptfs.h + /** @hide */ + public static final int CRYPT_TYPE_PASSWORD = 0; + /** @hide */ + public static final int CRYPT_TYPE_DEFAULT = 1; + /** @hide */ + public static final int CRYPT_TYPE_PATTERN = 2; + /** @hide */ + public static final int CRYPT_TYPE_PIN = 3; } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index d2d6ade..d66fc0f 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -16,7 +16,10 @@ package android.preference; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.media.AudioManager; import android.media.Ringtone; @@ -37,20 +40,25 @@ import android.widget.SeekBar.OnSeekBarChangeListener; * @hide */ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback { + private static final String TAG = "SeekBarVolumizer"; public interface Callback { void onSampleStarting(SeekBarVolumizer sbv); } - private Context mContext; - private Handler mHandler; + private final Context mContext; + private final Handler mHandler; + private final H mUiHandler = new H(); private final Callback mCallback; + private final Uri mDefaultUri; + private final AudioManager mAudioManager; + private final int mStreamType; + private final int mMaxStreamVolume; + private final Receiver mReceiver = new Receiver(); + private final Observer mVolumeObserver; - private AudioManager mAudioManager; - private int mStreamType; private int mOriginalStreamVolume; private Ringtone mRingtone; - private int mLastProgress = -1; private SeekBar mSeekBar; private int mVolumeBeforeMute = -1; @@ -58,44 +66,25 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private static final int MSG_SET_STREAM_VOLUME = 0; private static final int MSG_START_SAMPLE = 1; private static final int MSG_STOP_SAMPLE = 2; + private static final int MSG_INIT_SAMPLE = 3; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; - private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if (mSeekBar != null && mAudioManager != null) { - int volume = mAudioManager.getStreamVolume(mStreamType); - mSeekBar.setProgress(volume); - } - } - }; - - public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri, + public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) { mContext = context; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mStreamType = streamType; - mSeekBar = seekBar; - - HandlerThread thread = new HandlerThread(VolumePreference.TAG + ".CallbackHandler"); + mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType); + HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); thread.start(); mHandler = new Handler(thread.getLooper(), this); mCallback = callback; - - initSeekBar(seekBar, defaultUri); - } - - private void initSeekBar(SeekBar seekBar, Uri defaultUri) { - seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType)); mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); - seekBar.setProgress(mOriginalStreamVolume); - seekBar.setOnSeekBarChangeListener(this); - + mVolumeObserver = new Observer(mHandler); mContext.getContentResolver().registerContentObserver( System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver); - + mReceiver.setListening(true); if (defaultUri == null) { if (mStreamType == AudioManager.STREAM_RING) { defaultUri = Settings.System.DEFAULT_RINGTONE_URI; @@ -105,12 +94,19 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI; } } + mDefaultUri = defaultUri; + mHandler.sendEmptyMessage(MSG_INIT_SAMPLE); + } - mRingtone = RingtoneManager.getRingtone(mContext, defaultUri); - - if (mRingtone != null) { - mRingtone.setStreamType(mStreamType); + public void setSeekBar(SeekBar seekBar) { + if (mSeekBar != null) { + mSeekBar.setOnSeekBarChangeListener(null); } + mSeekBar = seekBar; + mSeekBar.setOnSeekBarChangeListener(null); + mSeekBar.setMax(mMaxStreamVolume); + mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume); + mSeekBar.setOnSeekBarChangeListener(this); } @Override @@ -125,12 +121,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba case MSG_STOP_SAMPLE: onStopSample(); break; + case MSG_INIT_SAMPLE: + onInitSample(); + break; default: - Log.e(VolumePreference.TAG, "invalid SeekBarVolumizer message: "+msg.what); + Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); } return true; } + private void onInitSample() { + mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri); + if (mRingtone != null) { + mRingtone.setStreamType(mStreamType); + } + } + private void postStartSample() { mHandler.removeMessages(MSG_START_SAMPLE); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE), @@ -143,7 +149,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mCallback.onSampleStarting(this); } if (mRingtone != null) { - mRingtone.play(); + try { + mRingtone.play(); + } catch (Throwable e) { + Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e); + } } } } @@ -165,6 +175,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mSeekBar.setOnSeekBarChangeListener(null); + mReceiver.setListening(false); + mHandler.getLooper().quitSafely(); } public void revertVolume() { @@ -245,4 +257,62 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postSetVolume(mLastProgress); } } -}
\ No newline at end of file + + private final class H extends Handler { + private static final int UPDATE_SLIDER = 1; + + @Override + public void handleMessage(Message msg) { + if (msg.what == UPDATE_SLIDER) { + if (mSeekBar != null) { + mSeekBar.setProgress(msg.arg1); + mLastProgress = mSeekBar.getProgress(); + } + } + } + + public void postUpdateSlider(int volume) { + obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget(); + } + } + + private final class Observer extends ContentObserver { + public Observer(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mSeekBar != null && mAudioManager != null) { + final int volume = mAudioManager.getStreamVolume(mStreamType); + mUiHandler.postUpdateSlider(volume); + } + } + } + + private final class Receiver extends BroadcastReceiver { + private boolean mListening; + + public void setListening(boolean listening) { + if (mListening == listening) return; + mListening = listening; + if (listening) { + final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); + mContext.registerReceiver(this, filter); + } else { + mContext.unregisterReceiver(this); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return; + final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); + if (mSeekBar != null && streamType == mStreamType && streamValue != -1) { + mUiHandler.postUpdateSlider(streamValue); + } + } + } +} diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index 171e5c3..df9e10e 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -66,7 +66,8 @@ public class VolumePreference extends SeekBarDialogPreference implements super.onBindDialogView(view); final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); - mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType, null, this); + mSeekBarVolumizer = new SeekBarVolumizer(getContext(), mStreamType, null, this); + mSeekBarVolumizer.setSeekBar(seekBar); getPreferenceManager().registerOnActivityStopListener(this); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index b53ea81..6db78f4 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1152,8 +1152,6 @@ public final class ContactsContract { * address book index, which is usually the first letter of the sort key. * When this parameter is supplied, the row counts are returned in the * cursor extras bundle. - * - * @hide */ public final static class ContactCounts { @@ -1163,7 +1161,24 @@ public final class ContactsContract { * first letter of the sort key. This parameter does not affect the main * content of the cursor. * - * @hide + * <p> + * <pre> + * Example: + * Uri uri = Contacts.CONTENT_URI.buildUpon() + * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true") + * .build(); + * Cursor cursor = getContentResolver().query(uri, + * new String[] {Contacts.DISPLAY_NAME}, + * null, null, null); + * Bundle bundle = cursor.getExtras(); + * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) && + * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) { + * String sections[] = + * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); + * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); + * } + * </pre> + * </p> */ public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras"; @@ -1171,8 +1186,6 @@ public final class ContactsContract { * The array of address book index titles, which are returned in the * same order as the data in the cursor. * <p>TYPE: String[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles"; @@ -1180,8 +1193,6 @@ public final class ContactsContract { * The array of group counts for the corresponding group. Contains the same number * of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array. * <p>TYPE: int[]</p> - * - * @hide */ public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts"; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e9ffc52..bec401e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4461,6 +4461,12 @@ public final class Settings { INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF; /** + * Whether the device should wake when the wake gesture sensor detects motion. + * @hide + */ + public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled"; + + /** * The current night mode that has been selected by the user. Owned * and controlled by UiModeManagerService. Constants are as per * UiModeManager. diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java deleted file mode 100644 index e4f93a8..0000000 --- a/core/java/android/provider/TvContract.java +++ /dev/null @@ -1,741 +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.provider; - -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.net.Uri; -import android.tv.TvInputService; - -import java.util.List; - -/** - * <p> - * The contract between the TV provider and applications. Contains definitions for the supported - * URIs and columns. - * </p> - * <h3>Overview</h3> - * <p> - * TvContract defines a basic database of TV content metadata such as channel and program - * information. The information is stored in {@link Channels} and {@link Programs} tables. - * </p> - * <ul> - * <li>A row in the {@link Channels} table represents information about a TV channel. The data - * format can vary greatly from standard to standard or according to service provider, thus - * the columns here are mostly comprised of basic entities that are usually seen to users - * regardless of standard such as channel number and name.</li> - * <li>A row in the {@link Programs} table represents a set of data describing a TV program such - * as program title and start time.</li> - * </ul> - */ -public final class TvContract { - /** The authority for the TV provider. */ - public static final String AUTHORITY = "com.android.tv"; - - private static final String PATH_CHANNEL = "channel"; - private static final String PATH_PROGRAM = "program"; - private static final String PATH_INPUT = "input"; - - /** - * An optional query, update or delete URI parameter that allows the caller to specify start - * time (in milliseconds since the epoch) to filter programs. - * - * @hide - */ - public static final String PARAM_START_TIME = "start_time"; - - /** - * An optional query, update or delete URI parameter that allows the caller to specify end time - * (in milliseconds since the epoch) to filter programs. - * - * @hide - */ - public static final String PARAM_END_TIME = "end_time"; - - /** - * A query, update or delete URI parameter that allows the caller to operate on all or - * browsable-only channels. If set to "true", the rows that contain non-browsable channels are - * not affected. - * - * @hide - */ - public static final String PARAM_BROWSABLE_ONLY = "browable_only"; - - /** - * Builds a URI that points to a specific channel. - * - * @param channelId The ID of the channel to point to. - */ - public static final Uri buildChannelUri(long channelId) { - return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId); - } - - /** - * Builds a URI that points to all browsable channels from a given TV input. - * - * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements - * the given TV input. - */ - public static final Uri buildChannelsUriForInput(ComponentName name) { - return buildChannelsUriForInput(name, true); - } - - /** - * Builds a URI that points to all or browsable-only channels from a given TV input. - * - * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements - * the given TV input. - * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set - * to {@code false} the URI points to all channels regardless of whether they are - * browsable or not. - */ - public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) - .appendPath(PATH_INPUT).appendPath(name.getPackageName()) - .appendPath(name.getClassName()).appendPath(PATH_CHANNEL) - .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build(); - } - - /** - * Builds a URI that points to a specific program. - * - * @param programId The ID of the program to point to. - */ - public static final Uri buildProgramUri(long programId) { - return ContentUris.withAppendedId(Programs.CONTENT_URI, programId); - } - - /** - * Builds a URI that points to all programs on a given channel. - * - * @param channelUri The URI of the channel to return programs for. - */ - public static final Uri buildProgramsUriForChannel(Uri channelUri) { - if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) { - throw new IllegalArgumentException("Not a channel: " + channelUri); - } - String channelId = String.valueOf(ContentUris.parseId(channelUri)); - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) - .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build(); - } - - /** - * Builds a URI that points to programs on a specific channel whose schedules overlap with the - * given time frame. - * - * @param channelUri The URI of the channel to return programs for. - * @param startTime The start time used to filter programs. The returned programs should have - * {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time. - * @param endTime The end time used to filter programs. The returned programs should have - * {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time. - */ - public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime, - long endTime) { - Uri uri = buildProgramsUriForChannel(channelUri); - return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime)) - .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build(); - } - - /** - * Builds a URI that points to a specific program the user watched. - * - * @param watchedProgramId The ID of the watched program to point to. - * @hide - */ - public static final Uri buildWatchedProgramUri(long watchedProgramId) { - return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId); - } - - /** - * Extracts the {@link Channels#COLUMN_PACKAGE_NAME} from a given URI. - * - * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or - * {@link #buildChannelsUriForInput(ComponentName, boolean)}. - * @hide - */ - public static final String getPackageName(Uri channelsUri) { - final List<String> paths = channelsUri.getPathSegments(); - if (paths.size() < 4) { - throw new IllegalArgumentException("Not channels: " + channelsUri); - } - if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { - throw new IllegalArgumentException("Not channels: " + channelsUri); - } - return paths.get(1); - } - - /** - * Extracts the {@link Channels#COLUMN_SERVICE_NAME} from a given URI. - * - * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or - * {@link #buildChannelsUriForInput(ComponentName, boolean)}. - * @hide - */ - public static final String getServiceName(Uri channelsUri) { - final List<String> paths = channelsUri.getPathSegments(); - if (paths.size() < 4) { - throw new IllegalArgumentException("Not channels: " + channelsUri); - } - if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { - throw new IllegalArgumentException("Not channels: " + channelsUri); - } - return paths.get(2); - } - - /** - * Extracts the {@link Channels#_ID} from a given URI. - * - * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or - * {@link #buildProgramsUriForChannel(Uri, long, long)}. - * @hide - */ - public static final String getChannelId(Uri programsUri) { - final List<String> paths = programsUri.getPathSegments(); - if (paths.size() < 3) { - throw new IllegalArgumentException("Not programs: " + programsUri); - } - if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) { - throw new IllegalArgumentException("Not programs: " + programsUri); - } - return paths.get(1); - } - - - private TvContract() {} - - /** - * Common base for the tables of TV channels/programs. - */ - public interface BaseTvColumns extends BaseColumns { - /** - * The name of the package that owns a row in each table. - * <p> - * The TV provider fills it in with the name of the package that provides the initial data - * of that row. If the package is later uninstalled, the rows it owns are automatically - * removed from the tables. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_PACKAGE_NAME = "package_name"; - } - - /** Column definitions for the TV channels table. */ - public static final class Channels implements BaseTvColumns { - - /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" - + PATH_CHANNEL); - - /** The MIME type of a directory of TV channels. */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.com.android.tv.channels"; - - /** The MIME type of a single TV channel. */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.com.android.tv.channels"; - - /** A generic channel type. */ - public static final int TYPE_OTHER = 0x0; - - /** The special channel type used for pass-through inputs such as HDMI. */ - public static final int TYPE_PASSTHROUGH = 0x00010000; - - /** The channel type for DVB-T (terrestrial). */ - public static final int TYPE_DVB_T = 0x00020000; - - /** The channel type for DVB-T2 (terrestrial). */ - public static final int TYPE_DVB_T2 = 0x00020001; - - /** The channel type for DVB-S (satellite). */ - public static final int TYPE_DVB_S = 0x00020100; - - /** The channel type for DVB-S2 (satellite). */ - public static final int TYPE_DVB_S2 = 0x00020101; - - /** The channel type for DVB-C (cable). */ - public static final int TYPE_DVB_C = 0x00020200; - - /** The channel type for DVB-C2 (cable). */ - public static final int TYPE_DVB_C2 = 0x00020201; - - /** The channel type for DVB-H (handheld). */ - public static final int TYPE_DVB_H = 0x00020300; - - /** The channel type for DVB-SH (satellite). */ - public static final int TYPE_DVB_SH = 0x00020400; - - /** The channel type for ATSC (terrestrial). */ - public static final int TYPE_ATSC_T = 0x00030000; - - /** The channel type for ATSC (cable). */ - public static final int TYPE_ATSC_C = 0x00030200; - - /** The channel type for ATSC-M/H (mobile/handheld). */ - public static final int TYPE_ATSC_M_H = 0x00030200; - - /** The channel type for ISDB-T (terrestrial). */ - public static final int TYPE_ISDB_T = 0x00040000; - - /** The channel type for ISDB-Tb (Brazil). */ - public static final int TYPE_ISDB_TB = 0x00040100; - - /** The channel type for ISDB-S (satellite). */ - public static final int TYPE_ISDB_S = 0x00040200; - - /** The channel type for ISDB-C (cable). */ - public static final int TYPE_ISDB_C = 0x00040300; - - /** The channel type for 1seg (handheld). */ - public static final int TYPE_1SEG = 0x00040400; - - /** The channel type for DTMB (terrestrial). */ - public static final int TYPE_DTMB = 0x00050000; - - /** The channel type for CMMB (handheld). */ - public static final int TYPE_CMMB = 0x00050100; - - /** The channel type for T-DMB (terrestrial). */ - public static final int TYPE_T_DMB = 0x00060000; - - /** The channel type for S-DMB (satellite). */ - public static final int TYPE_S_DMB = 0x00060100; - - /** A generic service type. */ - public static final int SERVICE_TYPE_OTHER = 0x0; - - /** The service type for regular TV channels. */ - public static final int SERVICE_TYPE_TV = 0x1; - - /** The service type for radio channels. */ - public static final int SERVICE_TYPE_RADIO = 0x2; - - /** - * The name of the {@link TvInputService} subclass that provides this TV channel. This - * should be a fully qualified class name (such as, "com.example.project.TvInputService"). - * <p> - * This is a required field. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_SERVICE_NAME = "service_name"; - - /** - * The predefined type of this TV channel. - * <p> - * This is primarily used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the - * current channel conforms to, with an exception being {@link #TYPE_PASSTHROUGH}, which is - * a special channel type used only by pass-through inputs such as HDMI. The value should - * match to one of the followings: {@link #TYPE_OTHER}, {@link #TYPE_PASSTHROUGH}, - * {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S}, {@link #TYPE_DVB_S2}, - * {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H}, {@link #TYPE_DVB_SH}, - * {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C}, {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T}, - * {@link #TYPE_ISDB_TB}, {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C} {@link #TYPE_1SEG}, - * {@link #TYPE_DTMB}, {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB} - * </p><p> - * This is a required field. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_TYPE = "type"; - - /** - * The predefined service type of this TV channel. - * <p> - * This is primarily used to indicate whether the current channel is a regular TV channel or - * a radio-like channel. Use the same coding for {@code service_type} in the underlying - * broadcast standard if it is defined there (e.g. ATSC A/53, ETSI EN 300 468 and ARIB - * STD-B10). Otherwise use one of the followings: {@link #SERVICE_TYPE_OTHER}, - * {@link #SERVICE_TYPE_TV}, {@link #SERVICE_TYPE_RADIO} - * </p><p> - * This is a required field. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_SERVICE_TYPE = "service_type"; - - /** - * The original network ID of this TV channel. - * <p> - * This is used to identify the originating delivery system, if applicable. Use the same - * coding for {@code origianal_network_id} in the underlying broadcast standard if it is - * defined there (e.g. ETSI EN 300 468/TR 101 211 and ARIB STD-B10). If channels cannot be - * globally identified by 2-tuple {{@link #COLUMN_TRANSPORT_STREAM_ID}, - * {@link #COLUMN_SERVICE_ID}}, one must carefully assign a value to this field to form a - * unique 3-tuple identification {{@link #COLUMN_ORIGINAL_NETWORK_ID}, - * {@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}} for its channels. - * </p><p> - * This is a required field if the channel cannot be uniquely identified by a 2-tuple - * {{@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}}. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id"; - - /** - * The transport stream ID of this channel. - * <p> - * This is used to identify the Transport Stream that contains the current channel from any - * other multiplex within a network, if applicable. Use the same coding for - * {@code transport_stream_id} defined in ISO/IEC 13818-1 if the channel is transmitted via - * the MPEG Transport Stream as is the case for many digital broadcast standards. - * </p><p> - * This is a required field if the current channel is transmitted via the MPEG Transport - * Stream. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id"; - - /** - * The service ID of this channel. - * <p> - * This is used to identify the current service (roughly equivalent to channel) from any - * other service within the Transport Stream, if applicable. Use the same coding for - * {@code service_id} in the underlying broadcast standard if it is defined there (e.g. ETSI - * EN 300 468 and ARIB STD-B10) or {@code program_number} (which usually has the same value - * as {@code service_id}) in ISO/IEC 13818-1 if the channel is transmitted via the MPEG - * Transport Stream. - * </p><p> - * This is a required field if the current channel is transmitted via the MPEG Transport - * Stream. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_SERVICE_ID = "service_id"; - - /** - * The channel number that is displayed to the user. - * <p> - * The format can vary depending on broadcast standard and product specification. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_DISPLAY_NUMBER = "display_number"; - - /** - * The channel name that is displayed to the user. - * <p> - * A call sign is a good candidate to use for this purpose but any name that helps the user - * recognize the current channel will be enough. Can also be empty depending on broadcast - * standard. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_DISPLAY_NAME = "display_name"; - - /** - * The description of this TV channel. - * <p> - * Can be empty initially. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_DESCRIPTION = "description"; - - /** - * The flag indicating whether this TV channel is browsable or not. - * <p> - * A value of 1 indicates the channel is included in the channel list that applications use - * to browse channels, a value of 0 indicates the channel is not included in the list. If - * not specified, this value is set to 1 (browsable) by default. - * </p><p> - * Type: INTEGER (boolean) - * </p> - */ - public static final String COLUMN_BROWSABLE = "browsable"; - - /** - * The flag indicating whether this TV channel is searchable or not. - * <p> - * In some regions, it is not allowed to surface search results for a given channel without - * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates - * the channel is searchable and can be included in search results, a value of 0 indicates - * the channel and its TV programs are hidden from search. If not specified, this value is - * set to 1 (searchable) by default. - * </p> - * <p> - * Type: INTEGER (boolean) - * </p> - */ - public static final String COLUMN_SEARCHABLE = "searchable"; - - /** - * The flag indicating whether this TV channel is locked or not. - * <p> - * This is primarily used for alternative parental control to prevent unauthorized users - * from watching the current channel regardless of the content rating. A value of 1 - * indicates the channel is locked and the user is required to enter passcode to unlock it - * in order to watch the current program from the channel, a value of 0 indicates the - * channel is not locked thus the user is not prompted to enter passcode If not specified, - * this value is set to 0 (not locked) by default. - * </p><p> - * Type: INTEGER (boolean) - * </p> - */ - public static final String COLUMN_LOCKED = "locked"; - - /** - * Generic data used by individual TV input services. - * <p> - * Type: BLOB - * </p> - */ - public static final String COLUMN_DATA = "data"; - - - /** - * The version number of this row entry used by TV input services. - * <p> - * This is best used by sync adapters to identify the rows to update. The number can be - * defined by individual TV input services. One may assign the same value as - * {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are - * coming from a TV broadcast. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_VERSION_NUMBER = "version_number"; - - private Channels() {} - } - - /** Column definitions for the TV programs table. */ - public static final class Programs implements BaseTvColumns { - - /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" - + PATH_PROGRAM); - - /** The MIME type of a directory of TV programs. */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.com.android.tv.programs"; - - /** The MIME type of a single TV program. */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.com.android.tv.programs"; - - /** - * The ID of the TV channel that contains this TV program. - * <p> - * This is a part of the channel URI and matches to {@link BaseColumns#_ID}. - * </p><p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_CHANNEL_ID = "channel_id"; - - /** - * The title of this TV program. - * <p> - * Type: TEXT - * </p> - **/ - public static final String COLUMN_TITLE = "title"; - - /** - * The start time of this TV program, in milliseconds since the epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; - - /** - * The end time of this TV program, in milliseconds since the epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; - - /** - * The comma-separated genre string of this TV program. - * <p> - * Use the same language appeared in the underlying broadcast standard, if applicable. (For - * example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or - * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, use one of the - * following genres: - * <ul> - * <li>Family/Kids</li> - * <li>Sports</li> - * <li>Shopping</li> - * <li>Movies</li> - * <li>Comedy</li> - * <li>Travel</li> - * <li>Drama</li> - * <li>Education</li> - * <li>Animal/Wildlife</li> - * <li>News</li> - * <li>Gaming</li> - * <li>Others</li> - * </ul> - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_GENRE = "genre"; - - /** - * The description of this TV program that is displayed to the user by default. - * <p> - * The maximum length of this field is 256 characters. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_DESCRIPTION = "description"; - - /** - * The detailed, lengthy description of this TV program that is displayed only when the user - * wants to see more information. - * <p> - * TV input services should leave this field empty if they have no additional - * details beyond {@link #COLUMN_DESCRIPTION}. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_LONG_DESCRIPTION = "long_description"; - - /** - * The comma-separated audio languages of this TV program. - * <p> - * This is used to describe available audio languages included in the program. Use - * 3-character language code as specified by ISO 639-2. - * </p><p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_AUDIO_LANGUAGE = "audio_language"; - - /** - * Generic data used by TV input services. - * <p> - * Type: BLOB - * </p> - */ - public static final String COLUMN_DATA = "data"; - - /** - * The version number of this row entry used by TV input services. - * <p> - * This is best used by sync adapters to identify the rows to update. The number can be - * defined by individual TV input services. One may assign the same value as - * {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV - * broadcast. - * </p><p> - * Type: INTEGER - * </p> - */ - public static final String COLUMN_VERSION_NUMBER = "version_number"; - - private Programs() {} - } - - /** - * Column definitions for the TV programs that the user watched. Applications do not have access - * to this table. - * - * @hide - */ - public static final class WatchedPrograms implements BaseColumns { - - /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = - Uri.parse("content://" + AUTHORITY + "/watched_program"); - - /** The MIME type of a directory of watched programs. */ - public static final String CONTENT_TYPE = - "vnd.android.cursor.dir/vnd.com.android.tv.watched_programs"; - - /** The MIME type of a single item in this table. */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.com.android.tv.watched_programs"; - - /** - * The UTC time that the user started watching this TV program, in milliseconds since the - * epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS = - "watch_start_time_utc_millis"; - - /** - * The UTC time that the user stopped watching this TV program, in milliseconds since the - * epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis"; - - /** - * The channel ID that contains this TV program. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_CHANNEL_ID = "channel_id"; - - /** - * The title of this TV program. - * <p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_TITLE = "title"; - - /** - * The start time of this TV program, in milliseconds since the epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; - - /** - * The end time of this TV program, in milliseconds since the epoch. - * <p> - * Type: INTEGER (long) - * </p> - */ - public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; - - /** - * The description of this TV program. - * <p> - * Type: TEXT - * </p> - */ - public static final String COLUMN_DESCRIPTION = "description"; - - private WatchedPrograms() {} - } -} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 846e292..d02fc7b 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -41,12 +41,18 @@ public class ZenModeConfig implements Parcelable { public static final String SLEEP_MODE_NIGHTS = "nights"; public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights"; + public static final int SOURCE_ANYONE = 0; + public static final int SOURCE_CONTACT = 1; + public static final int SOURCE_STAR = 2; + public static final int MAX_SOURCE = SOURCE_STAR; + private static final int XML_VERSION = 1; private static final String ZEN_TAG = "zen"; private static final String ZEN_ATT_VERSION = "version"; private static final String ALLOW_TAG = "allow"; private static final String ALLOW_ATT_CALLS = "calls"; private static final String ALLOW_ATT_MESSAGES = "messages"; + private static final String ALLOW_ATT_FROM = "from"; private static final String SLEEP_TAG = "sleep"; private static final String SLEEP_ATT_MODE = "mode"; @@ -61,6 +67,7 @@ public class ZenModeConfig implements Parcelable { public boolean allowCalls; public boolean allowMessages; + public int allowFrom = SOURCE_ANYONE; public String sleepMode; public int sleepStartHour; @@ -92,6 +99,7 @@ public class ZenModeConfig implements Parcelable { conditionIds = new Uri[len]; source.readTypedArray(conditionIds, Uri.CREATOR); } + allowFrom = source.readInt(); } @Override @@ -120,6 +128,7 @@ public class ZenModeConfig implements Parcelable { } else { dest.writeInt(0); } + dest.writeInt(allowFrom); } @Override @@ -127,6 +136,7 @@ public class ZenModeConfig implements Parcelable { return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') .append("allowCalls=").append(allowCalls) .append(",allowMessages=").append(allowMessages) + .append(",allowFrom=").append(sourceToString(allowFrom)) .append(",sleepMode=").append(sleepMode) .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute) .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute) @@ -137,6 +147,19 @@ public class ZenModeConfig implements Parcelable { .append(']').toString(); } + public static String sourceToString(int source) { + switch (source) { + case SOURCE_ANYONE: + return "anyone"; + case SOURCE_CONTACT: + return "contacts"; + case SOURCE_STAR: + return "stars"; + default: + return "UNKNOWN"; + } + } + @Override public boolean equals(Object o) { if (!(o instanceof ZenModeConfig)) return false; @@ -144,6 +167,7 @@ public class ZenModeConfig implements Parcelable { final ZenModeConfig other = (ZenModeConfig) o; return other.allowCalls == allowCalls && other.allowMessages == allowMessages + && other.allowFrom == allowFrom && Objects.equals(other.sleepMode, sleepMode) && other.sleepStartHour == sleepStartHour && other.sleepStartMinute == sleepStartMinute @@ -155,8 +179,8 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour, - sleepStartMinute, sleepEndHour, sleepEndMinute, + return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode, + sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds)); } @@ -191,6 +215,10 @@ public class ZenModeConfig implements Parcelable { if (ALLOW_TAG.equals(tag)) { rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false); rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false); + rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); + if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { + throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom); + } } else if (SLEEP_TAG.equals(tag)) { final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE); rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode) @@ -224,6 +252,7 @@ public class ZenModeConfig implements Parcelable { out.startTag(null, ALLOW_TAG); out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls)); out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); + out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); out.endTag(null, ALLOW_TAG); out.startTag(null, SLEEP_TAG); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index a83544d..2e9077a 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -20,8 +20,8 @@ import android.app.Dialog; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Rect; import android.graphics.Region; import android.inputmethodservice.SoftInputWindow; import android.os.Binder; @@ -32,6 +32,7 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -46,9 +47,22 @@ import com.android.internal.app.IVoiceInteractorRequest; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.lang.ref.WeakReference; + import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +/** + * An active voice interaction session, providing a facility for the implementation + * to interact with the user in the voice interaction layer. This interface is no shown + * by default, but you can request that it be shown with {@link #showWindow()}, which + * will result in a later call to {@link #onCreateContentView()} in which the UI can be + * built + * + * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish} + * when done. It can also initiate voice interactions with applications by calling + * {@link #startVoiceActivity}</p>. + */ public abstract class VoiceInteractionSession implements KeyEvent.Callback { static final String TAG = "VoiceInteractionSession"; static final boolean DEBUG = true; @@ -79,11 +93,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; + final WeakReference<VoiceInteractionSession> mWeakRef + = new WeakReference<VoiceInteractionSession>(this); + final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() { @Override public IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, Bundle extras) { - Request request = findRequest(callback, true); + IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) { + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION, new Caller(callingPackage, Binder.getCallingUid()), request, prompt, extras)); @@ -91,9 +108,19 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { + Request request = newRequest(callback); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE, + new Caller(callingPackage, Binder.getCallingUid()), request, + message, extras)); + return request.mInterface; + } + + @Override public IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, Bundle extras) { - Request request = findRequest(callback, true); + Request request = newRequest(callback); mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND, new Caller(callingPackage, Binder.getCallingUid()), request, command, extras)); @@ -142,29 +169,60 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() { @Override public void cancel() throws RemoteException { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + VoiceInteractionSession session = mSession.get(); + if (session != null) { + session.mHandlerCaller.sendMessage( + session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this)); + } } }; final IVoiceInteractorCallback mCallback; - final HandlerCaller mHandlerCaller; - Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) { + final WeakReference<VoiceInteractionSession> mSession; + + Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) { mCallback = callback; - mHandlerCaller = handlerCaller; + mSession = session.mWeakRef; + } + + void finishRequest() { + VoiceInteractionSession session = mSession.get(); + if (session == null) { + throw new IllegalStateException("VoiceInteractionSession has been destroyed"); + } + Request req = session.removeRequest(mInterface.asBinder()); + if (req == null) { + throw new IllegalStateException("Request not active: " + this); + } else if (req != this) { + throw new IllegalStateException("Current active request " + req + + " not same as calling request " + this); + } } public void sendConfirmResult(boolean confirmed, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + " confirmed=" + confirmed + " result=" + result); + finishRequest(); mCallback.deliverConfirmationResult(mInterface, confirmed, result); } catch (RemoteException e) { } } + public void sendAbortVoiceResult(Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface + + " result=" + result); + finishRequest(); + mCallback.deliverAbortVoiceResult(mInterface, result); + } catch (RemoteException e) { + } + } + public void sendCommandResult(boolean complete, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + " result=" + result); + finishRequest(); mCallback.deliverCommandResult(mInterface, complete, result); } catch (RemoteException e) { } @@ -173,6 +231,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public void sendCancelResult() { try { if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface); + finishRequest(); mCallback.deliverCancel(mInterface); } catch (RemoteException e) { } @@ -190,9 +249,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } static final int MSG_START_CONFIRMATION = 1; - static final int MSG_START_COMMAND = 2; - static final int MSG_SUPPORTS_COMMANDS = 3; - static final int MSG_CANCEL = 4; + static final int MSG_START_ABORT_VOICE = 2; + static final int MSG_START_COMMAND = 3; + static final int MSG_SUPPORTS_COMMANDS = 4; + static final int MSG_CANCEL = 5; static final int MSG_TASK_STARTED = 100; static final int MSG_TASK_FINISHED = 101; @@ -208,9 +268,16 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface + " prompt=" + args.arg3 + " extras=" + args.arg4); - onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3, + onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, (Bundle)args.arg4); break; + case MSG_START_ABORT_VOICE: + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface + + " message=" + args.arg3 + " extras=" + args.arg4); + onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3, + (Bundle) args.arg4); + break; case MSG_START_COMMAND: args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface @@ -262,14 +329,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { */ public static final class Insets { /** - * This is the top part of the UI that is the main content. It is + * This is the part of the UI that is the main content. It is * used to determine the basic space needed, to resize/pan the * application behind. It is assumed that this inset does not * change very much, since any change will cause a full resize/pan * of the application behind. This value is relative to the top edge * of the input method window. */ - public int contentTopInsets; + public final Rect contentInsets = new Rect(); /** * This is the region of the UI that is touchable. It is used when @@ -311,7 +378,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { new ViewTreeObserver.OnComputeInternalInsetsListener() { public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { onComputeInsets(mTmpInsets); - info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets; + info.contentInsets.set(mTmpInsets.contentInsets); + info.visibleInsets.set(mTmpInsets.contentInsets); info.touchableRegion.set(mTmpInsets.touchableRegion); info.setTouchableInsets(mTmpInsets.touchableInsets); } @@ -327,18 +395,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mCallbacks, true); } - Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) { + Request newRequest(IVoiceInteractorCallback callback) { + synchronized (this) { + Request req = new Request(callback, this); + mActiveRequests.put(req.mInterface.asBinder(), req); + return req; + } + } + + Request removeRequest(IBinder reqInterface) { synchronized (this) { - Request req = mActiveRequests.get(callback.asBinder()); + Request req = mActiveRequests.get(reqInterface); if (req != null) { - if (newRequest) { - throw new IllegalArgumentException("Given request callback " + callback - + " is already active"); - } - return req; + mActiveRequests.remove(req); } - req = new Request(callback, mHandlerCaller); - mActiveRequests.put(callback.asBinder(), req); return req; } } @@ -423,11 +493,34 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mTheme = theme; } + /** + * Ask that a new activity be started for voice interaction. This will create a + * new dedicated task in the activity manager for this voice interaction session; + * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} + * will be set for you to make it a new task. + * + * <p>The newly started activity will be displayed to the user in a special way, as + * a layer under the voice interaction UI.</p> + * + * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor} + * through which it can perform voice interactions through your session. These requests + * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands}, + * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}. + * + * <p>You will receive a call to {@link #onTaskStarted} when the task starts up + * and {@link #onTaskFinished} when the last activity has finished. + * + * @param intent The Intent to start this voice interaction. The given Intent will + * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since + * this is part of a voice interaction. + */ public void startVoiceActivity(Intent intent) { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); } try { + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); int res = mSystemService.startVoiceActivity(mToken, intent, intent.resolveType(mContext.getContentResolver())); Instrumentation.checkStartActivityResult(res, intent); @@ -435,14 +528,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Convenience for inflating views. + */ public LayoutInflater getLayoutInflater() { return mInflater; } + /** + * Retrieve the window being used to show the session's UI. + */ public Dialog getWindow() { return mWindow; } + /** + * Finish the session. + */ public void finish() { if (mToken == null) { throw new IllegalStateException("Can't call before onCreate()"); @@ -454,22 +556,35 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + /** + * Initiatize a new session. + * + * @param args The arguments that were supplied to + * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}. + */ public void onCreate(Bundle args) { mTheme = mTheme != 0 ? mTheme : com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession; mInflater = (LayoutInflater)mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, - mCallbacks, this, mDispatcherState, true); + mCallbacks, this, mDispatcherState, + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true); mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); mWindow.setToken(mToken); } + /** + * Last callback to the session as it is being finished. + */ public void onDestroy() { } + /** + * Hook in which to create the session's UI. + */ public View onCreateContentView() { return null; } @@ -502,6 +617,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { finish(); } + /** + * Sessions automatically watch for requests that all system UI be closed (such as when + * the user presses HOME), which will appear here. The default implementation always + * calls {@link #finish}. + */ public void onCloseSystemDialogs() { finish(); } @@ -517,20 +637,106 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { int[] loc = mTmpLocation; View decor = getWindow().getWindow().getDecorView(); decor.getLocationInWindow(loc); - outInsets.contentTopInsets = loc[1]; + outInsets.contentInsets.top = 0; + outInsets.contentInsets.left = 0; + outInsets.contentInsets.right = 0; + outInsets.contentInsets.bottom = 0; outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME; outInsets.touchableRegion.setEmpty(); } + /** + * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)} + * has actually started. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the now running task. + */ public void onTaskStarted(Intent intent, int taskId) { } + /** + * Called when the last activity of a task initiated by + * {@link #startVoiceActivity(android.content.Intent)} has finished. The default + * implementation calls {@link #finish()} on the assumption that this represents + * the completion of a voice action. You can override the implementation if you would + * like a different behavior. + * + * @param intent The original {@link Intent} supplied to + * {@link #startVoiceActivity(android.content.Intent)}. + * @param taskId Unique ID of the finished task. + */ public void onTaskFinished(Intent intent, int taskId) { finish(); } - public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands); - public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras); + /** + * Request to query for what extended commands the session supports. + * + * @param caller Who is making the request. + * @param commands An array of commands that are being queried. + * @return Return an array of booleans indicating which of each entry in the + * command array is supported. A true entry in the array indicates the command + * is supported; false indicates it is not. The default implementation returns + * an array of all false entries. + */ + public boolean[] onGetSupportedCommands(Caller caller, String[] commands) { + return new boolean[commands.length]; + } + + /** + * Request to confirm with the user before proceeding with an unrecoverable operation, + * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest + * VoiceInteractor.ConfirmationRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param prompt The prompt informing the user of what will happen, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}. + */ + public abstract void onConfirm(Caller caller, Request request, CharSequence prompt, + Bundle extras); + + /** + * Request to abort the voice interaction session because the voice activity can not + * complete its interaction using voice. Corresponds to + * {@link android.app.VoiceInteractor.AbortVoiceRequest + * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty + * confirmation back to allow the activity to exit. + * + * @param caller Who is making the request. + * @param request The active request. + * @param message The message informing the user of the problem, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}. + */ + public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) { + request.sendAbortVoiceResult(null); + } + + /** + * Process an arbitrary extended command from the caller, + * corresponding to a {@link android.app.VoiceInteractor.CommandRequest + * VoiceInteractor.CommandRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param command The command that is being executed, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}. + */ public abstract void onCommand(Caller caller, Request request, String command, Bundle extras); + + /** + * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request} + * that was previously delivered to {@link #onConfirm} or {@link #onCommand}. + * + * @param request The request that is being canceled. + */ public abstract void onCancel(Request request); } diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java new file mode 100644 index 0000000..c886e5d --- /dev/null +++ b/core/java/android/speech/tts/Markup.java @@ -0,0 +1,537 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that provides markup to a synthesis request to control aspects of speech. + * <p> + * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently + * available set of features and should be used to construct instances of the Markup class. + * </p> + * <p> + * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of + * parameters, and a list of children. + * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node + * can be either a part of sentence (often a leaf node), or node altering some property of its + * children (node with children). The top level node has to be of type "utterance" and its children + * are synthesized in order. + * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not + * support Markup at all, it should use the plain text of the top level node. If an engine does not + * recognize or support a node type, it will try to use the plain text of that node if provided. If + * the plain text is null, it will synthesize its children in order. + * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the + * parameters may be for example "month: 7" and "day: 10". + * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a + * measure may have a node of type "decimal" as its child) or to modify some property of its + * children. See "plain text" on how they are processed if the parent of the children is unknown to + * the engine. + * <p> + */ +public final class Markup implements Parcelable { + + private String mType; + private String mPlainText; + + private Bundle mParameters = new Bundle(); + private List<Markup> mNestedMarkups = new ArrayList<Markup>(); + + private static final String TYPE = "type"; + private static final String PLAIN_TEXT = "plain_text"; + private static final String MARKUP = "markup"; + + private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)"; + private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX); + + /** + * Constructs an empty markup. + */ + public Markup() {} + + /** + * Constructs a markup of the given type. + */ + public Markup(String type) { + setType(type); + } + + /** + * Returns the type of this node; can be null. + */ + public String getType() { + return mType; + } + + /** + * Sets the type of this node. can be null. May only contain [0-9a-z_]. + */ + public void setType(String type) { + if (type != null) { + Matcher matcher = legalIdentifierPattern.matcher(type); + if (!matcher.matches()) { + throw new IllegalArgumentException("Type cannot be empty and may only contain " + + "0-9, a-z and underscores."); + } + } + mType = type; + } + + /** + * Returns this node's plain text; can be null. + */ + public String getPlainText() { + return mPlainText; + } + + /** + * Sets this nodes's plain text; can be null. + */ + public void setPlainText(String plainText) { + mPlainText = plainText; + } + + /** + * Adds or modifies a parameter. + * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text". + * @param value The value. + * @throws An {@link IllegalArgumentException} if the key is null or empty. + * @return this + */ + public Markup setParameter(String key, String value) { + if (key == null || key.isEmpty()) { + throw new IllegalArgumentException("Key cannot be null or empty."); + } + if (key.equals("type")) { + throw new IllegalArgumentException("Key cannot be \"type\"."); + } + if (key.equals("plain_text")) { + throw new IllegalArgumentException("Key cannot be \"plain_text\"."); + } + Matcher matcher = legalIdentifierPattern.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores."); + } + + if (value != null) { + mParameters.putString(key, value); + } else { + removeParameter(key); + } + return this; + } + + /** + * Removes the parameter with the given key + */ + public void removeParameter(String key) { + mParameters.remove(key); + } + + /** + * Returns the value of the parameter. + * @param key The parameter key. + * @return The value of the parameter or null if the parameter is not set. + */ + public String getParameter(String key) { + return mParameters.getString(key); + } + + /** + * Returns the number of parameters that have been set. + */ + public int parametersSize() { + return mParameters.size(); + } + + /** + * Appends a child to the list of children + * @param markup The child. + * @return This instance. + * @throws {@link IllegalArgumentException} if markup is null. + */ + public Markup addNestedMarkup(Markup markup) { + if (markup == null) { + throw new IllegalArgumentException("Nested markup cannot be null"); + } + mNestedMarkups.add(markup); + return this; + } + + /** + * Removes the given node from its children. + * @param markup The child to remove. + * @return True if this instance was modified by this operation, false otherwise. + */ + public boolean removeNestedMarkup(Markup markup) { + return mNestedMarkups.remove(markup); + } + + /** + * Returns the index'th child. + * @param i The index of the child. + * @return The child. + * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize() + */ + public Markup getNestedMarkup(int i) { + return mNestedMarkups.get(i); + } + + + /** + * Returns the number of children. + */ + public int nestedMarkupSize() { + return mNestedMarkups.size(); + } + + /** + * Returns a string representation of this Markup instance. Can be deserialized back to a Markup + * instance with markupFromString(). + */ + public String toString() { + StringBuilder out = new StringBuilder(); + if (mType != null) { + out.append(TYPE + ": \"" + mType + "\""); + } + if (mPlainText != null) { + out.append(out.length() > 0 ? " " : ""); + out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\""); + } + // Sort the parameters alphabetically by key so we have a stable output. + SortedMap<String, String> sortedMap = new TreeMap<String, String>(); + for (String key : mParameters.keySet()) { + sortedMap.put(key, mParameters.getString(key)); + } + for (Map.Entry<String, String> entry : sortedMap.entrySet()) { + out.append(out.length() > 0 ? " " : ""); + out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\""); + } + for (Markup m : mNestedMarkups) { + out.append(out.length() > 0 ? " " : ""); + String nestedStr = m.toString(); + if (nestedStr.isEmpty()) { + out.append(MARKUP + " {}"); + } else { + out.append(MARKUP + " { " + m.toString() + " }"); + } + } + return out.toString(); + } + + /** + * Escapes backslashes and double quotes in the plain text and parameter values before this + * instance is written to a string. + * @param str The string to escape. + * @return The escaped string. + */ + private static String escapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '"') { + out.append("\\\""); + } else if (str.charAt(i) == '\\') { + out.append("\\\\"); + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * The reverse of the escape method, returning plain text and parameter values to their original + * form. + * @param str An escaped string. + * @return The unescaped string. + */ + private static String unescapeQuotedString(String str) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '\\') { + i++; + if (i >= str.length()) { + throw new IllegalArgumentException("Unterminated escape sequence in string: " + + str); + } + c = str.charAt(i); + if (c == '\\') { + out.append("\\"); + } else if (c == '"') { + out.append("\""); + } else { + throw new IllegalArgumentException("Unsupported escape sequence: \\" + c + + " in string " + str); + } + } else { + out.append(c); + } + } + return out.toString(); + } + + /** + * Returns true if the given string consists only of whitespace. + * @param str The string to check. + * @return True if the given string consists only of whitespace. + */ + private static boolean isWhitespace(String str) { + return Pattern.matches("\\s*", str); + } + + /** + * Parses the given string, and overrides the values of this instance with those contained + * in the given string. + * @param str The string to parse; can have superfluous whitespace. + * @return An empty string on success, else the remainder of the string that could not be + * parsed. + */ + private String fromReadableString(String str) { + while (!isWhitespace(str)) { + String newStr = matchValue(str); + if (newStr == null) { + newStr = matchMarkup(str); + + if (newStr == null) { + return str; + } + } + str = newStr; + } + return ""; + } + + // Matches: key : "value" + // where key is an identifier and value can contain escaped quotes + // there may be superflouous whitespace + // The value string may contain quotes and backslashes. + private static final String OPTIONAL_WHITESPACE = "\\s*"; + private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)"; + private static final String KEY_VALUE_REGEX = + "\\A" + OPTIONAL_WHITESPACE + // start of string + IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key: + "\"" + VALUE_REGEX + "\""; // "value" + private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX); + + /** + * Tries to match a key-value pair at the start of the string. If found, add that as a parameter + * of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed key-value pair on success, else null. + */ + private String matchValue(String str) { + // Matches: key: "value" + Matcher matcher = KEY_VALUE_PATTERN.matcher(str); + if (!matcher.find()) { + return null; + } + String key = matcher.group(1); + String value = matcher.group(2); + + if (key == null || value == null) { + return null; + } + String unescapedValue = unescapeQuotedString(value); + if (key.equals(TYPE)) { + this.mType = unescapedValue; + } else if (key.equals(PLAIN_TEXT)) { + this.mPlainText = unescapedValue; + } else { + setParameter(key, unescapedValue); + } + + return str.substring(matcher.group(0).length()); + } + + // matches 'markup {' + private static final Pattern OPEN_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{"); + // matches '}' + private static final Pattern CLOSE_MARKUP_PATTERN = + Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}"); + + /** + * Tries to parse a Markup specification from the start of the string. If so, add that markup to + * the list of nested Markup's of this instance. + * @param str The string to parse. + * @return The remainder of the string without the parsed Markup on success, else null. + */ + private String matchMarkup(String str) { + // find and strip "markup {" + Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str); + + if (!matcher.find()) { + return null; + } + String strRemainder = str.substring(matcher.group(0).length()); + // parse and strip markup contents + Markup nestedMarkup = new Markup(); + strRemainder = nestedMarkup.fromReadableString(strRemainder); + + // find and strip "}" + Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder); + if (!matcherClose.find()) { + return null; + } + strRemainder = strRemainder.substring(matcherClose.group(0).length()); + + // Everything parsed, add markup + this.addNestedMarkup(nestedMarkup); + + // Return remainder + return strRemainder; + } + + /** + * Returns a Markup instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Markup instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + public static Markup markupFromString(String string) throws IllegalArgumentException { + Markup m = new Markup(); + if (m.fromReadableString(string).isEmpty()) { + return m; + } else { + throw new IllegalArgumentException("Cannot parse input to Markup"); + } + } + + /** + * Compares the specified object with this Markup for equality. + * @return True if the given object is a Markup instance with the same type, plain text, + * parameters and the nested markups are also equal to each other and in the same order. + */ + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Markup) ) return false; + Markup m = (Markup) o; + + if (nestedMarkupSize() != this.nestedMarkupSize()) { + return false; + } + + if (!(mType == null ? m.mType == null : mType.equals(m.mType))) { + return false; + } + if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) { + return false; + } + if (!equalBundles(mParameters, m.mParameters)) { + return false; + } + + for (int i = 0; i < this.nestedMarkupSize(); i++) { + if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) { + return false; + } + } + + return true; + } + + /** + * Checks if two bundles are equal to each other. Used by equals(o). + */ + private boolean equalBundles(Bundle one, Bundle two) { + if (one == null || two == null) { + return false; + } + + if(one.size() != two.size()) { + return false; + } + + Set<String> valuesOne = one.keySet(); + for(String key : valuesOne) { + Object valueOne = one.get(key); + Object valueTwo = two.get(key); + if (valueOne instanceof Bundle && valueTwo instanceof Bundle && + !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) { + return false; + } else if (valueOne == null) { + if (valueTwo != null || !two.containsKey(key)) { + return false; + } + } else if(!valueOne.equals(valueTwo)) { + return false; + } + } + return true; + } + + /** + * Returns an unmodifiable list of the children. + * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException} + * if an attempt is made to modify it + */ + public List<Markup> getNestedMarkups() { + return Collections.unmodifiableList(mNestedMarkups); + } + + /** + * @hide + */ + public Markup(Parcel in) { + mType = in.readString(); + mPlainText = in.readString(); + mParameters = in.readBundle(); + in.readList(mNestedMarkups, Markup.class.getClassLoader()); + } + + /** + * Creates a deep copy of the given markup. + */ + public Markup(Markup markup) { + mType = markup.mType; + mPlainText = markup.mPlainText; + mParameters = markup.mParameters; + for (Markup nested : markup.getNestedMarkups()) { + addNestedMarkup(new Markup(nested)); + } + } + + /** + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mType); + dest.writeString(mPlainText); + dest.writeBundle(mParameters); + dest.writeList(mNestedMarkups); + } + + /** + * @hide + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Markup createFromParcel(Parcel in) { + return new Markup(in); + } + + public Markup[] newArray(int size) { + return new Markup[size]; + } + }; +} + diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java index a1da49c..130e3f9 100644 --- a/core/java/android/speech/tts/SynthesisRequestV2.java +++ b/core/java/android/speech/tts/SynthesisRequestV2.java @@ -4,11 +4,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.speech.tts.TextToSpeechClient.UtteranceId; +import android.util.Log; /** * Service-side representation of a synthesis request from a V2 API client. Contains: * <ul> - * <li>The utterance to synthesize</li> + * <li>The markup object to synthesize containing the utterance.</li> * <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li> * <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li> * <li>Voice parameters (Bundle of parameters)</li> @@ -16,8 +17,11 @@ import android.speech.tts.TextToSpeechClient.UtteranceId; * </ul> */ public final class SynthesisRequestV2 implements Parcelable { - /** Synthesis utterance. */ - private final String mText; + + private static final String TAG = "SynthesisRequestV2"; + + /** Synthesis markup */ + private final Markup mMarkup; /** Synthesis id. */ private final String mUtteranceId; @@ -34,9 +38,9 @@ public final class SynthesisRequestV2 implements Parcelable { /** * Constructor for test purposes. */ - public SynthesisRequestV2(String text, String utteranceId, String voiceName, + public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName, Bundle voiceParams, Bundle audioParams) { - this.mText = text; + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = voiceName; this.mVoiceParams = voiceParams; @@ -49,15 +53,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @hide */ public SynthesisRequestV2(Parcel in) { - this.mText = in.readString(); + this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader()); this.mUtteranceId = in.readString(); this.mVoiceName = in.readString(); this.mVoiceParams = in.readBundle(); this.mAudioParams = in.readBundle(); } - SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) { - this.mText = text; + /** + * Constructor to request the synthesis of a sentence. + */ + SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) { + this.mMarkup = markup; this.mUtteranceId = utteranceId; this.mVoiceName = rconfig.getVoice().getName(); this.mVoiceParams = rconfig.getVoiceParams(); @@ -71,7 +78,7 @@ public final class SynthesisRequestV2 implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText); + dest.writeValue(mMarkup); dest.writeString(mUtteranceId); dest.writeString(mVoiceName); dest.writeBundle(mVoiceParams); @@ -82,7 +89,18 @@ public final class SynthesisRequestV2 implements Parcelable { * @return the text which should be synthesized. */ public String getText() { - return mText; + if (mMarkup.getPlainText() == null) { + Log.e(TAG, "Plaintext of markup is null."); + return ""; + } + return mMarkup.getPlainText(); + } + + /** + * @return the markup which should be synthesized. + */ + public Markup getMarkup() { + return mMarkup; } /** diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java index 85f702b..e17b498 100644 --- a/core/java/android/speech/tts/TextToSpeechClient.java +++ b/core/java/android/speech/tts/TextToSpeechClient.java @@ -512,7 +512,6 @@ public class TextToSpeechClient { } } - /** * Connects the client to TTS service. This method returns immediately, and connects to the * service in the background. @@ -876,7 +875,7 @@ public class TextToSpeechClient { private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak"; /** - * Speaks the string using the specified queuing strategy using current + * Speaks the string using the specified queuing strategy and the current * voice. This method is asynchronous, i.e. the method just adds the request * to the queue of TTS requests and then returns. The synthesis might not * have finished (or even started!) at the time when this method returns. @@ -887,12 +886,35 @@ public class TextToSpeechClient { * in {@link RequestCallbacks}. * @param config Synthesis request configuration. Can't be null. Has to contain a * voice. - * @param callbacks Synthesis request callbacks. If null, default request + * @param callbacks Synthesis request callbacks. If null, the default request * callbacks object will be used. */ public void queueSpeak(final String utterance, final UtteranceId utteranceId, final RequestConfig config, final RequestCallbacks callbacks) { + queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks); + } + + /** + * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using + * the specified queuing strategy and the current voice. This method is + * asynchronous, i.e. the method just adds the request to the queue of TTS + * requests and then returns. The synthesis might not have finished (or even + * started!) at the time when this method returns. + * + * @param markup The Markup to be spoken. The written equivalent of the spoken + * text should be no longer than 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param config Synthesis request configuration. Can't be null. Has to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, the default request + * callbacks object will be used. + */ + public void queueSpeak(final Markup markup, + final UtteranceId utteranceId, + final RequestConfig config, + final RequestCallbacks callbacks) { runAction(new Action(ACTION_QUEUE_SPEAK_NAME) { @Override public void run(ITextToSpeechService service) throws RemoteException { @@ -908,7 +930,7 @@ public class TextToSpeechClient { int queueResult = service.speakV2( getCallerIdentity(), - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } @@ -931,12 +953,37 @@ public class TextToSpeechClient { * @param outputFile File to write the generated audio data to. * @param config Synthesis request configuration. Can't be null. Have to contain a * voice. - * @param callbacks Synthesis request callbacks. If null, default request + * @param callbacks Synthesis request callbacks. If null, the default request * callbacks object will be used. */ public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId, final File outputFile, final RequestConfig config, final RequestCallbacks callbacks) { + queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks); + } + + /** + * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance}) + * to a file using the specified parameters. This method is asynchronous, i.e. the + * method just adds the request to the queue of TTS requests and then returns. The + * synthesis might not have finished (or even started!) at the time when this method + * returns. + * + * @param markup The Markup that should be synthesized. The written equivalent of + * the spoken text should be no longer than 1000 characters. + * @param utteranceId Unique identificator used to track the synthesis progress + * in {@link RequestCallbacks}. + * @param outputFile File to write the generated audio data to. + * @param config Synthesis request configuration. Can't be null. Have to contain a + * voice. + * @param callbacks Synthesis request callbacks. If null, the default request + * callbacks object will be used. + */ + public void queueSynthesizeToFile( + final Markup markup, + final UtteranceId utteranceId, + final File outputFile, final RequestConfig config, + final RequestCallbacks callbacks) { runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { @Override public void run(ITextToSpeechService service) throws RemoteException { @@ -964,8 +1011,7 @@ public class TextToSpeechClient { int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(), fileDescriptor, - new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), - config)); + new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config)); fileDescriptor.close(); if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); @@ -981,6 +1027,13 @@ public class TextToSpeechClient { }); } + private static Markup createMarkupFromString(String str) { + return new Utterance() + .append(new Utterance.TtsText(str)) + .setNoWarningOnFallback(true) + .createMarkup(); + } + private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence"; /** diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 6b899d9..14a4024 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -352,6 +352,12 @@ public abstract class TextToSpeechService extends Service { params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true"); } + String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK); + if (noWarning == null || noWarning.equals("false")) { + Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " + + "back to the given plain text."); + } + // Build V1 request SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params); Locale locale = selectedVoice.getLocale(); @@ -856,14 +862,53 @@ public abstract class TextToSpeechService extends Service { } } + /** + * Estimate of the character count equivalent of a Markup instance. Calculated + * by summing the characters of all Markups of type "text". Each other node + * is counted as a single character, as the character count of other nodes + * is non-trivial to calculate and we don't want to accept arbitrarily large + * requests. + */ + private int estimateSynthesisLengthFromMarkup(Markup m) { + int size = 0; + if (m.getType() != null && + m.getType().equals("text") && + m.getParameter("text") != null) { + size += m.getParameter("text").length(); + } else if (m.getType() == null || + !m.getType().equals("utterance")) { + size += 1; + } + for (Markup nested : m.getNestedMarkups()) { + size += estimateSynthesisLengthFromMarkup(nested); + } + return size; + } + @Override public boolean isValid() { - if (mSynthesisRequest.getText() == null) { - Log.e(TAG, "null synthesis text"); + if (mSynthesisRequest.getMarkup() == null) { + Log.e(TAG, "No markup in request."); return false; } - if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) { - Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars"); + String type = mSynthesisRequest.getMarkup().getType(); + if (type == null) { + Log.w(TAG, "Top level markup node should have type \"utterance\", not null"); + return false; + } else if (!type.equals("utterance")) { + Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " + + "\"" + type + "\""); + return false; + } + + int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup()); + if (estimate >= TextToSpeech.getMaxSpeechInputLength()) { + Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars."); + return false; + } + + if (estimate <= 0) { + Log.e(TAG, "null synthesis text"); return false; } diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java new file mode 100644 index 0000000..0a29283 --- /dev/null +++ b/core/java/android/speech/tts/Utterance.java @@ -0,0 +1,595 @@ +package android.speech.tts; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class acts as a builder for {@link Markup} instances. + * <p> + * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and + * {@link Utterance.TtsText}). + * <p>Each semiotic class can be supplied with morphosyntactic features + * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this + * information during synthesis. + * Examples where morphosyntactic features matter: + * <ul> + * <li>In French, the number one is verbalized differently based on the gender of the noun + * it modifies. "un homme" (one man) versus "une femme" (one woman). + * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be + * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You + * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative + * form ("einen") instead of the nominative form "ein". + * </p> + * <p> + * Utterance usage example: + * Markup m1 = new Utterance().append("The Eiffel Tower is") + * .append(new TtsCardinal(324)) + * .append("meters tall."); + * Markup m2 = new Utterance().append("Sie haben") + * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE) + * .append("Tag frei."); + * </p> + */ +public class Utterance { + + /*** + * Toplevel type of markup representation. + */ + public static final String TYPE_UTTERANCE = "utterance"; + /*** + * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that + * no warning will be given when the synthesizer does not support Markup. This is used when + * the user only provides a string to the API instead of a markup. + */ + public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback"; + + // Gender. + public final static int GENDER_UNKNOWN = 0; + public final static int GENDER_NEUTRAL = 1; + public final static int GENDER_MALE = 2; + public final static int GENDER_FEMALE = 3; + + // Animacy. + public final static int ANIMACY_UNKNOWN = 0; + public final static int ANIMACY_ANIMATE = 1; + public final static int ANIMACY_INANIMATE = 2; + + // Multiplicity. + public final static int MULTIPLICITY_UNKNOWN = 0; + public final static int MULTIPLICITY_SINGLE = 1; + public final static int MULTIPLICITY_DUAL = 2; + public final static int MULTIPLICITY_PLURAL = 3; + + // Case. + public final static int CASE_UNKNOWN = 0; + public final static int CASE_NOMINATIVE = 1; + public final static int CASE_ACCUSATIVE = 2; + public final static int CASE_DATIVE = 3; + public final static int CASE_ABLATIVE = 4; + public final static int CASE_GENITIVE = 5; + public final static int CASE_VOCATIVE = 6; + public final static int CASE_LOCATIVE = 7; + public final static int CASE_INSTRUMENTAL = 8; + + private List<AbstractTts<? extends AbstractTts<?>>> says = + new ArrayList<AbstractTts<? extends AbstractTts<?>>>(); + Boolean mNoWarningOnFallback = null; + + /** + * Objects deriving from this class can be appended to a Utterance. This class uses generics + * so method from this class can return instances of its child classes, resulting in a better + * API (CRTP pattern). + */ + public static abstract class AbstractTts<C extends AbstractTts<C>> { + + protected Markup mMarkup = new Markup(); + + /** + * Empty constructor. + */ + protected AbstractTts() { + } + + /** + * Construct with Markup. + * @param markup + */ + protected AbstractTts(Markup markup) { + mMarkup = markup; + } + + /** + * Returns the type of this class, e.g. "cardinal" or "measure". + * @return The type. + */ + public String getType() { + return mMarkup.getType(); + } + + /** + * A fallback plain text can be provided, in case the engine does not support this class + * type, or even Markup altogether. + * @param plainText A string with the plain text. + * @return This instance. + */ + @SuppressWarnings("unchecked") + public C setPlainText(String plainText) { + mMarkup.setPlainText(plainText); + return (C) this; + } + + /** + * Returns the plain text (fallback) string. + * @return Plain text string or null if not set. + */ + public String getPlainText() { + return mMarkup.getPlainText(); + } + + /** + * Populates the plainText if not set and builds a Markup instance. + * @return The Markup object describing this instance. + */ + public Markup getMarkup() { + return new Markup(mMarkup); + } + + @SuppressWarnings("unchecked") + protected C setParameter(String key, String value) { + mMarkup.setParameter(key, value); + return (C) this; + } + + protected String getParameter(String key) { + return mMarkup.getParameter(key); + } + + @SuppressWarnings("unchecked") + protected C removeParameter(String key) { + mMarkup.removeParameter(key); + return (C) this; + } + + /** + * Returns a string representation of this instance, can be deserialized to an equal + * Utterance instance. + */ + public String toString() { + return mMarkup.toString(); + } + + /** + * Returns a generated plain text alternative for this instance if this instance isn't + * better representated by the list of it's children. + * @return Best effort plain text representation of this instance, can be null. + */ + public String generatePlainText() { + return null; + } + } + + public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>> + extends AbstractTts<C> { + // Keys. + private static final String KEY_GENDER = "gender"; + private static final String KEY_ANIMACY = "animacy"; + private static final String KEY_MULTIPLICITY = "multiplicity"; + private static final String KEY_CASE = "case"; + + protected AbstractTtsSemioticClass() { + super(); + } + + protected AbstractTtsSemioticClass(Markup markup) { + super(markup); + } + + @SuppressWarnings("unchecked") + public C setGender(int gender) { + if (gender < 0 || gender > 3) { + throw new IllegalArgumentException("Only four types of gender can be set: " + + "unknown, neutral, maculine and female."); + } + if (gender != GENDER_UNKNOWN) { + setParameter(KEY_GENDER, String.valueOf(gender)); + } else { + setParameter(KEY_GENDER, null); + } + return (C) this; + } + + public int getGender() { + String gender = mMarkup.getParameter(KEY_GENDER); + return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setAnimacy(int animacy) { + if (animacy < 0 || animacy > 2) { + throw new IllegalArgumentException( + "Only two types of animacy can be set: unknown, animate and inanimate"); + } + if (animacy != ANIMACY_UNKNOWN) { + setParameter(KEY_ANIMACY, String.valueOf(animacy)); + } else { + setParameter(KEY_ANIMACY, null); + } + return (C) this; + } + + public int getAnimacy() { + String animacy = getParameter(KEY_ANIMACY); + return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setMultiplicity(int multiplicity) { + if (multiplicity < 0 || multiplicity > 3) { + throw new IllegalArgumentException( + "Only four types of multiplicity can be set: unknown, single, dual and " + + "plural."); + } + if (multiplicity != MULTIPLICITY_UNKNOWN) { + setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity)); + } else { + setParameter(KEY_MULTIPLICITY, null); + } + return (C) this; + } + + public int getMultiplicity() { + String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY); + return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN; + } + + @SuppressWarnings("unchecked") + public C setCase(int grammaticalCase) { + if (grammaticalCase < 0 || grammaticalCase > 8) { + throw new IllegalArgumentException( + "Only nine types of grammatical case can be set."); + } + if (grammaticalCase != CASE_UNKNOWN) { + setParameter(KEY_CASE, String.valueOf(grammaticalCase)); + } else { + setParameter(KEY_CASE, null); + } + return (C) this; + } + + public int getCase() { + String grammaticalCase = mMarkup.getParameter(KEY_CASE); + return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN; + } + } + + /** + * Class that contains regular text, synthesis engine pronounces it using its regular pipeline. + * Parameters: + * <ul> + * <li>Text: the text to synthesize</li> + * </ul> + */ + public static class TtsText extends AbstractTtsSemioticClass<TtsText> { + + // The type of this node. + protected static final String TYPE_TEXT = "text"; + // The text parameter stores the text to be synthesized. + private static final String KEY_TEXT = "text"; + + /** + * Default constructor. + */ + public TtsText() { + mMarkup.setType(TYPE_TEXT); + } + + /** + * Constructor that sets the text to be synthesized. + * @param text The text to be synthesized. + */ + public TtsText(String text) { + this(); + setText(text); + } + + /** + * Constructs a TtsText with the values of the Markup, does not check if the given Markup is + * of the right type. + */ + private TtsText(Markup markup) { + super(markup); + } + + /** + * Sets the text to be synthesized. + * @return This instance. + */ + public TtsText setText(String text) { + setParameter(KEY_TEXT, text); + return this; + } + + /** + * Returns the text to be synthesized. + * @return This instance. + */ + public String getText() { + return getParameter(KEY_TEXT); + } + + /** + * Generates a best effort plain text, in this case simply the text. + */ + @Override + public String generatePlainText() { + return getText(); + } + } + + /** + * Contains a cardinal. + * Parameters: + * <ul> + * <li>integer: the integer to synthesize</li> + * </ul> + */ + public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> { + + // The type of this node. + protected static final String TYPE_CARDINAL = "cardinal"; + // The parameter integer stores the integer to synthesize. + private static final String KEY_INTEGER = "integer"; + + /** + * Default constructor. + */ + public TtsCardinal() { + mMarkup.setType(TYPE_CARDINAL); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(int integer) { + this(); + setInteger(integer); + } + + /** + * Constructor that sets the integer to be synthesized. + */ + public TtsCardinal(String integer) { + this(); + setInteger(integer); + } + + /** + * Constructs a TtsText with the values of the Markup. + * Does not check if the given Markup is of the right type. + */ + private TtsCardinal(Markup markup) { + super(markup); + } + + /** + * Sets the integer. + * @return This instance. + */ + public TtsCardinal setInteger(int integer) { + return setInteger(String.valueOf(integer)); + } + + /** + * Sets the integer. + * @param integer A non-empty string of digits with an optional '-' in front. + * @return This instance. + */ + public TtsCardinal setInteger(String integer) { + if (!integer.matches("-?\\d+")) { + throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\""); + } + setParameter(KEY_INTEGER, integer); + return this; + } + + /** + * Returns the integer parameter. + */ + public String getInteger() { + return getParameter(KEY_INTEGER); + } + + /** + * Generates a best effort plain text, in this case simply the integer. + */ + @Override + public String generatePlainText() { + return getInteger(); + } + } + + /** + * Default constructor. + */ + public Utterance() {} + + /** + * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the + * this same method on its children. + */ + private String constructPlainText(Markup m) { + StringBuilder plainText = new StringBuilder(); + if (m.getPlainText() != null) { + plainText.append(m.getPlainText()); + } else { + for (Markup nestedMarkup : m.getNestedMarkups()) { + String nestedPlainText = constructPlainText(nestedMarkup); + if (!nestedPlainText.isEmpty()) { + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(nestedPlainText); + } + } + } + return plainText.toString(); + } + + /** + * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the + * user has not provided one already. + * @return A Markup instance representing this utterance. + */ + public Markup createMarkup() { + Markup markup = new Markup(TYPE_UTTERANCE); + StringBuilder plainText = new StringBuilder(); + for (AbstractTts<? extends AbstractTts<?>> say : says) { + // Get a copy of this markup, and generate a plaintext for it if is not set. + Markup sayMarkup = say.getMarkup(); + if (sayMarkup.getPlainText() == null) { + sayMarkup.setPlainText(say.generatePlainText()); + } + if (plainText.length() != 0) { + plainText.append(" "); + } + plainText.append(constructPlainText(sayMarkup)); + markup.addNestedMarkup(sayMarkup); + } + if (mNoWarningOnFallback != null) { + markup.setParameter(KEY_NO_WARNING_ON_FALLBACK, + mNoWarningOnFallback ? "true" : "false"); + } + markup.setPlainText(plainText.toString()); + return markup; + } + + /** + * Appends an element to this Utterance instance. + * @return this instance + */ + public Utterance append(AbstractTts<? extends AbstractTts<?>> say) { + says.add(say); + return this; + } + + private Utterance append(Markup markup) { + if (markup.getType().equals(TtsText.TYPE_TEXT)) { + append(new TtsText(markup)); + } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) { + append(new TtsCardinal(markup)); + } else { + // Unknown node, a class we don't know about. + if (markup.getPlainText() != null) { + append(new TtsText(markup.getPlainText())); + } else { + // No plainText specified; add its children + // seperately. In case of a new prosody node, + // we would still verbalize it correctly. + for (Markup nested : markup.getNestedMarkups()) { + append(nested); + } + } + } + return this; + } + + /** + * Returns a string representation of this Utterance instance. Can be deserialized back to an + * Utterance instance with utteranceFromString(). Can be used to store utterances to be used + * at a later time. + */ + public String toString() { + String out = "type: \"" + TYPE_UTTERANCE + "\""; + if (mNoWarningOnFallback != null) { + out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\""; + } + for (AbstractTts<? extends AbstractTts<?>> say : says) { + out += " markup { " + say.getMarkup().toString() + " }"; + } + return out; + } + + /** + * Returns an Utterance instance from the string representation generated by toString(). + * @param string The string representation generated by toString(). + * @return The new Utterance instance. + * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed. + */ + static public Utterance utteranceFromString(String string) throws IllegalArgumentException { + Utterance utterance = new Utterance(); + Markup markup = Markup.markupFromString(string); + if (!markup.getType().equals(TYPE_UTTERANCE)) { + throw new IllegalArgumentException("Top level markup should be of type \"" + + TYPE_UTTERANCE + "\", but was of type \"" + + markup.getType() + "\".") ; + } + for (Markup nestedMarkup : markup.getNestedMarkups()) { + utterance.append(nestedMarkup); + } + return utterance; + } + + /** + * Appends a new TtsText with the given text. + * @param text The text to synthesize. + * @return This instance. + */ + public Utterance append(String text) { + return append(new TtsText(text)); + } + + /** + * Appends a TtsCardinal representing the given number. + * @param integer The integer to synthesize. + * @return this + */ + public Utterance append(int integer) { + return append(new TtsCardinal(integer)); + } + + /** + * Returns the n'th element in this Utterance. + * @param i The index. + * @return The n'th element in this Utterance. + * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size() + */ + public AbstractTts<? extends AbstractTts<?>> get(int i) { + return says.get(i); + } + + /** + * Returns the number of elements in this Utterance. + * @return The number of elements in this Utterance. + */ + public int size() { + return says.size(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !(o instanceof Utterance) ) return false; + Utterance utt = (Utterance) o; + + if (says.size() != utt.says.size()) { + return false; + } + + for (int i = 0; i < says.size(); i++) { + if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) { + return false; + } + } + return true; + } + + /** + * Can be set to true or false, true indicating that the user provided only a string to the API, + * at which the system will not issue a warning if the synthesizer falls back onto the plain + * text when the synthesizer does not support Markup. + */ + public Utterance setNoWarningOnFallback(boolean noWarning) { + mNoWarningOnFallback = noWarning; + return this; + } +} diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl deleted file mode 100644 index ac83356..0000000 --- a/core/java/android/tv/ITvInputClient.aidl +++ /dev/null @@ -1,32 +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.tv; - -import android.content.ComponentName; -import android.tv.ITvInputSession; -import android.view.InputChannel; - -/** - * Interface a client of the ITvInputManager implements, to identify itself and receive information - * about changes to the state of each TV input service. - * @hide - */ -oneway interface ITvInputClient { - void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq); - void onAvailabilityChanged(in String inputId, boolean isAvailable); - void onSessionReleased(int seq); -} diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl deleted file mode 100644 index b756aba..0000000 --- a/core/java/android/tv/ITvInputManager.aidl +++ /dev/null @@ -1,49 +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.tv; - -import android.content.ComponentName; -import android.graphics.Rect; -import android.net.Uri; -import android.tv.ITvInputClient; -import android.tv.TvInputInfo; -import android.view.Surface; - -/** - * Interface to the TV input manager service. - * @hide - */ -interface ITvInputManager { - List<TvInputInfo> getTvInputList(int userId); - - boolean getAvailability(in ITvInputClient client, in String inputId, int userId); - - void registerCallback(in ITvInputClient client, in String inputId, int userId); - void unregisterCallback(in ITvInputClient client, in String inputId, int userId); - - void createSession(in ITvInputClient client, in String inputId, int seq, int userId); - void releaseSession(in IBinder sessionToken, int userId); - - void setSurface(in IBinder sessionToken, in Surface surface, int userId); - void setVolume(in IBinder sessionToken, float volume, int userId); - void tune(in IBinder sessionToken, in Uri channelUri, int userId); - - void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, - int userId); - void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId); - void removeOverlayView(in IBinder sessionToken, int userId); -} diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl deleted file mode 100644 index 4f1bc2b..0000000 --- a/core/java/android/tv/ITvInputService.aidl +++ /dev/null @@ -1,31 +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.tv; - -import android.tv.ITvInputServiceCallback; -import android.tv.ITvInputSessionCallback; -import android.view.InputChannel; - -/** - * Top-level interface to a TV input component (implemented in a Service). - * @hide - */ -oneway interface ITvInputService { - void registerCallback(ITvInputServiceCallback callback); - void unregisterCallback(in ITvInputServiceCallback callback); - void createSession(in InputChannel channel, ITvInputSessionCallback callback); -} diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl deleted file mode 100644 index 32fee4b..0000000 --- a/core/java/android/tv/ITvInputSession.aidl +++ /dev/null @@ -1,39 +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.tv; - -import android.graphics.Rect; -import android.net.Uri; -import android.view.Surface; - -/** - * Sub-interface of ITvInputService which is created per session and has its own context. - * @hide - */ -oneway interface ITvInputSession { - void release(); - - void setSurface(in Surface surface); - // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan - // is to introduce some new concepts that will solve a number of problems in audio policy today. - void setVolume(float volume); - void tune(in Uri channelUri); - - void createOverlayView(in IBinder windowToken, in Rect frame); - void relayoutOverlayView(in Rect frame); - void removeOverlayView(); -} diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java deleted file mode 100644 index 3ccccf3..0000000 --- a/core/java/android/tv/ITvInputSessionWrapper.java +++ /dev/null @@ -1,179 +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.tv; - -import android.content.Context; -import android.graphics.Rect; -import android.net.Uri; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.tv.TvInputManager.Session; -import android.tv.TvInputService.TvInputSessionImpl; -import android.util.Log; -import android.view.InputChannel; -import android.view.InputEvent; -import android.view.InputEventReceiver; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; - -import com.android.internal.os.HandlerCaller; -import com.android.internal.os.SomeArgs; - -/** - * Implements the internal ITvInputSession interface to convert incoming calls on to it back to - * calls on the public TvInputSession interface, scheduling them on the main thread of the process. - * - * @hide - */ -public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback { - private static final String TAG = "TvInputSessionWrapper"; - - private static final int DO_RELEASE = 1; - private static final int DO_SET_SURFACE = 2; - private static final int DO_SET_VOLUME = 3; - private static final int DO_TUNE = 4; - private static final int DO_CREATE_OVERLAY_VIEW = 5; - private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; - private static final int DO_REMOVE_OVERLAY_VIEW = 7; - - private final HandlerCaller mCaller; - - private TvInputSessionImpl mTvInputSessionImpl; - private InputChannel mChannel; - private TvInputEventReceiver mReceiver; - - public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl, - InputChannel channel) { - mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); - mTvInputSessionImpl = sessionImpl; - mChannel = channel; - if (channel != null) { - mReceiver = new TvInputEventReceiver(channel, context.getMainLooper()); - } - } - - @Override - public void executeMessage(Message msg) { - if (mTvInputSessionImpl == null) { - return; - } - - switch (msg.what) { - case DO_RELEASE: { - mTvInputSessionImpl.release(); - mTvInputSessionImpl = null; - if (mReceiver != null) { - mReceiver.dispose(); - mReceiver = null; - } - if (mChannel != null) { - mChannel.dispose(); - mChannel = null; - } - return; - } - case DO_SET_SURFACE: { - mTvInputSessionImpl.setSurface((Surface) msg.obj); - return; - } - case DO_SET_VOLUME: { - mTvInputSessionImpl.setVolume((Float) msg.obj); - return; - } - case DO_TUNE: { - mTvInputSessionImpl.tune((Uri) msg.obj); - return; - } - case DO_CREATE_OVERLAY_VIEW: { - SomeArgs args = (SomeArgs) msg.obj; - mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); - args.recycle(); - return; - } - case DO_RELAYOUT_OVERLAY_VIEW: { - mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj); - return; - } - case DO_REMOVE_OVERLAY_VIEW: { - mTvInputSessionImpl.removeOverlayView(true); - return; - } - default: { - Log.w(TAG, "Unhandled message code: " + msg.what); - return; - } - } - } - - @Override - public void release() { - mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE)); - } - - @Override - public void setSurface(Surface surface) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface)); - } - - @Override - public final void setVolume(float volume) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume)); - } - - @Override - public void tune(Uri channelUri) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri)); - } - - @Override - public void createOverlayView(IBinder windowToken, Rect frame) { - mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken, - frame)); - } - - @Override - public void relayoutOverlayView(Rect frame) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame)); - } - - @Override - public void removeOverlayView() { - mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW)); - } - - private final class TvInputEventReceiver extends InputEventReceiver { - public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper); - } - - @Override - public void onInputEvent(InputEvent event) { - if (mTvInputSessionImpl == null) { - // The session has been finished. - finishInputEvent(event, false); - return; - } - - int handled = mTvInputSessionImpl.dispatchInputEvent(event, this); - if (handled != Session.DISPATCH_IN_PROGRESS) { - finishInputEvent(event, handled == Session.DISPATCH_HANDLED); - } - } - } -} diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java deleted file mode 100644 index 50462cc..0000000 --- a/core/java/android/tv/TvInputInfo.java +++ /dev/null @@ -1,163 +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.tv; - -import android.content.ComponentName; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * This class is used to specify meta information of a TV input. - */ -public final class TvInputInfo implements Parcelable { - private final ResolveInfo mService; - private final String mId; - - /** - * Constructor. - * - * @param service The ResolveInfo returned from the package manager about this TV input service. - * @hide - */ - public TvInputInfo(ResolveInfo service) { - mService = service; - ServiceInfo si = service.serviceInfo; - mId = generateInputIdForComponenetName(new ComponentName(si.packageName, si.name)); - } - - /** - * Returns a unique ID for this TV input. The ID is generated from the package and class name - * implementing the TV input service. - */ - public String getId() { - return mId; - } - - /** - * Returns the .apk package that implements this TV input service. - */ - public String getPackageName() { - return mService.serviceInfo.packageName; - } - - /** - * Returns the class name of the service component that implements this TV input service. - */ - public String getServiceName() { - return mService.serviceInfo.name; - } - - /** - * Returns the component of the service that implements this TV input. - */ - public ComponentName getComponent() { - return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); - } - - /** - * Loads the user-displayed label for this TV input service. - * - * @param pm Supplies a PackageManager used to load the TV input's resources. - * @return a CharSequence containing the TV input's label. If the TV input does not have - * a label, its name is returned. - */ - public CharSequence loadLabel(PackageManager pm) { - return mService.loadLabel(pm); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public int hashCode() { - return mId.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (!(o instanceof TvInputInfo)) { - return false; - } - - TvInputInfo obj = (TvInputInfo) o; - return mId.equals(obj.mId) - && mService.serviceInfo.packageName.equals(obj.mService.serviceInfo.packageName) - && mService.serviceInfo.name.equals(obj.mService.serviceInfo.name); - } - - @Override - public String toString() { - return "TvInputInfo{id=" + mId - + ", pkg=" + mService.serviceInfo.packageName - + ", service=" + mService.serviceInfo.name + "}"; - } - - /** - * Used to package this object into a {@link Parcel}. - * - * @param dest The {@link Parcel} to be written. - * @param flags The flags used for parceling. - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mId); - mService.writeToParcel(dest, flags); - } - - /** - * Used to generate an input id from a ComponentName. - * - * @param name the component name for generating an input id. - * @return the generated input id for the given {@code name}. - * @hide - */ - public static final String generateInputIdForComponenetName(ComponentName name) { - return name.flattenToShortString(); - } - - /** - * Used to make this class parcelable. - * - * @hide - */ - public static final Parcelable.Creator<TvInputInfo> CREATOR = - new Parcelable.Creator<TvInputInfo>() { - @Override - public TvInputInfo createFromParcel(Parcel in) { - return new TvInputInfo(in); - } - - @Override - public TvInputInfo[] newArray(int size) { - return new TvInputInfo[size]; - } - }; - - private TvInputInfo(Parcel in) { - mId = in.readString(); - mService = ResolveInfo.CREATOR.createFromParcel(in); - } -} diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java deleted file mode 100644 index dfa84f8..0000000 --- a/core/java/android/tv/TvInputManager.java +++ /dev/null @@ -1,784 +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.tv; - -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; -import android.util.Pools.Pool; -import android.util.Pools.SimplePool; -import android.util.SparseArray; -import android.view.InputChannel; -import android.view.InputEvent; -import android.view.InputEventSender; -import android.view.Surface; -import android.view.View; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Central system API to the overall TV input framework (TIF) architecture, which arbitrates - * interaction between applications and the selected TV inputs. - */ -public final class TvInputManager { - private static final String TAG = "TvInputManager"; - - private final ITvInputManager mService; - - // A mapping from an input to the list of its TvInputListenerRecords. - private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap = - new HashMap<String, List<TvInputListenerRecord>>(); - - // A mapping from the sequence number of a session to its SessionCallbackRecord. - private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = - new SparseArray<SessionCallbackRecord>(); - - // A sequence number for the next session to be created. Should be protected by a lock - // {@code mSessionCallbackRecordMap}. - private int mNextSeq; - - private final ITvInputClient mClient; - - private final int mUserId; - - /** - * Interface used to receive the created session. - */ - public abstract static class SessionCallback { - /** - * This is called after {@link TvInputManager#createSession} has been processed. - * - * @param session A {@link TvInputManager.Session} instance created. This can be - * {@code null} if the creation request failed. - */ - public void onSessionCreated(Session session) { - } - - /** - * This is called when {@link TvInputManager.Session} is released. - * This typically happens when the process hosting the session has crashed or been killed. - * - * @param session A {@link TvInputManager.Session} instance released. - */ - public void onSessionReleased(Session session) { - } - } - - private static final class SessionCallbackRecord { - private final SessionCallback mSessionCallback; - private final Handler mHandler; - private Session mSession; - - public SessionCallbackRecord(SessionCallback sessionCallback, - Handler handler) { - mSessionCallback = sessionCallback; - mHandler = handler; - } - - public void postSessionCreated(final Session session) { - mSession = session; - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onSessionCreated(session); - } - }); - } - - public void postSessionReleased() { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onSessionReleased(mSession); - } - }); - } - } - - /** - * Interface used to monitor status of the TV input. - */ - public abstract static class TvInputListener { - /** - * This is called when the availability status of a given TV input is changed. - * - * @param inputId the id of the TV input. - * @param isAvailable {@code true} if the given TV input is available to show TV programs. - * {@code false} otherwise. - */ - public void onAvailabilityChanged(String inputId, boolean isAvailable) { - } - } - - private static final class TvInputListenerRecord { - private final TvInputListener mListener; - private final Handler mHandler; - - public TvInputListenerRecord(TvInputListener listener, Handler handler) { - mListener = listener; - mHandler = handler; - } - - public TvInputListener getListener() { - return mListener; - } - - public void postAvailabilityChanged(final String inputId, final boolean isAvailable) { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onAvailabilityChanged(inputId, isAvailable); - } - }); - } - } - - /** - * @hide - */ - public TvInputManager(ITvInputManager service, int userId) { - mService = service; - mUserId = userId; - mClient = new ITvInputClient.Stub() { - @Override - public void onSessionCreated(String inputId, IBinder token, InputChannel channel, - int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for " + token); - return; - } - Session session = null; - if (token != null) { - session = new Session(token, channel, mService, mUserId, seq, - mSessionCallbackRecordMap); - } - record.postSessionCreated(session); - } - } - - @Override - public void onSessionReleased(int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - mSessionCallbackRecordMap.delete(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq:" + seq); - return; - } - record.mSession.releaseInternal(); - record.postSessionReleased(); - } - } - - @Override - public void onAvailabilityChanged(String inputId, boolean isAvailable) { - synchronized (mTvInputListenerRecordsMap) { - List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); - if (records == null) { - // Silently ignore - no listener is registered yet. - return; - } - int recordsCount = records.size(); - for (int i = 0; i < recordsCount; i++) { - records.get(i).postAvailabilityChanged(inputId, isAvailable); - } - } - } - }; - } - - /** - * Returns the complete list of TV inputs on the system. - * - * @return List of {@link TvInputInfo} for each TV input that describes its meta information. - */ - public List<TvInputInfo> getTvInputList() { - try { - return mService.getTvInputList(mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Returns the availability of a given TV input. - * - * @param inputId the id of the TV input. - * @throws IllegalArgumentException if the argument is {@code null}. - * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given - * TV input. - */ - public boolean getAvailability(String inputId) { - if (inputId == null) { - throw new IllegalArgumentException("id cannot be null"); - } - synchronized (mTvInputListenerRecordsMap) { - List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); - if (records == null || records.size() == 0) { - throw new IllegalStateException("At least one listener should be registered."); - } - } - try { - return mService.getAvailability(mClient, inputId, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Registers a {@link TvInputListener} for a given TV input. - * - * @param inputId the id of the TV input. - * @param listener a listener used to monitor status of the given TV input. - * @param handler a {@link Handler} that the status change will be delivered to. - * @throws IllegalArgumentException if any of the arguments is {@code null}. - */ - public void registerListener(String inputId, TvInputListener listener, Handler handler) { - if (inputId == null) { - throw new IllegalArgumentException("id cannot be null"); - } - if (listener == null) { - throw new IllegalArgumentException("listener cannot be null"); - } - if (handler == null) { - throw new IllegalArgumentException("handler cannot be null"); - } - synchronized (mTvInputListenerRecordsMap) { - List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); - if (records == null) { - records = new ArrayList<TvInputListenerRecord>(); - mTvInputListenerRecordsMap.put(inputId, records); - try { - mService.registerCallback(mClient, inputId, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - records.add(new TvInputListenerRecord(listener, handler)); - } - } - - /** - * Unregisters the existing {@link TvInputListener} for a given TV input. - * - * @param inputId the id of the TV input. - * @param listener the existing listener to remove for the given TV input. - * @throws IllegalArgumentException if any of the arguments is {@code null}. - */ - public void unregisterListener(String inputId, final TvInputListener listener) { - if (inputId == null) { - throw new IllegalArgumentException("id cannot be null"); - } - if (listener == null) { - throw new IllegalArgumentException("listener cannot be null"); - } - synchronized (mTvInputListenerRecordsMap) { - List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId); - if (records == null) { - Log.e(TAG, "No listener found for " + inputId); - return; - } - for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) { - TvInputListenerRecord record = it.next(); - if (record.getListener() == listener) { - it.remove(); - } - } - if (records.isEmpty()) { - try { - mService.unregisterCallback(mClient, inputId, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - mTvInputListenerRecordsMap.remove(inputId); - } - } - } - } - - /** - * Creates a {@link Session} for a given TV input. - * <p> - * The number of sessions that can be created at the same time is limited by the capability of - * the given TV input. - * </p> - * - * @param inputId the id of the TV input. - * @param callback a callback used to receive the created session. - * @param handler a {@link Handler} that the session creation will be delivered to. - * @throws IllegalArgumentException if any of the arguments is {@code null}. - */ - public void createSession(String inputId, final SessionCallback callback, - Handler handler) { - if (inputId == null) { - throw new IllegalArgumentException("id cannot be null"); - } - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - if (handler == null) { - throw new IllegalArgumentException("handler cannot be null"); - } - SessionCallbackRecord record = new SessionCallbackRecord(callback, handler); - synchronized (mSessionCallbackRecordMap) { - int seq = mNextSeq++; - mSessionCallbackRecordMap.put(seq, record); - try { - mService.createSession(mClient, inputId, seq, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - } - - /** The Session provides the per-session functionality of TV inputs. */ - public static final class Session { - static final int DISPATCH_IN_PROGRESS = -1; - static final int DISPATCH_NOT_HANDLED = 0; - static final int DISPATCH_HANDLED = 1; - - private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; - - private final ITvInputManager mService; - private final int mUserId; - private final int mSeq; - - // For scheduling input event handling on the main thread. This also serves as a lock to - // protect pending input events and the input channel. - private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); - - private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); - private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); - private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; - - private IBinder mToken; - private TvInputEventSender mSender; - private InputChannel mChannel; - - /** @hide */ - private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, - int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { - mToken = token; - mChannel = channel; - mService = service; - mUserId = userId; - mSeq = seq; - mSessionCallbackRecordMap = sessionCallbackRecordMap; - } - - /** - * Releases this session. - * - * @throws IllegalStateException if the session has been already released. - */ - public void release() { - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - mService.releaseSession(mToken, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - - releaseInternal(); - } - - /** - * Sets the {@link android.view.Surface} for this session. - * - * @param surface A {@link android.view.Surface} used to render video. - * @throws IllegalStateException if the session has been already released. - * @hide - */ - public void setSurface(Surface surface) { - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - // surface can be null. - try { - mService.setSurface(mToken, surface, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Sets the relative volume of this session to handle a change of audio focus. - * - * @param volume A volume value between 0.0f to 1.0f. - * @throws IllegalArgumentException if the volume value is out of range. - * @throws IllegalStateException if the session has been already released. - */ - public void setVolume(float volume) { - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - if (volume < 0.0f || volume > 1.0f) { - throw new IllegalArgumentException("volume should be between 0.0f and 1.0f"); - } - mService.setVolume(mToken, volume, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Tunes to a given channel. - * - * @param channelUri The URI of a channel. - * @throws IllegalArgumentException if the argument is {@code null}. - * @throws IllegalStateException if the session has been already released. - */ - public void tune(Uri channelUri) { - if (channelUri == null) { - throw new IllegalArgumentException("channelUri cannot be null"); - } - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - mService.tune(mToken, channelUri, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} - * should be called whenever the layout of its containing view is changed. - * {@link #removeOverlayView()} should be called to remove the overlay view. - * Since a session can have only one overlay view, this method should be called only once - * or it can be called again after calling {@link #removeOverlayView()}. - * - * @param view A view playing TV. - * @param frame A position of the overlay view. - * @throws IllegalArgumentException if any of the arguments is {@code null}. - * @throws IllegalStateException if {@code view} is not attached to a window or - * if the session has been already released. - */ - void createOverlayView(View view, Rect frame) { - if (view == null) { - throw new IllegalArgumentException("view cannot be null"); - } - if (frame == null) { - throw new IllegalArgumentException("frame cannot be null"); - } - if (view.getWindowToken() == null) { - throw new IllegalStateException("view must be attached to a window"); - } - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Relayouts the current overlay view. - * - * @param frame A new position of the overlay view. - * @throws IllegalArgumentException if the arguments is {@code null}. - * @throws IllegalStateException if the session has been already released. - */ - void relayoutOverlayView(Rect frame) { - if (frame == null) { - throw new IllegalArgumentException("frame cannot be null"); - } - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - mService.relayoutOverlayView(mToken, frame, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Removes the current overlay view. - * - * @throws IllegalStateException if the session has been already released. - */ - void removeOverlayView() { - if (mToken == null) { - throw new IllegalStateException("the session has been already released"); - } - try { - mService.removeOverlayView(mToken, mUserId); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - /** - * Dispatches an input event to this session. - * - * @param event {@link InputEvent} to dispatch. - * @param token A token used to identify the input event later in the callback. - * @param callback A callback used to receive the dispatch result. - * @param handler {@link Handler} that the dispatch result will be delivered to. - * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns - * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns - * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will - * be invoked later. - * @throws IllegalArgumentException if any of the necessary arguments is {@code null}. - * @hide - */ - public int dispatchInputEvent(InputEvent event, Object token, - FinishedInputEventCallback callback, Handler handler) { - if (event == null) { - throw new IllegalArgumentException("event cannot be null"); - } - if (callback != null && handler == null) { - throw new IllegalArgumentException("handler cannot be null"); - } - synchronized (mHandler) { - if (mChannel == null) { - return DISPATCH_NOT_HANDLED; - } - PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); - if (Looper.myLooper() == Looper.getMainLooper()) { - // Already running on the main thread so we can send the event immediately. - return sendInputEventOnMainLooperLocked(p); - } - - // Post the event to the main thread. - Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); - msg.setAsynchronous(true); - mHandler.sendMessage(msg); - return DISPATCH_IN_PROGRESS; - } - } - - /** - * Callback that is invoked when an input event that was dispatched to this session has been - * finished. - * - * @hide - */ - public interface FinishedInputEventCallback { - /** - * Called when the dispatched input event is finished. - * - * @param token a token passed to {@link #dispatchInputEvent}. - * @param handled {@code true} if the dispatched input event was handled properly. - * {@code false} otherwise. - */ - public void onFinishedInputEvent(Object token, boolean handled); - } - - // Must be called on the main looper - private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { - synchronized (mHandler) { - int result = sendInputEventOnMainLooperLocked(p); - if (result == DISPATCH_IN_PROGRESS) { - return; - } - } - - invokeFinishedInputEventCallback(p, false); - } - - private int sendInputEventOnMainLooperLocked(PendingEvent p) { - if (mChannel != null) { - if (mSender == null) { - mSender = new TvInputEventSender(mChannel, mHandler.getLooper()); - } - - final InputEvent event = p.mEvent; - final int seq = event.getSequenceNumber(); - if (mSender.sendInputEvent(seq, event)) { - mPendingEvents.put(seq, p); - Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); - return DISPATCH_IN_PROGRESS; - } - - Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" - + event); - } - return DISPATCH_NOT_HANDLED; - } - - void finishedInputEvent(int seq, boolean handled, boolean timeout) { - final PendingEvent p; - synchronized (mHandler) { - int index = mPendingEvents.indexOfKey(seq); - if (index < 0) { - return; // spurious, event already finished or timed out - } - - p = mPendingEvents.valueAt(index); - mPendingEvents.removeAt(index); - - if (timeout) { - Log.w(TAG, "Timeout waiting for seesion to handle input event after " - + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); - } else { - mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); - } - } - - invokeFinishedInputEventCallback(p, handled); - } - - // Assumes the event has already been removed from the queue. - void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { - p.mHandled = handled; - if (p.mHandler.getLooper().isCurrentThread()) { - // Already running on the callback handler thread so we can send the callback - // immediately. - p.run(); - } else { - // Post the event to the callback handler thread. - // In this case, the callback will be responsible for recycling the event. - Message msg = Message.obtain(p.mHandler, p); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - } - - private void flushPendingEventsLocked() { - mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); - - final int count = mPendingEvents.size(); - for (int i = 0; i < count; i++) { - int seq = mPendingEvents.keyAt(i); - Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - } - - private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, - FinishedInputEventCallback callback, Handler handler) { - PendingEvent p = mPendingEventPool.acquire(); - if (p == null) { - p = new PendingEvent(); - } - p.mEvent = event; - p.mToken = token; - p.mCallback = callback; - p.mHandler = handler; - return p; - } - - private void recyclePendingEventLocked(PendingEvent p) { - p.recycle(); - mPendingEventPool.release(p); - } - - private void releaseInternal() { - mToken = null; - synchronized (mHandler) { - if (mChannel != null) { - if (mSender != null) { - flushPendingEventsLocked(); - mSender.dispose(); - mSender = null; - } - mChannel.dispose(); - mChannel = null; - } - } - synchronized (mSessionCallbackRecordMap) { - mSessionCallbackRecordMap.remove(mSeq); - } - } - - private final class InputEventHandler extends Handler { - public static final int MSG_SEND_INPUT_EVENT = 1; - public static final int MSG_TIMEOUT_INPUT_EVENT = 2; - public static final int MSG_FLUSH_INPUT_EVENT = 3; - - InputEventHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SEND_INPUT_EVENT: { - sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); - return; - } - case MSG_TIMEOUT_INPUT_EVENT: { - finishedInputEvent(msg.arg1, false, true); - return; - } - case MSG_FLUSH_INPUT_EVENT: { - finishedInputEvent(msg.arg1, false, false); - return; - } - } - } - } - - private final class TvInputEventSender extends InputEventSender { - public TvInputEventSender(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper); - } - - @Override - public void onInputEventFinished(int seq, boolean handled) { - finishedInputEvent(seq, handled, false); - } - } - - private final class PendingEvent implements Runnable { - public InputEvent mEvent; - public Object mToken; - public FinishedInputEventCallback mCallback; - public Handler mHandler; - public boolean mHandled; - - public void recycle() { - mEvent = null; - mToken = null; - mCallback = null; - mHandler = null; - mHandled = false; - } - - @Override - public void run() { - mCallback.onFinishedInputEvent(mToken, mHandled); - - synchronized (mHandler) { - recyclePendingEventLocked(this); - } - } - } - } -} diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java deleted file mode 100644 index eeb738d..0000000 --- a/core/java/android/tv/TvInputService.java +++ /dev/null @@ -1,551 +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.tv; - -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.tv.TvInputManager.Session; -import android.util.Log; -import android.view.Gravity; -import android.view.InputChannel; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.InputEventReceiver; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.SomeArgs; - -/** - * A base class for implementing television input service. - */ -public abstract class TvInputService extends Service { - // STOPSHIP: Turn debugging off. - private static final boolean DEBUG = true; - private static final String TAG = "TvInputService"; - - /** - * This is the interface name that a service implementing a TV input should say that it support - * -- that is, this is the action it uses for its intent filter. To be supported, the service - * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that - * other applications cannot abuse it. - */ - public static final String SERVICE_INTERFACE = "android.tv.TvInputService"; - - private String mId; - private final Handler mHandler = new ServiceHandler(); - private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks = - new RemoteCallbackList<ITvInputServiceCallback>(); - private boolean mAvailable; - - @Override - public void onCreate() { - super.onCreate(); - mId = TvInputInfo.generateInputIdForComponenetName( - new ComponentName(getPackageName(), getClass().getName())); - } - - @Override - public final IBinder onBind(Intent intent) { - return new ITvInputService.Stub() { - @Override - public void registerCallback(ITvInputServiceCallback cb) { - if (cb != null) { - mCallbacks.register(cb); - // The first time a callback is registered, the service needs to report its - // availability status so that the system can know its initial value. - try { - cb.onAvailabilityChanged(mId, mAvailable); - } catch (RemoteException e) { - Log.e(TAG, "error in onAvailabilityChanged", e); - } - } - } - - @Override - public void unregisterCallback(ITvInputServiceCallback cb) { - if (cb != null) { - mCallbacks.unregister(cb); - } - } - - @Override - public void createSession(InputChannel channel, ITvInputSessionCallback cb) { - if (channel == null) { - Log.w(TAG, "Creating session without input channel"); - } - if (cb == null) { - return; - } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = channel; - args.arg2 = cb; - mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget(); - } - }; - } - - /** - * Convenience method to notify an availability change of this TV input service. - * - * @param available {@code true} if the input service is available to show TV programs. - */ - public final void setAvailable(boolean available) { - if (available != mAvailable) { - mAvailable = available; - mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available) - .sendToTarget(); - } - } - - /** - * Get the number of callbacks that are registered. - * - * @hide - */ - @VisibleForTesting - public final int getRegisteredCallbackCount() { - return mCallbacks.getRegisteredCallbackCount(); - } - - /** - * Returns a concrete implementation of {@link TvInputSessionImpl}. - * <p> - * May return {@code null} if this TV input service fails to create a session for some reason. - * </p> - */ - public abstract TvInputSessionImpl onCreateSession(); - - /** - * Base class for derived classes to implement to provide {@link TvInputManager.Session}. - */ - public abstract class TvInputSessionImpl implements KeyEvent.Callback { - private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); - private final WindowManager mWindowManager; - private WindowManager.LayoutParams mWindowParams; - private Surface mSurface; - private View mOverlayView; - private boolean mOverlayViewEnabled; - private IBinder mWindowToken; - private Rect mOverlayFrame; - - public TvInputSessionImpl() { - mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - } - - /** - * Enables or disables the overlay view. By default, the overlay view is disabled. Must be - * called explicitly after the session is created to enable the overlay view. - * - * @param enable {@code true} if you want to enable the overlay view. {@code false} - * otherwise. - */ - public void setOverlayViewEnabled(final boolean enable) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (enable == mOverlayViewEnabled) { - return; - } - mOverlayViewEnabled = enable; - if (enable) { - if (mWindowToken != null) { - createOverlayView(mWindowToken, mOverlayFrame); - } - } else { - removeOverlayView(false); - } - } - }); - } - - /** - * Called when the session is released. - */ - public abstract void onRelease(); - - /** - * Sets the {@link Surface} for the current input session on which the TV input renders - * video. - * - * @param surface {@link Surface} an application passes to this TV input session. - * @return {@code true} if the surface was set, {@code false} otherwise. - */ - public abstract boolean onSetSurface(Surface surface); - - /** - * Sets the relative volume of the current TV input session to handle the change of audio - * focus by setting. - * - * @param volume Volume scale from 0.0 to 1.0. - */ - public abstract void onSetVolume(float volume); - - /** - * Tunes to a given channel. - * - * @param channelUri The URI of the channel. - * @return {@code true} the tuning was successful, {@code false} otherwise. - */ - public abstract boolean onTune(Uri channelUri); - - /** - * Called when an application requests to create an overlay view. Each session - * implementation can override this method and return its own view. - * - * @return a view attached to the overlay window - */ - public View onCreateOverlayView() { - return null; - } - - /** - * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent) - * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event). - * <p> - * Override this to intercept key down events before they are processed by the application. - * If you return true, the application will not process the event itself. If you return - * false, the normal application processing will occur as if the TV input had not seen the - * event at all. - * - * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - */ - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return false; - } - - /** - * Default implementation of - * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent) - * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event). - * <p> - * Override this to intercept key long press events before they are processed by the - * application. If you return true, the application will not process the event itself. If - * you return false, the normal application processing will occur as if the TV input had not - * seen the event at all. - * - * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - */ - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - return false; - } - - /** - * Default implementation of - * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) - * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event). - * <p> - * Override this to intercept special key multiple events before they are processed by the - * application. If you return true, the application will not itself process the event. If - * you return false, the normal application processing will occur as if the TV input had not - * seen the event at all. - * - * @param keyCode The value in event.getKeyCode(). - * @param count The number of times the action was made. - * @param event Description of the key event. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - */ - @Override - public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { - return false; - } - - /** - * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent) - * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event). - * <p> - * Override this to intercept key up events before they are processed by the application. If - * you return true, the application will not itself process the event. If you return false, - * the normal application processing will occur as if the TV input had not seen the event at - * all. - * - * @param keyCode The value in event.getKeyCode(). - * @param event Description of the key event. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - */ - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return false; - } - - /** - * Implement this method to handle touch screen motion events on the current input session. - * - * @param event The motion event being received. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - * @see View#onTouchEvent - */ - public boolean onTouchEvent(MotionEvent event) { - return false; - } - - /** - * Implement this method to handle trackball events on the current input session. - * - * @param event The motion event being received. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - * @see View#onTrackballEvent - */ - public boolean onTrackballEvent(MotionEvent event) { - return false; - } - - /** - * Implement this method to handle generic motion events on the current input session. - * - * @param event The motion event being received. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - * @see View#onGenericMotionEvent - */ - public boolean onGenericMotionEvent(MotionEvent event) { - return false; - } - - /** - * This method is called when the application would like to stop using the current input - * session. - */ - void release() { - onRelease(); - if (mSurface != null) { - mSurface.release(); - mSurface = null; - } - removeOverlayView(true); - } - - /** - * Calls {@link #onSetSurface}. - */ - void setSurface(Surface surface) { - onSetSurface(surface); - if (mSurface != null) { - mSurface.release(); - } - mSurface = surface; - // TODO: Handle failure. - } - - /** - * Calls {@link #onSetVolume}. - */ - void setVolume(float volume) { - onSetVolume(volume); - } - - /** - * Calls {@link #onTune}. - */ - void tune(Uri channelUri) { - onTune(channelUri); - // TODO: Handle failure. - } - - /** - * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach - * to the overlay window. - * - * @param windowToken A window token of an application. - * @param frame A position of the overlay view. - */ - void createOverlayView(IBinder windowToken, Rect frame) { - if (mOverlayView != null) { - mWindowManager.removeView(mOverlayView); - mOverlayView = null; - } - if (DEBUG) { - Log.d(TAG, "create overlay view(" + frame + ")"); - } - mWindowToken = windowToken; - mOverlayFrame = frame; - if (!mOverlayViewEnabled) { - return; - } - mOverlayView = onCreateOverlayView(); - if (mOverlayView == null) { - return; - } - // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create - // an overlay window above the media window but below the application window. - int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; - // We make the overlay view non-focusable and non-touchable so that - // the application that owns the window token can decide whether to consume or - // dispatch the input events. - int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - mWindowParams = new WindowManager.LayoutParams( - frame.right - frame.left, frame.bottom - frame.top, - frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT); - mWindowParams.privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; - mWindowParams.gravity = Gravity.START | Gravity.TOP; - mWindowParams.token = windowToken; - mWindowManager.addView(mOverlayView, mWindowParams); - } - - /** - * Relayouts the current overlay view. - * - * @param frame A new position of the overlay view. - */ - void relayoutOverlayView(Rect frame) { - if (DEBUG) { - Log.d(TAG, "relayout overlay view(" + frame + ")"); - } - mOverlayFrame = frame; - if (!mOverlayViewEnabled || mOverlayView == null) { - return; - } - mWindowParams.x = frame.left; - mWindowParams.y = frame.top; - mWindowParams.width = frame.right - frame.left; - mWindowParams.height = frame.bottom - frame.top; - mWindowManager.updateViewLayout(mOverlayView, mWindowParams); - } - - /** - * Removes the current overlay view. - */ - void removeOverlayView(boolean clearWindowToken) { - if (DEBUG) { - Log.d(TAG, "remove overlay view(" + mOverlayView + ")"); - } - if (clearWindowToken) { - mWindowToken = null; - mOverlayFrame = null; - } - if (mOverlayView != null) { - mWindowManager.removeView(mOverlayView); - mOverlayView = null; - mWindowParams = null; - } - } - - /** - * Takes care of dispatching incoming input events and tells whether the event was handled. - */ - int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { - if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); - if (event instanceof KeyEvent) { - if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) { - return Session.DISPATCH_HANDLED; - } - } else if (event instanceof MotionEvent) { - MotionEvent motionEvent = (MotionEvent) event; - final int source = motionEvent.getSource(); - if (motionEvent.isTouchEvent()) { - if (onTouchEvent(motionEvent)) { - return Session.DISPATCH_HANDLED; - } - } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - if (onTrackballEvent(motionEvent)) { - return Session.DISPATCH_HANDLED; - } - } else { - if (onGenericMotionEvent(motionEvent)) { - return Session.DISPATCH_HANDLED; - } - } - } - if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) { - return Session.DISPATCH_NOT_HANDLED; - } - if (!mOverlayView.hasWindowFocus()) { - mOverlayView.getViewRootImpl().windowFocusChanged(true, true); - } - mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver); - return Session.DISPATCH_IN_PROGRESS; - } - } - - private final class ServiceHandler extends Handler { - private static final int DO_CREATE_SESSION = 1; - private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2; - - @Override - public final void handleMessage(Message msg) { - switch (msg.what) { - case DO_CREATE_SESSION: { - SomeArgs args = (SomeArgs) msg.obj; - InputChannel channel = (InputChannel) args.arg1; - ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; - try { - TvInputSessionImpl sessionImpl = onCreateSession(); - if (sessionImpl == null) { - // Failed to create a session. - cb.onSessionCreated(null); - } else { - ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, - sessionImpl, channel); - cb.onSessionCreated(stub); - } - } catch (RemoteException e) { - Log.e(TAG, "error in onSessionCreated"); - } - args.recycle(); - return; - } - case DO_BROADCAST_AVAILABILITY_CHANGE: { - boolean isAvailable = (Boolean) msg.obj; - int n = mCallbacks.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable); - } - } catch (RemoteException e) { - Log.e(TAG, "Unexpected exception", e); - } finally { - mCallbacks.finishBroadcast(); - } - return; - } - default: { - Log.w(TAG, "Unhandled message code: " + msg.what); - return; - } - } - } - } -} diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java deleted file mode 100644 index 59b6386..0000000 --- a/core/java/android/tv/TvView.java +++ /dev/null @@ -1,383 +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.tv; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.text.TextUtils; -import android.tv.TvInputManager.Session; -import android.tv.TvInputManager.Session.FinishedInputEventCallback; -import android.tv.TvInputManager.SessionCallback; -import android.util.AttributeSet; -import android.util.Log; -import android.view.InputEvent; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.ViewRootImpl; - -/** - * View playing TV - */ -public class TvView extends SurfaceView { - // STOPSHIP: Turn debugging off. - private static final boolean DEBUG = true; - private static final String TAG = "TvView"; - - private final Handler mHandler = new Handler(); - private TvInputManager.Session mSession; - private Surface mSurface; - private boolean mOverlayViewCreated; - private Rect mOverlayViewFrame; - private final TvInputManager mTvInputManager; - private SessionCallback mSessionCallback; - private OnUnhandledInputEventListener mOnUnhandledInputEventListener; - - private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width - + ", height=" + height + ")"); - if (holder.getSurface() == mSurface) { - return; - } - mSurface = holder.getSurface(); - setSessionSurface(mSurface); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - mSurface = holder.getSurface(); - setSessionSurface(mSurface); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - mSurface = null; - setSessionSurface(null); - } - }; - - private final FinishedInputEventCallback mFinishedInputEventCallback = - new FinishedInputEventCallback() { - @Override - public void onFinishedInputEvent(Object token, boolean handled) { - if (DEBUG) { - Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")"); - } - if (handled) { - return; - } - // TODO: Re-order unhandled events. - InputEvent event = (InputEvent) token; - if (dispatchUnhandledInputEvent(event)) { - return; - } - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - viewRootImpl.dispatchUnhandledInputEvent(event); - } - } - }; - - public TvView(Context context) { - this(context, null, 0); - } - - public TvView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TvView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - getHolder().addCallback(mSurfaceHolderCallback); - mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); - } - - /** - * Binds a TV input to this view. {@link SessionCallback#onSessionCreated} will be - * called to send the result of this binding with {@link TvInputManager.Session}. - * If a TV input is already bound, the input will be unbound from this view and its session - * will be released. - * - * @param inputId the id of TV input which will be bound to this view. - * @param callback called when TV input is bound. The callback sends - * {@link TvInputManager.Session} - * @throws IllegalArgumentException if any of the arguments is {@code null}. - */ - public void bindTvInput(String inputId, SessionCallback callback) { - if (TextUtils.isEmpty(inputId)) { - throw new IllegalArgumentException("inputId cannot be null or an empty string"); - } - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - if (mSession != null) { - release(); - } - // When bindTvInput is called multiple times before the callback is called, - // only the callback of the last bindTvInput call will be actually called back. - // The previous callbacks will be ignored. For the logic, mSessionCallback - // is newly assigned for every bindTvInput call and compared with - // MySessionCreateCallback.this. - mSessionCallback = new MySessionCallback(callback); - mTvInputManager.createSession(inputId, mSessionCallback, mHandler); - } - - /** - * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session} - * is released. - */ - public void unbindTvInput() { - if (mSession != null) { - release(); - } - mSessionCallback = null; - } - - /** - * Dispatches an unhandled input event to the next receiver. - * <p> - * Except system keys, TvView always consumes input events in the normal flow. This is called - * asynchronously from where the event is dispatched. It gives the host application a chance to - * dispatch the unhandled input events. - * - * @param event The input event. - * @return {@code true} if the event was handled by the view, {@code false} otherwise. - */ - public boolean dispatchUnhandledInputEvent(InputEvent event) { - if (mOnUnhandledInputEventListener != null) { - if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) { - return true; - } - } - return onUnhandledInputEvent(event); - } - - /** - * Called when an unhandled input event was also not handled by the user provided callback. This - * is the last chance to handle the unhandled input event in the TvView. - * - * @param event The input event. - * @return If you handled the event, return {@code true}. If you want to allow the event to be - * handled by the next receiver, return {@code false}. - */ - public boolean onUnhandledInputEvent(InputEvent event) { - return false; - } - - /** - * Registers a callback to be invoked when an input event was not handled by the bound TV input. - * - * @param listener The callback to invoke when the unhandled input event was received. - */ - public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) { - mOnUnhandledInputEventListener = listener; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (super.dispatchKeyEvent(event)) { - return true; - } - if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); - if (mSession == null) { - return false; - } - InputEvent copiedEvent = event.copy(); - int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, - mHandler); - return ret != Session.DISPATCH_NOT_HANDLED; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - if (super.dispatchTouchEvent(event)) { - return true; - } - if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")"); - if (mSession == null) { - return false; - } - InputEvent copiedEvent = event.copy(); - int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, - mHandler); - return ret != Session.DISPATCH_NOT_HANDLED; - } - - @Override - public boolean dispatchTrackballEvent(MotionEvent event) { - if (super.dispatchTrackballEvent(event)) { - return true; - } - if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")"); - if (mSession == null) { - return false; - } - InputEvent copiedEvent = event.copy(); - int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, - mHandler); - return ret != Session.DISPATCH_NOT_HANDLED; - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent event) { - if (super.dispatchGenericMotionEvent(event)) { - return true; - } - if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")"); - if (mSession == null) { - return false; - } - InputEvent copiedEvent = event.copy(); - int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, - mHandler); - return ret != Session.DISPATCH_NOT_HANDLED; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - createSessionOverlayView(); - } - - @Override - protected void onDetachedFromWindow() { - removeSessionOverlayView(); - super.onDetachedFromWindow(); - } - - /** @hide */ - @Override - protected void updateWindow(boolean force, boolean redrawNeeded) { - super.updateWindow(force, redrawNeeded); - relayoutSessionOverlayView(); - } - - private void release() { - setSessionSurface(null); - removeSessionOverlayView(); - mSession.release(); - mSession = null; - } - - private void setSessionSurface(Surface surface) { - if (mSession == null) { - return; - } - mSession.setSurface(surface); - } - - private void createSessionOverlayView() { - if (mSession == null || !isAttachedToWindow() - || mOverlayViewCreated) { - return; - } - mOverlayViewFrame = getViewFrameOnScreen(); - mSession.createOverlayView(this, mOverlayViewFrame); - mOverlayViewCreated = true; - } - - private void removeSessionOverlayView() { - if (mSession == null || !mOverlayViewCreated) { - return; - } - mSession.removeOverlayView(); - mOverlayViewCreated = false; - mOverlayViewFrame = null; - } - - private void relayoutSessionOverlayView() { - if (mSession == null || !isAttachedToWindow() - || !mOverlayViewCreated) { - return; - } - Rect viewFrame = getViewFrameOnScreen(); - if (viewFrame.equals(mOverlayViewFrame)) { - return; - } - mSession.relayoutOverlayView(viewFrame); - mOverlayViewFrame = viewFrame; - } - - private Rect getViewFrameOnScreen() { - int[] location = new int[2]; - getLocationOnScreen(location); - return new Rect(location[0], location[1], - location[0] + getWidth(), location[1] + getHeight()); - } - - /** - * Interface definition for a callback to be invoked when the unhandled input event is received. - */ - public interface OnUnhandledInputEventListener { - /** - * Called when an input event was not handled by the bound TV input. - * <p> - * This is called asynchronously from where the event is dispatched. It gives the host - * application a chance to handle the unhandled input events. - * - * @param event The input event. - * @return If you handled the event, return {@code true}. If you want to allow the event to - * be handled by the next receiver, return {@code false}. - */ - boolean onUnhandledInputEvent(InputEvent event); - } - - private class MySessionCallback extends SessionCallback { - final SessionCallback mExternalCallback; - - MySessionCallback(SessionCallback externalCallback) { - mExternalCallback = externalCallback; - } - - @Override - public void onSessionCreated(Session session) { - if (this != mSessionCallback) { - // This callback is obsolete. - if (session != null) { - session.release(); - } - return; - } - mSession = session; - if (session != null) { - // mSurface may not be ready yet as soon as starting an application. - // In the case, we don't send Session.setSurface(null) unnecessarily. - // setSessionSurface will be called in surfaceCreated. - if (mSurface != null) { - setSessionSurface(mSurface); - } - createSessionOverlayView(); - } - if (mExternalCallback != null) { - mExternalCallback.onSessionCreated(session); - } - } - - @Override - public void onSessionReleased(Session session) { - mSession = null; - if (mExternalCallback != null) { - mExternalCallback.onSessionReleased(session); - } - } - } -} diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 424d860..5056097 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -75,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas { // Constructors /////////////////////////////////////////////////////////////////////////// - /** - * Creates a canvas to render directly on screen. - */ - GLES20Canvas(boolean translucent) { - this(false, translucent); - } - - protected GLES20Canvas(boolean record, boolean translucent) { - mOpaque = !translucent; - - if (record) { - mRenderer = nCreateDisplayListRenderer(); - } else { - mRenderer = nCreateRenderer(); - } - + // TODO: Merge with GLES20RecordingCanvas + protected GLES20Canvas() { + mOpaque = false; + mRenderer = nCreateDisplayListRenderer(); setupFinalizer(); } @@ -102,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas { } } - private static native long nCreateRenderer(); private static native long nCreateDisplayListRenderer(); private static native void nResetDisplayListRenderer(long renderer); private static native void nDestroyRenderer(long renderer); @@ -131,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetProperty(String name, String value); /////////////////////////////////////////////////////////////////////////// - // Hardware layers - /////////////////////////////////////////////////////////////////////////// - - @Override - void pushLayerUpdate(HardwareLayer layer) { - nPushLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void cancelLayerUpdate(HardwareLayer layer) { - nCancelLayerUpdate(mRenderer, layer.getLayer()); - } - - @Override - void flushLayerUpdates() { - nFlushLayerUpdates(mRenderer); - } - - @Override - void clearLayerUpdates() { - nClearLayerUpdates(mRenderer); - } - - static native boolean nCopyLayer(long layerId, long bitmap); - private static native void nClearLayerUpdates(long renderer); - private static native void nFlushLayerUpdates(long renderer); - private static native void nPushLayerUpdate(long renderer, long layer); - private static native void nCancelLayerUpdate(long renderer, long layer); - - /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// @@ -234,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nFinish(long renderer); - /** - * Returns the size of the stencil buffer required by the underlying - * implementation. - * - * @return The minimum number of bits the stencil buffer must. Always >= 0. - * - * @hide - */ - public static int getStencilSize() { - return nGetStencilSize(); - } - - private static native int nGetStencilSize(); - /////////////////////////////////////////////////////////////////////////// // Functor /////////////////////////////////////////////////////////////////////////// @@ -284,49 +227,6 @@ class GLES20Canvas extends HardwareCanvas { */ static final int FLUSH_CACHES_FULL = 2; - /** - * Flush caches to reclaim as much memory as possible. The amount of memory - * to reclaim is indicate by the level parameter. - * - * The level can be one of {@link #FLUSH_CACHES_MODERATE} or - * {@link #FLUSH_CACHES_FULL}. - * - * @param level Hint about the amount of memory to reclaim - */ - static void flushCaches(int level) { - nFlushCaches(level); - } - - private static native void nFlushCaches(int level); - - /** - * Release all resources associated with the underlying caches. This should - * only be called after a full flushCaches(). - * - * @hide - */ - static void terminateCaches() { - nTerminateCaches(); - } - - private static native void nTerminateCaches(); - - static boolean initCaches() { - return nInitCaches(); - } - - private static native boolean nInitCaches(); - - /////////////////////////////////////////////////////////////////////////// - // Atlas - /////////////////////////////////////////////////////////////////////////// - - static void initAtlas(GraphicBuffer buffer, long[] map) { - nInitAtlas(buffer, map, map.length); - } - - private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count); - /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -899,12 +799,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPath(long renderer, long path, long paint); private static native void nDrawRects(long renderer, long region, long paint); - void drawRects(float[] rects, int count, Paint paint) { - nDrawRects(mRenderer, rects, count, paint.mNativePaint); - } - - private static native void nDrawRects(long renderer, float[] rects, int count, long paint); - @Override public void drawPicture(Picture picture) { if (picture.createdFromStream) { diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index a94ec3a..b2961e5 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { RenderNode mNode; private GLES20RecordingCanvas() { - super(true, true); + super(); } static GLES20RecordingCanvas obtain(@NonNull RenderNode node) { diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java deleted file mode 100644 index 6dd7c00..0000000 --- a/core/java/android/view/GLRenderer.java +++ /dev/null @@ -1,1534 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW; -import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; -import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY; -import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_DRAW; -import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT; -import static javax.microedition.khronos.egl.EGL10.EGL_NONE; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY; -import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE; -import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; -import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; -import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; -import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; -import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS; -import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; -import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH; -import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; - -import android.content.ComponentCallbacks2; -import android.graphics.Bitmap; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; -import android.opengl.EGL14; -import android.opengl.GLUtils; -import android.opengl.ManagedEGLContext; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.Trace; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Surface.OutOfResourcesException; - -import com.google.android.gles_jni.EGLImpl; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; - -/** - * Hardware renderer using OpenGL - * - * @hide - */ -public class GLRenderer extends HardwareRenderer { - static final int SURFACE_STATE_ERROR = 0; - static final int SURFACE_STATE_SUCCESS = 1; - static final int SURFACE_STATE_UPDATED = 2; - - static final int FUNCTOR_PROCESS_DELAY = 4; - - /** - * Number of frames to profile. - */ - private static final int PROFILE_MAX_FRAMES = 128; - - /** - * Number of floats per profiled frame. - */ - private static final int PROFILE_FRAME_DATA_COUNT = 3; - - private static final int PROFILE_DRAW_MARGIN = 0; - private static final int PROFILE_DRAW_WIDTH = 3; - private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 }; - private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d; - private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; - private static final int PROFILE_DRAW_DP_PER_MS = 7; - - private static final String[] VISUALIZERS = { - PROFILE_PROPERTY_VISUALIZE_BARS, - PROFILE_PROPERTY_VISUALIZE_LINES - }; - - private static final String[] OVERDRAW = { - OVERDRAW_PROPERTY_SHOW, - }; - private static final int GL_VERSION = 2; - - static EGL10 sEgl; - static EGLDisplay sEglDisplay; - static EGLConfig sEglConfig; - static final Object[] sEglLock = new Object[0]; - int mWidth = -1, mHeight = -1; - - static final ThreadLocal<ManagedEGLContext> sEglContextStorage - = new ThreadLocal<ManagedEGLContext>(); - - EGLContext mEglContext; - Thread mEglThread; - - EGLSurface mEglSurface; - - GL mGl; - HardwareCanvas mCanvas; - - String mName; - - long mFrameCount; - Paint mDebugPaint; - - static boolean sDirtyRegions; - static final boolean sDirtyRegionsRequested; - static { - String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true"); - //noinspection PointlessBooleanExpression,ConstantConditions - sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty); - sDirtyRegionsRequested = sDirtyRegions; - } - - boolean mDirtyRegionsEnabled; - boolean mUpdateDirtyRegions; - - boolean mProfileEnabled; - int mProfileVisualizerType = -1; - float[] mProfileData; - ReentrantLock mProfileLock; - int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - - GraphDataProvider mDebugDataProvider; - float[][] mProfileShapes; - Paint mProfilePaint; - - boolean mDebugDirtyRegions; - int mDebugOverdraw = -1; - - final boolean mTranslucent; - - private boolean mDestroyed; - - private final Rect mRedrawClip = new Rect(); - - private final int[] mSurfaceSize = new int[2]; - - private long mDrawDelta = Long.MAX_VALUE; - - private GLES20Canvas mGlCanvas; - - private DisplayMetrics mDisplayMetrics; - - private static EGLSurface sPbuffer; - private static final Object[] sPbufferLock = new Object[0]; - - private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>(); - - private static class GLRendererEglContext extends ManagedEGLContext { - final Handler mHandler = new Handler(); - - public GLRendererEglContext(EGLContext context) { - super(context); - } - - @Override - public void onTerminate(final EGLContext eglContext) { - // Make sure we do this on the correct thread. - if (mHandler.getLooper() != Looper.myLooper()) { - mHandler.post(new Runnable() { - @Override - public void run() { - onTerminate(eglContext); - } - }); - return; - } - - synchronized (sEglLock) { - if (sEgl == null) return; - - if (EGLImpl.getInitCount(sEglDisplay) == 1) { - usePbufferSurface(eglContext); - GLES20Canvas.terminateCaches(); - - sEgl.eglDestroyContext(sEglDisplay, eglContext); - sEglContextStorage.set(null); - sEglContextStorage.remove(); - - sEgl.eglDestroySurface(sEglDisplay, sPbuffer); - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - - sEgl.eglReleaseThread(); - sEgl.eglTerminate(sEglDisplay); - - sEgl = null; - sEglDisplay = null; - sEglConfig = null; - sPbuffer = null; - } - } - } - } - - HardwareCanvas createCanvas() { - return mGlCanvas = new GLES20Canvas(mTranslucent); - } - - ManagedEGLContext createManagedContext(EGLContext eglContext) { - return new GLRendererEglContext(mEglContext); - } - - int[] getConfig(boolean dirtyRegions) { - //noinspection PointlessBooleanExpression,ConstantConditions - final int stencilSize = GLES20Canvas.getStencilSize(); - final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; - - return new int[] { - EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 0, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_STENCIL_SIZE, stencilSize, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, - EGL_NONE - }; - } - - void initCaches() { - if (GLES20Canvas.initCaches()) { - // Caches were (re)initialized, rebind atlas - initAtlas(); - } - } - - void initAtlas() { - IBinder binder = ServiceManager.getService("assetatlas"); - if (binder == null) return; - - IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); - try { - if (atlas.isCompatible(android.os.Process.myPpid())) { - GraphicBuffer buffer = atlas.getBuffer(); - if (buffer != null) { - long[] map = atlas.getMap(); - if (map != null) { - GLES20Canvas.initAtlas(buffer, map); - } - // If IAssetAtlas is not the same class as the IBinder - // we are using a remote service and we can safely - // destroy the graphic buffer - if (atlas.getClass() != binder.getClass()) { - buffer.destroy(); - } - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Could not acquire atlas", e); - } - } - - boolean canDraw() { - return mGl != null && mCanvas != null && mGlCanvas != null; - } - - int onPreDraw(Rect dirty) { - return mGlCanvas.onPreDraw(dirty); - } - - void onPostDraw() { - mGlCanvas.onPostDraw(); - } - - void drawProfileData(View.AttachInfo attachInfo) { - if (mDebugDataProvider != null) { - final GraphDataProvider provider = mDebugDataProvider; - initProfileDrawData(attachInfo, provider); - - final int height = provider.getVerticalUnitSize(); - final int margin = provider.getHorizontaUnitMargin(); - final int width = provider.getHorizontalUnitSize(); - - int x = 0; - int count = 0; - int current = 0; - - final float[] data = provider.getData(); - final int elementCount = provider.getElementCount(); - final int graphType = provider.getGraphType(); - - int totalCount = provider.getFrameCount() * elementCount; - if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) { - totalCount -= elementCount; - } - - for (int i = 0; i < totalCount; i += elementCount) { - if (data[i] < 0.0f) break; - - int index = count * 4; - if (i == provider.getCurrentFrame() * elementCount) current = index; - - x += margin; - int x2 = x + width; - - int y2 = mHeight; - int y1 = (int) (y2 - data[i] * height); - - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = x; - r[index + 1] = y1; - r[index + 2] = x2; - r[index + 3] = y2; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - case GraphDataProvider.GRAPH_TYPE_LINES: { - for (int j = 0; j < elementCount; j++) { - //noinspection MismatchedReadAndWriteOfArray - final float[] r = mProfileShapes[j]; - r[index] = (x + x2) * 0.5f; - r[index + 1] = index == 0 ? y1 : r[index - 1]; - r[index + 2] = r[index] + width; - r[index + 3] = y1; - - y2 = y1; - if (j < elementCount - 1) { - y1 = (int) (y2 - data[i + j + 1] * height); - } - } - } break; - } - - - x += width; - count++; - } - - x += margin; - - drawGraph(graphType, count); - drawCurrentFrame(graphType, current); - drawThreshold(x, height); - } - } - - private void drawGraph(int graphType, int count) { - for (int i = 0; i < mProfileShapes.length; i++) { - mDebugDataProvider.setupGraphPaint(mProfilePaint, i); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint); - break; - } - } - } - - private void drawCurrentFrame(int graphType, int index) { - if (index >= 0) { - mDebugDataProvider.setupCurrentFramePaint(mProfilePaint); - switch (graphType) { - case GraphDataProvider.GRAPH_TYPE_BARS: - mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index + 2], mProfileShapes[0][index + 3], - mProfilePaint); - break; - case GraphDataProvider.GRAPH_TYPE_LINES: - mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1], - mProfileShapes[2][index], mHeight, mProfilePaint); - break; - } - } - } - - private void drawThreshold(int x, int height) { - float threshold = mDebugDataProvider.getThreshold(); - if (threshold > 0.0f) { - mDebugDataProvider.setupThresholdPaint(mProfilePaint); - int y = (int) (mHeight - threshold * height); - mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint); - } - } - - private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) { - if (mProfileShapes == null) { - final int elementCount = provider.getElementCount(); - final int frameCount = provider.getFrameCount(); - - mProfileShapes = new float[elementCount][]; - for (int i = 0; i < elementCount; i++) { - mProfileShapes[i] = new float[frameCount * 4]; - } - - mProfilePaint = new Paint(); - } - - mProfilePaint.reset(); - if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) { - mProfilePaint.setAntiAlias(true); - } - - if (mDisplayMetrics == null) { - mDisplayMetrics = new DisplayMetrics(); - } - - attachInfo.mDisplay.getMetrics(mDisplayMetrics); - provider.prepare(mDisplayMetrics); - } - - @Override - void destroy(boolean full) { - try { - if (full && mCanvas != null) { - mCanvas = null; - } - - if (!isEnabled() || mDestroyed) { - setEnabled(false); - return; - } - - destroySurface(); - setEnabled(false); - - mDestroyed = true; - mGl = null; - } finally { - if (full && mGlCanvas != null) { - mGlCanvas = null; - } - } - } - - @Override - void pushLayerUpdate(HardwareLayer layer) { - mGlCanvas.pushLayerUpdate(layer); - } - - @Override - void flushLayerUpdates() { - if (validate()) { - flushLayerChanges(); - mGlCanvas.flushLayerUpdates(); - } - } - - @Override - HardwareLayer createTextureLayer() { - validate(); - return HardwareLayer.createTextureLayer(this); - } - - @Override - public HardwareLayer createDisplayListLayer(int width, int height) { - validate(); - return HardwareLayer.createDisplayListLayer(this, width, height); - } - - @Override - void onLayerCreated(HardwareLayer hardwareLayer) { - mAttachedLayers.add(hardwareLayer); - } - - boolean hasContext() { - return sEgl != null && mEglContext != null - && mEglContext.equals(sEgl.eglGetCurrentContext()); - } - - @Override - void onLayerDestroyed(HardwareLayer layer) { - if (mGlCanvas != null) { - mGlCanvas.cancelLayerUpdate(layer); - } - if (hasContext()) { - long backingLayer = layer.detachBackingLayer(); - nDestroyLayer(backingLayer); - } - mAttachedLayers.remove(layer); - } - - @Override - public SurfaceTexture createSurfaceTexture(HardwareLayer layer) { - return layer.createSurfaceTexture(); - } - - @Override - boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) { - if (!validate()) { - throw new IllegalStateException("Could not acquire hardware rendering context"); - } - layer.flushChanges(); - return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap); - } - - @Override - boolean safelyRun(Runnable action) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - - if (needsContext) { - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - if (managedContext == null) return false; - usePbufferSurface(managedContext.getContext()); - } - - try { - action.run(); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - return true; - } - - @Override - void invokeFunctor(long functor, boolean waitForCompletion) { - boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR; - boolean hasContext = !needsContext; - - if (needsContext) { - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - if (managedContext != null) { - usePbufferSurface(managedContext.getContext()); - hasContext = true; - } - } - - try { - nInvokeFunctor(functor, hasContext); - } finally { - if (needsContext) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - } - - private static native void nInvokeFunctor(long functor, boolean hasContext); - - @Override - void destroyHardwareResources(final View view) { - if (view != null) { - safelyRun(new Runnable() { - @Override - public void run() { - if (mCanvas != null) { - mCanvas.clearLayerUpdates(); - } - destroyResources(view); - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); - } - }); - } - } - - private static void destroyResources(View view) { - view.destroyHardwareResources(); - - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - - int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - destroyResources(group.getChildAt(i)); - } - } - } - - static void startTrimMemory(int level) { - if (sEgl == null || sEglConfig == null) return; - - GLRendererEglContext managedContext = - (GLRendererEglContext) sEglContextStorage.get(); - // We do not have OpenGL objects - if (managedContext == null) { - return; - } else { - usePbufferSurface(managedContext.getContext()); - } - - if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL); - } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE); - } - } - - static void endTrimMemory() { - if (sEgl != null && sEglDisplay != null) { - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - } - - private static void usePbufferSurface(EGLContext eglContext) { - synchronized (sPbufferLock) { - // Create a temporary 1x1 pbuffer so we have a context - // to clear our OpenGL objects - if (sPbuffer == null) { - sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { - EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE - }); - } - } - sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); - } - - GLRenderer(boolean translucent) { - mTranslucent = translucent; - - loadSystemProperties(); - } - - @Override - void setOpaque(boolean opaque) { - // Not supported - } - - @Override - boolean loadSystemProperties() { - boolean value; - boolean changed = false; - - String profiling = SystemProperties.get(PROFILE_PROPERTY); - int graphType = search(VISUALIZERS, profiling); - value = graphType >= 0; - - if (graphType != mProfileVisualizerType) { - changed = true; - mProfileVisualizerType = graphType; - - mProfileShapes = null; - mProfilePaint = null; - - if (value) { - mDebugDataProvider = new DrawPerformanceDataProvider(graphType); - } else { - mDebugDataProvider = null; - } - } - - // If on-screen profiling is not enabled, we need to check whether - // console profiling only is enabled - if (!value) { - value = Boolean.parseBoolean(profiling); - } - - if (value != mProfileEnabled) { - changed = true; - mProfileEnabled = value; - - if (mProfileEnabled) { - Log.d(LOG_TAG, "Profiling hardware renderer"); - - int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY, - PROFILE_MAX_FRAMES); - mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - - mProfileLock = new ReentrantLock(); - } else { - mProfileData = null; - mProfileLock = null; - mProfileVisualizerType = -1; - } - - mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT; - } - - value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false); - if (value != mDebugDirtyRegions) { - changed = true; - mDebugDirtyRegions = value; - - if (mDebugDirtyRegions) { - Log.d(LOG_TAG, "Debugging dirty regions"); - } - } - - String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY); - int debugOverdraw = search(OVERDRAW, overdraw); - if (debugOverdraw != mDebugOverdraw) { - changed = true; - mDebugOverdraw = debugOverdraw; - } - - if (loadProperties()) { - changed = true; - } - - return changed; - } - - private static int search(String[] values, String value) { - for (int i = 0; i < values.length; i++) { - if (values[i].equals(value)) return i; - } - return -1; - } - - @Override - void dumpGfxInfo(PrintWriter pw) { - if (mProfileEnabled) { - pw.printf("\n\tDraw\tProcess\tExecute\n"); - - mProfileLock.lock(); - try { - for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { - if (mProfileData[i] < 0) { - break; - } - pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], - mProfileData[i + 2]); - mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; - } - mProfileCurrentFrame = mProfileData.length; - } finally { - mProfileLock.unlock(); - } - } - } - - @Override - long getFrameCount() { - return mFrameCount; - } - - /** - * Indicates whether this renderer instance can track and update dirty regions. - */ - boolean hasDirtyRegions() { - return mDirtyRegionsEnabled; - } - - /** - * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)} - * is invoked and the requested flag is turned off. The error code is - * also logged as a warning. - */ - void checkEglErrors() { - if (isEnabled()) { - checkEglErrorsForced(); - } - } - - private void checkEglErrorsForced() { - int error = sEgl.eglGetError(); - if (error != EGL_SUCCESS) { - // something bad has happened revert to - // normal rendering. - Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error)); - fallback(error != EGL11.EGL_CONTEXT_LOST); - } - } - - private void fallback(boolean fallback) { - destroy(true); - if (fallback) { - // we'll try again if it was context lost - setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " - + "Switching back to software rendering."); - } - } - - @Override - boolean initialize(Surface surface) throws OutOfResourcesException { - if (isRequested() && !isEnabled()) { - boolean contextCreated = initializeEgl(); - mGl = createEglSurface(surface); - mDestroyed = false; - - if (mGl != null) { - int err = sEgl.eglGetError(); - if (err != EGL_SUCCESS) { - destroy(true); - setRequested(false); - } else { - if (mCanvas == null) { - mCanvas = createCanvas(); - } - setEnabled(true); - - if (contextCreated) { - initAtlas(); - } - } - - return mCanvas != null; - } - } - return false; - } - - @Override - void updateSurface(Surface surface) throws OutOfResourcesException { - if (isRequested() && isEnabled()) { - createEglSurface(surface); - } - } - - @Override - void pauseSurface(Surface surface) { - // No-op - } - - boolean initializeEgl() { - synchronized (sEglLock) { - if (sEgl == null && sEglConfig == null) { - sEgl = (EGL10) EGLContext.getEGL(); - - // Get to the default display. - sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); - - if (sEglDisplay == EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed " - + GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - // We can now initialize EGL for that display - int[] version = new int[2]; - if (!sEgl.eglInitialize(sEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - checkEglErrorsForced(); - - sEglConfig = loadEglConfig(); - } - } - - ManagedEGLContext managedContext = sEglContextStorage.get(); - mEglContext = managedContext != null ? managedContext.getContext() : null; - mEglThread = Thread.currentThread(); - - if (mEglContext == null) { - mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); - sEglContextStorage.set(createManagedContext(mEglContext)); - return true; - } - - return false; - } - - private EGLConfig loadEglConfig() { - EGLConfig eglConfig = chooseEglConfig(); - if (eglConfig == null) { - // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without - if (sDirtyRegions) { - sDirtyRegions = false; - eglConfig = chooseEglConfig(); - if (eglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - } else { - throw new RuntimeException("eglConfig not initialized"); - } - } - return eglConfig; - } - - private EGLConfig chooseEglConfig() { - EGLConfig[] configs = new EGLConfig[1]; - int[] configsCount = new int[1]; - int[] configSpec = getConfig(sDirtyRegions); - - // Debug - final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, ""); - if ("all".equalsIgnoreCase(debug)) { - sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount); - - EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]]; - sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs, - configsCount[0], configsCount); - - for (EGLConfig config : debugConfigs) { - printConfig(config); - } - } - - if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) { - throw new IllegalArgumentException("eglChooseConfig failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } else if (configsCount[0] > 0) { - if ("choice".equalsIgnoreCase(debug)) { - printConfig(configs[0]); - } - return configs[0]; - } - - return null; - } - - private static void printConfig(EGLConfig config) { - int[] value = new int[1]; - - Log.d(LOG_TAG, "EGL configuration " + config + ":"); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value); - Log.d(LOG_TAG, " RED_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value); - Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value); - Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value); - Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value); - Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value); - Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value); - Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value); - Log.d(LOG_TAG, " SAMPLES = " + value[0]); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value); - Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0])); - - sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value); - Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0])); - } - - GL createEglSurface(Surface surface) throws OutOfResourcesException { - // Check preconditions. - if (sEgl == null) { - throw new RuntimeException("egl not initialized"); - } - if (sEglDisplay == null) { - throw new RuntimeException("eglDisplay not initialized"); - } - if (sEglConfig == null) { - throw new RuntimeException("eglConfig not initialized"); - } - if (Thread.currentThread() != mEglThread) { - throw new IllegalStateException("HardwareRenderer cannot be used " - + "from multiple threads"); - } - - // In case we need to destroy an existing surface - destroySurface(); - - // Create an EGL surface we can render into. - if (!createSurface(surface)) { - return null; - } - - initCaches(); - - return mEglContext.getGL(); - } - - private void enableDirtyRegions() { - // If mDirtyRegions is set, this means we have an EGL configuration - // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set - if (sDirtyRegions) { - if (!(mDirtyRegionsEnabled = preserveBackBuffer())) { - Log.w(LOG_TAG, "Backbuffer cannot be preserved"); - } - } else if (sDirtyRegionsRequested) { - // If mDirtyRegions is not set, our EGL configuration does not - // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default - // swap behavior might be EGL_BUFFER_PRESERVED, which means we - // want to set mDirtyRegions. We try to do this only if dirty - // regions were initially requested as part of the device - // configuration (see RENDER_DIRTY_REGIONS) - mDirtyRegionsEnabled = isBackBufferPreserved(); - } - } - - EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { - final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE }; - - EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, - attribs); - if (context == null || context == EGL_NO_CONTEXT) { - //noinspection ConstantConditions - throw new IllegalStateException( - "Could not create an EGL context. eglCreateContext failed with error: " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - return context; - } - - void destroySurface() { - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) { - sEgl.eglMakeCurrent(sEglDisplay, - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - } - } - - @Override - void invalidate(Surface surface) { - // Cancels any existing buffer to ensure we'll get a buffer - // of the right size before we call eglSwapBuffers - sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) { - sEgl.eglDestroySurface(sEglDisplay, mEglSurface); - mEglSurface = null; - setEnabled(false); - } - - if (surface.isValid()) { - if (!createSurface(surface)) { - return; - } - - mUpdateDirtyRegions = true; - - if (mCanvas != null) { - setEnabled(true); - } - } - } - - private boolean createSurface(Surface surface) { - mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null); - - if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { - int error = sEgl.eglGetError(); - if (error == EGL_BAD_NATIVE_WINDOW) { - Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - return false; - } - throw new RuntimeException("createWindowSurface failed " - + GLUtils.getEGLErrorString(error)); - } - - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new IllegalStateException("eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - } - - enableDirtyRegions(); - - return true; - } - - boolean validate() { - return checkRenderContext() != SURFACE_STATE_ERROR; - } - - @Override - void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) { - if (validate()) { - mCanvas.setViewport(width, height); - mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius); - mWidth = width; - mHeight = height; - } - } - - @Override - int getWidth() { - return mWidth; - } - - @Override - int getHeight() { - return mHeight; - } - - @Override - void setName(String name) { - mName = name; - } - - @Override - void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, - Rect dirty) { - if (canDraw()) { - if (!hasDirtyRegions()) { - dirty = null; - } - attachInfo.mIgnoreDirtyState = true; - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); - - view.mPrivateFlags |= View.PFLAG_DRAWN; - - // We are already on the correct thread - final int surfaceState = checkRenderContextUnsafe(); - if (surfaceState != SURFACE_STATE_ERROR) { - HardwareCanvas canvas = mCanvas; - - if (mProfileEnabled) { - mProfileLock.lock(); - } - - dirty = beginFrame(canvas, dirty, surfaceState); - - RenderNode displayList = buildDisplayList(view, canvas); - - flushLayerChanges(); - - // buildDisplayList() calls into user code which can cause - // an eglMakeCurrent to happen with a different surface/context. - // We must therefore check again here. - if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) { - return; - } - - int saveCount = 0; - int status = RenderNode.STATUS_DONE; - - long start = getSystemTime(); - try { - status = prepareFrame(dirty); - - saveCount = canvas.save(); - callbacks.onHardwarePreDraw(canvas); - - if (displayList != null) { - status |= drawDisplayList(canvas, displayList, status); - } else { - // Shouldn't reach here - view.draw(canvas); - } - } catch (Exception e) { - Log.e(LOG_TAG, "An error has occurred while drawing:", e); - } finally { - callbacks.onHardwarePostDraw(canvas); - canvas.restoreToCount(saveCount); - view.mRecreateDisplayList = false; - - mDrawDelta = getSystemTime() - start; - - if (mDrawDelta > 0) { - mFrameCount++; - - debugDirtyRegions(dirty, canvas); - drawProfileData(attachInfo); - } - } - - onPostDraw(); - - swapBuffers(status); - - if (mProfileEnabled) { - mProfileLock.unlock(); - } - - attachInfo.mIgnoreDirtyState = false; - } - } - } - - private void flushLayerChanges() { - // Loop through and apply any pending layer changes - for (int i = 0; i < mAttachedLayers.size(); i++) { - HardwareLayer layer = mAttachedLayers.get(i); - layer.flushChanges(); - if (!layer.isValid()) { - // The layer was removed from mAttachedLayers, rewind i by 1 - // Note that this shouldn't actually happen as View.getHardwareLayer() - // is already flushing for error checking reasons - i--; - } - } - } - - @Override - void fence() { - // Everything is immediate, so this is a no-op - } - - private RenderNode buildDisplayList(View view, HardwareCanvas canvas) { - view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) - == View.PFLAG_INVALIDATED; - view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; - - long buildDisplayListStartTime = startBuildDisplayListProfiling(); - canvas.clearLayerUpdates(); - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList"); - RenderNode renderNode = view.getDisplayList(); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - - endBuildDisplayListProfiling(buildDisplayListStartTime); - - return renderNode; - } - - private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) { - // We had to change the current surface and/or context, redraw everything - if (surfaceState == SURFACE_STATE_UPDATED) { - dirty = null; - beginFrame(null); - } else { - int[] size = mSurfaceSize; - beginFrame(size); - - if (size[1] != mHeight || size[0] != mWidth) { - mWidth = size[0]; - mHeight = size[1]; - - canvas.setViewport(mWidth, mHeight); - - dirty = null; - } - } - - if (mDebugDataProvider != null) dirty = null; - - return dirty; - } - - private long startBuildDisplayListProfiling() { - if (mProfileEnabled) { - mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT; - if (mProfileCurrentFrame >= mProfileData.length) { - mProfileCurrentFrame = 0; - } - - return System.nanoTime(); - } - return 0; - } - - private void endBuildDisplayListProfiling(long getDisplayListStartTime) { - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - getDisplayListStartTime) * 0.000001f; - //noinspection PointlessArithmeticExpression - mProfileData[mProfileCurrentFrame] = total; - } - } - - private int prepareFrame(Rect dirty) { - int status; - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame"); - try { - status = onPreDraw(dirty); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - return status; - } - - private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList, - int status) { - - long drawDisplayListStartTime = 0; - if (mProfileEnabled) { - drawDisplayListStartTime = System.nanoTime(); - } - - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList"); - nPrepareTree(displayList.getNativeDisplayList()); - try { - status |= canvas.drawDisplayList(displayList, mRedrawClip, - RenderNode.FLAG_CLIP_CHILDREN); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - drawDisplayListStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 1] = total; - } - - return status; - } - - private void swapBuffers(int status) { - if ((status & RenderNode.STATUS_DREW) == RenderNode.STATUS_DREW) { - long eglSwapBuffersStartTime = 0; - if (mProfileEnabled) { - eglSwapBuffersStartTime = System.nanoTime(); - } - - sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); - - if (mProfileEnabled) { - long now = System.nanoTime(); - float total = (now - eglSwapBuffersStartTime) * 0.000001f; - mProfileData[mProfileCurrentFrame + 2] = total; - } - - checkEglErrors(); - } - } - - private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) { - if (mDebugDirtyRegions) { - if (mDebugPaint == null) { - mDebugPaint = new Paint(); - mDebugPaint.setColor(0x7fff0000); - } - - if (dirty != null && (mFrameCount & 1) == 0) { - canvas.drawRect(dirty, mDebugPaint); - } - } - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method throws an IllegalStateException if invoked from a thread - * that did not initialize EGL. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContextUnsafe() - */ - int checkRenderContext() { - if (mEglThread != Thread.currentThread()) { - throw new IllegalStateException("Hardware acceleration can only be used with a " + - "single UI thread.\nOriginal thread: " + mEglThread + "\n" + - "Current thread: " + Thread.currentThread()); - } - - return checkRenderContextUnsafe(); - } - - /** - * Ensures the current EGL context and surface are the ones we expect. - * This method does not check the current thread. - * - * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current, - * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or - * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one - * - * @see #checkRenderContext() - */ - private int checkRenderContextUnsafe() { - if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) || - !mEglContext.equals(sEgl.eglGetCurrentContext())) { - if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - Log.e(LOG_TAG, "eglMakeCurrent failed " + - GLUtils.getEGLErrorString(sEgl.eglGetError())); - fallback(true); - return SURFACE_STATE_ERROR; - } else { - if (mUpdateDirtyRegions) { - enableDirtyRegions(); - mUpdateDirtyRegions = false; - } - return SURFACE_STATE_UPDATED; - } - } - return SURFACE_STATE_SUCCESS; - } - - private static int dpToPx(int dp, float density) { - return (int) (dp * density + 0.5f); - } - - static native boolean loadProperties(); - - static native void setupShadersDiskCache(String cacheFile); - - /** - * Notifies EGL that the frame is about to be rendered. - * @param size - */ - static native void beginFrame(int[] size); - - /** - * Returns the current system time according to the renderer. - * This method is used for debugging only and should not be used - * as a clock. - */ - static native long getSystemTime(); - - /** - * Preserves the back buffer of the current surface after a buffer swap. - * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current - * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL - * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT. - * - * @return True if the swap behavior was successfully changed, - * false otherwise. - */ - static native boolean preserveBackBuffer(); - - /** - * Indicates whether the current surface preserves its back buffer - * after a buffer swap. - * - * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED, - * false otherwise - */ - static native boolean isBackBufferPreserved(); - - static native void nDestroyLayer(long layerPtr); - - private static native void nPrepareTree(long displayListPtr); - - class DrawPerformanceDataProvider extends GraphDataProvider { - private final int mGraphType; - - private int mVerticalUnit; - private int mHorizontalUnit; - private int mHorizontalMargin; - private int mThresholdStroke; - - DrawPerformanceDataProvider(int graphType) { - mGraphType = graphType; - } - - @Override - void prepare(DisplayMetrics metrics) { - final float density = metrics.density; - - mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); - mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); - mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density); - mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); - } - - @Override - int getGraphType() { - return mGraphType; - } - - @Override - int getVerticalUnitSize() { - return mVerticalUnit; - } - - @Override - int getHorizontalUnitSize() { - return mHorizontalUnit; - } - - @Override - int getHorizontaUnitMargin() { - return mHorizontalMargin; - } - - @Override - float[] getData() { - return mProfileData; - } - - @Override - float getThreshold() { - return 16; - } - - @Override - int getFrameCount() { - return mProfileData.length / PROFILE_FRAME_DATA_COUNT; - } - - @Override - int getElementCount() { - return PROFILE_FRAME_DATA_COUNT; - } - - @Override - int getCurrentFrame() { - return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT; - } - - @Override - void setupGraphPaint(Paint paint, int elementIndex) { - paint.setColor(PROFILE_DRAW_COLORS[elementIndex]); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - - @Override - void setupThresholdPaint(Paint paint) { - paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR); - paint.setStrokeWidth(mThresholdStroke); - } - - @Override - void setupCurrentFramePaint(Paint paint) { - paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR); - if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke); - } - } -} diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 9568760..b8e7d8c 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas { return RenderNode.STATUS_DONE; } - /** - * Indicates that the specified layer must be updated as soon as possible. - * - * @param layer The layer to update - * - * @see #clearLayerUpdates() - * - * @hide - */ - abstract void pushLayerUpdate(HardwareLayer layer); - - /** - * Cancels a queued layer update. If the specified layer was not - * queued for update, this method has no effect. - * - * @param layer The layer whose update to cancel - * - * @see #pushLayerUpdate(HardwareLayer) - * @see #clearLayerUpdates() - * - * @hide - */ - abstract void cancelLayerUpdate(HardwareLayer layer); - - /** - * Immediately executes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void flushLayerUpdates(); - - /** - * Removes all enqueued layer updates. - * - * @see #pushLayerUpdate(HardwareLayer) - * - * @hide - */ - abstract void clearLayerUpdates(); - public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy, CanvasProperty<Float> radius, CanvasProperty<Paint> paint); } diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java index 4d78733..6acb134 100644 --- a/core/java/android/view/HardwareLayer.java +++ b/core/java/android/view/HardwareLayer.java @@ -22,6 +22,8 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import com.android.internal.util.VirtualRefBasePtr; + /** * A hardware layer can be used to render graphics operations into a hardware * friendly buffer. For instance, with an OpenGL backend a hardware layer @@ -36,7 +38,7 @@ final class HardwareLayer { private static final int LAYER_TYPE_DISPLAY_LIST = 2; private HardwareRenderer mRenderer; - private Finalizer mFinalizer; + private VirtualRefBasePtr mFinalizer; private RenderNode mDisplayList; private final int mLayerType; @@ -47,10 +49,7 @@ final class HardwareLayer { } mRenderer = renderer; mLayerType = type; - mFinalizer = new Finalizer(deferredUpdater); - - // Layer is considered initialized at this point, notify the HardwareRenderer - mRenderer.onLayerCreated(this); + mFinalizer = new VirtualRefBasePtr(deferredUpdater); } private void assertType(int type) { @@ -59,6 +58,10 @@ final class HardwareLayer { } } + boolean hasDisplayList() { + return mDisplayList != null; + } + /** * Update the paint used when drawing this layer. * @@ -66,7 +69,8 @@ final class HardwareLayer { * @see View#setLayerPaint(android.graphics.Paint) */ public void setLayerPaint(Paint paint) { - nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint); + nSetLayerPaint(mFinalizer.get(), paint.mNativePaint); + mRenderer.pushLayerUpdate(this); } /** @@ -75,7 +79,7 @@ final class HardwareLayer { * @return True if the layer can be rendered into, false otherwise */ public boolean isValid() { - return mFinalizer != null && mFinalizer.mDeferredUpdater != 0; + return mFinalizer != null && mFinalizer.get() != 0; } /** @@ -91,35 +95,14 @@ final class HardwareLayer { mDisplayList.destroyDisplayListData(); mDisplayList = null; } - if (mRenderer != null) { - mRenderer.onLayerDestroyed(this); - mRenderer = null; - } - doDestroyLayerUpdater(); + mRenderer.onLayerDestroyed(this); + mRenderer = null; + mFinalizer.release(); + mFinalizer = null; } public long getDeferredLayerUpdater() { - return mFinalizer.mDeferredUpdater; - } - - /** - * Destroys the deferred layer updater but not the backing layer. The - * backing layer is instead returned and is the caller's responsibility - * to destroy/recycle as appropriate. - * - * It is safe to call this in onLayerDestroyed only - */ - public long detachBackingLayer() { - long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater); - doDestroyLayerUpdater(); - return backingLayer; - } - - private void doDestroyLayerUpdater() { - if (mFinalizer != null) { - mFinalizer.destroy(); - mFinalizer = null; - } + return mFinalizer.get(); } public RenderNode startRecording() { @@ -132,7 +115,7 @@ final class HardwareLayer { } public void endRecording(Rect dirtyRect) { - nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(), + nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(), dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); mRenderer.pushLayerUpdate(this); } @@ -160,7 +143,7 @@ final class HardwareLayer { * match the desired values. */ public boolean prepare(int width, int height, boolean isOpaque) { - return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque); + return nPrepare(mFinalizer.get(), width, height, isOpaque); } /** @@ -169,7 +152,8 @@ final class HardwareLayer { * @param matrix The transform to apply to the layer. */ public void setTransform(Matrix matrix) { - nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance); + nSetTransform(mFinalizer.get(), matrix.native_instance); + mRenderer.pushLayerUpdate(this); } /** @@ -183,41 +167,25 @@ final class HardwareLayer { surface.detachFromGLContext(); // SurfaceTexture owns the texture name and detachFromGLContext // should have deleted it - nOnTextureDestroyed(mFinalizer.mDeferredUpdater); + nOnTextureDestroyed(mFinalizer.get()); } }); } - /** - * This exists to minimize impact into the current HardwareLayer paths as - * some of the specifics of how to handle error cases in the fully - * deferred model will work - */ - @Deprecated - public void flushChanges() { - if (HardwareRenderer.sUseRenderThread) { - // Not supported, don't try. - return; - } - - boolean success = nFlushChanges(mFinalizer.mDeferredUpdater); - if (!success) { - destroy(); - } - } - public long getLayer() { - return nGetLayer(mFinalizer.mDeferredUpdater); + return nGetLayer(mFinalizer.get()); } public void setSurfaceTexture(SurfaceTexture surface) { assertType(LAYER_TYPE_TEXTURE); - nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false); + nSetSurfaceTexture(mFinalizer.get(), surface, false); + mRenderer.pushLayerUpdate(this); } public void updateSurfaceTexture() { assertType(LAYER_TYPE_TEXTURE); - nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater); + nUpdateSurfaceTexture(mFinalizer.get()); + mRenderer.pushLayerUpdate(this); } /** @@ -225,48 +193,20 @@ final class HardwareLayer { */ SurfaceTexture createSurfaceTexture() { assertType(LAYER_TYPE_TEXTURE); - SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater)); - nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true); + SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get())); + nSetSurfaceTexture(mFinalizer.get(), st, true); return st; } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - static HardwareLayer createTextureLayer(HardwareRenderer renderer) { - return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE); - } - static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) { return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE); } - /** - * This should only be used by HardwareRenderer! Do not call directly - */ - static HardwareLayer createDisplayListLayer(HardwareRenderer renderer, - int width, int height) { - return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST); - } - static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) { return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST); } - /** This also creates the underlying layer */ - private static native long nCreateTextureLayer(); - private static native long nCreateRenderLayer(int width, int height); - private static native void nOnTextureDestroyed(long layerUpdater); - private static native long nDetachBackingLayer(long layerUpdater); - - /** This also destroys the underlying layer if it is still attached. - * Note it does not recycle the underlying layer, but instead queues it - * for deferred deletion. - * The HardwareRenderer should use detachBackingLayer() in the - * onLayerDestroyed() callback to do recycling if desired. - */ - private static native void nDestroyLayerUpdater(long layerUpdater); private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque); private static native void nSetLayerPaint(long layerUpdater, long paint); @@ -281,28 +221,4 @@ final class HardwareLayer { private static native long nGetLayer(long layerUpdater); private static native int nGetTexName(long layerUpdater); - - private static class Finalizer { - private long mDeferredUpdater; - - public Finalizer(long deferredUpdater) { - mDeferredUpdater = deferredUpdater; - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - void destroy() { - if (mDeferredUpdater != 0) { - nDestroyLayerUpdater(mDeferredUpdater); - mDeferredUpdater = 0; - } - } - } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index a902ce7..d67c974 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -17,13 +17,13 @@ package android.view; import android.graphics.Bitmap; -import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.util.DisplayMetrics; import android.view.Surface.OutOfResourcesException; import java.io.File; +import java.io.FileDescriptor; import java.io.PrintWriter; /** @@ -61,11 +61,9 @@ public abstract class HardwareRenderer { * Possible values: * "true", to enable profiling * "visual_bars", to enable profiling and visualize the results on screen - * "visual_lines", to enable profiling and visualize the results on screen * "false", to disable profiling * * @see #PROFILE_PROPERTY_VISUALIZE_BARS - * @see #PROFILE_PROPERTY_VISUALIZE_LINES * * @hide */ @@ -80,14 +78,6 @@ public abstract class HardwareRenderer { public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars"; /** - * Value for {@link #PROFILE_PROPERTY}. When the property is set to this - * value, profiling data will be visualized on screen as a line chart. - * - * @hide - */ - public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines"; - - /** * System property used to specify the number of frames to be used * when doing hardware rendering profiling. * The default value of this property is #PROFILE_MAX_FRAMES. @@ -181,9 +171,6 @@ public abstract class HardwareRenderer { */ public static boolean sSystemRendererDisabled = false; - /** @hide */ - public static boolean sUseRenderThread = true; - private boolean mEnabled; private boolean mRequested = true; @@ -298,16 +285,8 @@ public abstract class HardwareRenderer { /** * Outputs extra debugging information in the specified file descriptor. - * @param pw - */ - abstract void dumpGfxInfo(PrintWriter pw); - - /** - * Outputs the total number of frames rendered (used for fps calculations) - * - * @return the number of frames rendered */ - abstract long getFrameCount(); + abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd); /** * Loads system properties used by the renderer. This method is invoked @@ -327,7 +306,7 @@ public abstract class HardwareRenderer { * @hide */ public static void setupDiskCache(File cacheDir) { - GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); } /** @@ -341,12 +320,6 @@ public abstract class HardwareRenderer { abstract void pushLayerUpdate(HardwareLayer layer); /** - * Tells the HardwareRenderer that a layer was created. The renderer should - * make sure to apply any pending layer changes at the start of a new frame - */ - abstract void onLayerCreated(HardwareLayer hardwareLayer); - - /** * Tells the HardwareRenderer that the layer is destroyed. The renderer * should remove the layer from any update queues. */ @@ -493,11 +466,7 @@ public abstract class HardwareRenderer { static HardwareRenderer create(boolean translucent) { HardwareRenderer renderer = null; if (GLES20Canvas.isAvailable()) { - if (sUseRenderThread) { - renderer = new ThreadedRenderer(translucent); - } else { - renderer = new GLRenderer(translucent); - } + renderer = new ThreadedRenderer(translucent); } return renderer; } @@ -524,7 +493,7 @@ public abstract class HardwareRenderer { * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { - GLRenderer.startTrimMemory(level); + ThreadedRenderer.startTrimMemory(level); } /** @@ -532,7 +501,7 @@ public abstract class HardwareRenderer { * cleanup special resources used by the memory trimming process. */ static void endTrimMemory() { - GLRenderer.endTrimMemory(); + ThreadedRenderer.endTrimMemory(); } /** @@ -579,96 +548,8 @@ public abstract class HardwareRenderer { abstract void fence(); /** - * Describes a series of frames that should be drawn on screen as a graph. - * Each frame is composed of 1 or more elements. + * Called by {@link ViewRootImpl} when a new performTraverals is scheduled. */ - abstract class GraphDataProvider { - /** - * Draws the graph as bars. Frame elements are stacked on top of - * each other. - */ - public static final int GRAPH_TYPE_BARS = 0; - /** - * Draws the graph as lines. The number of series drawn corresponds - * to the number of elements. - */ - public static final int GRAPH_TYPE_LINES = 1; - - /** - * Returns the type of graph to render. - * - * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES} - */ - abstract int getGraphType(); - - /** - * This method is invoked before the graph is drawn. This method - * can be used to compute sizes, etc. - * - * @param metrics The display metrics - */ - abstract void prepare(DisplayMetrics metrics); - - /** - * @return The size in pixels of a vertical unit. - */ - abstract int getVerticalUnitSize(); - - /** - * @return The size in pixels of a horizontal unit. - */ - abstract int getHorizontalUnitSize(); - - /** - * @return The size in pixels of the margin between horizontal units. - */ - abstract int getHorizontaUnitMargin(); - - /** - * An optional threshold value. - * - * @return A value >= 0 to draw the threshold, a negative value - * to ignore it. - */ - abstract float getThreshold(); - - /** - * The data to draw in the graph. The number of elements in the - * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}. - * If a value is negative the following values will be ignored. - */ - abstract float[] getData(); - - /** - * Returns the number of frames to render in the graph. - */ - abstract int getFrameCount(); - - /** - * Returns the number of elements in each frame. This directly affects - * the number of series drawn in the graph. - */ - abstract int getElementCount(); - - /** - * Returns the current frame, if any. If the returned value is negative - * the current frame is ignored. - */ - abstract int getCurrentFrame(); - - /** - * Prepares the paint to draw the specified element (or series.) - */ - abstract void setupGraphPaint(Paint paint, int elementIndex); - - /** - * Prepares the paint to draw the threshold. - */ - abstract void setupThresholdPaint(Paint paint); - - /** - * Prepares the paint to draw the current frame indicator. - */ - abstract void setupCurrentFramePaint(Paint paint); + public void notifyFramePending() { } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 7d13399..af16185 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -79,7 +79,7 @@ interface IWindowManager void removeWindowToken(IBinder token); void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, - int configChanges); + int configChanges, boolean voiceInteraction); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); @@ -120,6 +120,7 @@ interface IWindowManager boolean isKeyguardSecure(); boolean inKeyguardRestrictedInputMode(); void dismissKeyguard(); + void keyguardGoingAway(); void closeSystemDialogs(String reason); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index cf125bc..e63829e 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -850,6 +850,13 @@ public class RenderNode { nOutput(mNativeRenderNode); } + /** + * Gets the size of the DisplayList for debug purposes. + */ + public int getDebugSize() { + return nGetDebugSize(mNativeRenderNode); + } + /////////////////////////////////////////////////////////////////////////// // Animations /////////////////////////////////////////////////////////////////////////// @@ -941,6 +948,7 @@ public class RenderNode { private static native float nGetPivotX(long renderNode); private static native float nGetPivotY(long renderNode); private static native void nOutput(long renderNode); + private static native int nGetDebugSize(long renderNode); /////////////////////////////////////////////////////////////////////////// // Animations diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index e918119..4979059 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -219,6 +219,15 @@ public final class RenderNodeAnimator extends Animator { return mTarget; } + /** + * WARNING: May only be called once!!! + * TODO: Fix above -_- + */ + public void setStartValue(float startValue) { + checkMutable(); + nSetStartValue(mNativePtr.get(), startValue); + } + @Override public void setStartDelay(long startDelay) { checkMutable(); @@ -282,11 +291,12 @@ public final class RenderNodeAnimator extends Animator { } private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis, - int property, float deltaValue); + int property, float finalValue); private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis, - long canvasProperty, float deltaValue); + long canvasProperty, float finalValue); private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis, - long canvasProperty, int paintField, float deltaValue); + long canvasProperty, int paintField, float finalValue); + private static native void nSetStartValue(long nativePtr, float startValue); private static native void nSetDuration(long nativePtr, long duration); private static native long nGetDuration(long nativePtr); private static native void nSetStartDelay(long nativePtr, long startDelay); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c15ce44..5cd3d62 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -38,11 +38,11 @@ public class SurfaceControl { private static native void nativeDestroy(long nativeObject); private static native Bitmap nativeScreenshot(IBinder displayToken, - int width, int height, int minLayer, int maxLayer, boolean allLayers, - boolean useIdentityTransform); + Rect sourceCrop, int width, int height, int minLayer, int maxLayer, + boolean allLayers, boolean useIdentityTransform); private static native void nativeScreenshot(IBinder displayToken, Surface consumer, - int width, int height, int minLayer, int maxLayer, boolean allLayers, - boolean useIdentityTransform); + Rect sourceCrop, int width, int height, int minLayer, int maxLayer, + boolean allLayers, boolean useIdentityTransform); private static native void nativeOpenTransaction(); private static native void nativeCloseTransaction(); @@ -597,8 +597,8 @@ public class SurfaceControl { public static void screenshot(IBinder display, Surface consumer, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform) { - screenshot(display, consumer, width, height, minLayer, maxLayer, false, - useIdentityTransform); + screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer, + false, useIdentityTransform); } /** @@ -613,7 +613,7 @@ public class SurfaceControl { */ public static void screenshot(IBinder display, Surface consumer, int width, int height) { - screenshot(display, consumer, width, height, 0, 0, true, false); + screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false); } /** @@ -623,7 +623,7 @@ public class SurfaceControl { * @param consumer The {@link Surface} to take the screenshot into. */ public static void screenshot(IBinder display, Surface consumer) { - screenshot(display, consumer, 0, 0, 0, 0, true, false); + screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false); } /** @@ -634,6 +634,8 @@ public class SurfaceControl { * the versions that use a {@link Surface} instead, such as * {@link SurfaceControl#screenshot(IBinder, Surface)}. * + * @param sourceCrop The portion of the screen to capture into the Bitmap; + * caller may pass in 'new Rect()' if no cropping is desired. * @param width The desired width of the returned bitmap; the raw * screen will be scaled down to this size. * @param height The desired height of the returned bitmap; the raw @@ -649,13 +651,13 @@ public class SurfaceControl { * if an error occurs. Make sure to call Bitmap.recycle() as soon as * possible, once its content is not needed anymore. */ - public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer, - boolean useIdentityTransform) { + public static Bitmap screenshot(Rect sourceCrop, int width, int height, + int minLayer, int maxLayer, boolean useIdentityTransform) { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); - return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false, - useIdentityTransform); + return nativeScreenshot(displayToken, sourceCrop, width, height, + minLayer, maxLayer, false, useIdentityTransform); } /** @@ -674,10 +676,10 @@ public class SurfaceControl { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); - return nativeScreenshot(displayToken, width, height, 0, 0, true, false); + return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false); } - private static void screenshot(IBinder display, Surface consumer, + private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform) { if (display == null) { @@ -686,7 +688,7 @@ public class SurfaceControl { if (consumer == null) { throw new IllegalArgumentException("consumer must not be null"); } - nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers, - useIdentityTransform); + nativeScreenshot(display, consumer, sourceCrop, width, height, + minLayer, maxLayer, allLayers, useIdentityTransform); } } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 1765c43..2a9f7d5 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -405,7 +405,9 @@ public class TextureView extends View { // To cancel updates, the easiest thing to do is simply to remove the // updates listener if (visibility == VISIBLE) { - mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + if (mLayer != null) { + mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + } updateLayerAndInvalidate(); } else { mSurface.setOnFrameAvailableListener(null); diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 704d516..9b3ef7f 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -22,19 +22,19 @@ import android.graphics.SurfaceTexture; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.Trace; import android.util.Log; import android.util.TimeUtils; import android.view.Surface.OutOfResourcesException; import android.view.View.AttachInfo; +import java.io.FileDescriptor; import java.io.PrintWriter; /** * Hardware renderer that proxies the rendering to a render thread. Most calls * are currently synchronous. - * TODO: Make draw() async. - * TODO: Figure out how to share the DisplayList between two threads (global lock?) * * The UI thread can block on the RenderThread, but RenderThread must never * block on the UI thread. @@ -62,11 +62,16 @@ public class ThreadedRenderer extends HardwareRenderer { // Needs a ViewRoot invalidate private static final int SYNC_INVALIDATE_REQUIRED = 0x1; + private static final String[] VISUALIZERS = { + PROFILE_PROPERTY_VISUALIZE_BARS, + }; + private int mWidth, mHeight; private long mNativeProxy; private boolean mInitialized = false; private RenderNode mRootNode; private Choreographer mChoreographer; + private boolean mProfilingEnabled; ThreadedRenderer(boolean translucent) { AtlasInitializer.sInstance.init(); @@ -79,6 +84,8 @@ public class ThreadedRenderer extends HardwareRenderer { // Setup timing mChoreographer = Choreographer.getInstance(); nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos()); + + loadSystemProperties(); } @Override @@ -117,7 +124,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void destroyHardwareResources(View view) { destroyResources(view); - // TODO: GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); + nFlushCaches(mNativeProxy, GLES20Canvas.FLUSH_CACHES_LAYERS); } private static void destroyResources(View view) { @@ -168,19 +175,33 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void dumpGfxInfo(PrintWriter pw) { - // TODO Auto-generated method stub + void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { + pw.flush(); + nDumpProfileInfo(mNativeProxy, fd); } - @Override - long getFrameCount() { - // TODO Auto-generated method stub - return 0; + private static int search(String[] values, String value) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(value)) return i; + } + return -1; + } + + private static boolean checkIfProfilingRequested() { + String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY); + int graphType = search(VISUALIZERS, profiling); + return (graphType >= 0) || Boolean.parseBoolean(profiling); } @Override boolean loadSystemProperties() { - return nLoadSystemProperties(mNativeProxy); + boolean changed = nLoadSystemProperties(mNativeProxy); + boolean wantProfiling = checkIfProfilingRequested(); + if (wantProfiling != mProfilingEnabled) { + mProfilingEnabled = wantProfiling; + changed = true; + } + return changed; } private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) { @@ -212,14 +233,24 @@ public class ThreadedRenderer extends HardwareRenderer { long frameTimeNanos = mChoreographer.getFrameTimeNanos(); attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS; + long recordDuration = 0; + if (mProfilingEnabled) { + recordDuration = System.nanoTime(); + } + updateRootDisplayList(view, callbacks); + if (mProfilingEnabled) { + recordDuration = System.nanoTime() - recordDuration; + } + attachInfo.mIgnoreDirtyState = false; if (dirty == null) { dirty = NULL_RECT; } int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, + recordDuration, view.getResources().getDisplayMetrics().density, dirty.left, dirty.top, dirty.right, dirty.bottom); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { attachInfo.mViewRootImpl.invalidate(); @@ -263,12 +294,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void pushLayerUpdate(HardwareLayer layer) { - // TODO: Remove this, it's not needed outside of GLRenderer - } - - @Override - void onLayerCreated(HardwareLayer layer) { - // TODO: Is this actually useful? + nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } @Override @@ -278,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override void onLayerDestroyed(HardwareLayer layer) { - nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater()); + nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater()); } @Override @@ -291,6 +317,11 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override + public void notifyFramePending() { + nNotifyFramePending(mNativeProxy); + } + + @Override protected void finalize() throws Throwable { try { nDeleteProxy(mNativeProxy); @@ -300,6 +331,14 @@ public class ThreadedRenderer extends HardwareRenderer { } } + static void startTrimMemory(int level) { + // TODO + } + + static void endTrimMemory() { + // TODO + } + private static class AtlasInitializer { static AtlasInitializer sInstance = new AtlasInitializer(); @@ -336,6 +375,8 @@ public class ThreadedRenderer extends HardwareRenderer { } } + static native void setupShadersDiskCache(String cacheFile); + private static native void nSetAtlas(GraphicBuffer buffer, long[] map); private static native long nCreateRootRenderNode(); @@ -351,7 +392,8 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nSetup(long nativeProxy, int width, int height, float lightX, float lightY, float lightZ, float lightRadius); private static native void nSetOpaque(long nativeProxy, boolean opaque); - private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos, + private static native int nSyncAndDrawFrame(long nativeProxy, + long frameTimeNanos, long recordDuration, float density, int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); private static native void nDestroyCanvasAndSurface(long nativeProxy); @@ -361,7 +403,13 @@ public class ThreadedRenderer extends HardwareRenderer { private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height); private static native long nCreateTextureLayer(long nativeProxy); private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); - private static native void nDestroyLayer(long nativeProxy, long layer); + private static native void nPushLayerUpdate(long nativeProxy, long layer); + private static native void nCancelLayerUpdate(long nativeProxy, long layer); + + private static native void nFlushCaches(long nativeProxy, int flushMode); private static native void nFence(long nativeProxy); + private static native void nNotifyFramePending(long nativeProxy); + + private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0f21c1d..622fa8c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9236,6 +9236,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Request unbuffered dispatch of the given stream of MotionEvents to this View. + * + * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input + * system not batch {@link MotionEvent}s but instead deliver them as soon as they're + * available. This method should only be called for touch events. + * + * <p class="note">This api is not intended for most applications. Buffered dispatch + * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent + * streams will not improve your input latency. Side effects include: increased latency, + * jittery scrolls and inability to take advantage of system resampling. Talk to your input + * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for + * you.</p> + */ + public final void requestUnbufferedDispatch(MotionEvent event) { + final int action = event.getAction(); + if (mAttachInfo == null + || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE + || !event.isTouchEvent()) { + return; + } + mAttachInfo.mUnbufferedDispatchRequested = true; + } + + /** * Set flags controlling behavior of this view. * * @param flags Constant indicating the value which should be set @@ -10687,8 +10711,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link Drawable#getOutline(Outline)}. Manually setting the Outline with this method allows * this behavior to be overridden. * <p> - * If the outline is empty or is null, shadows will be cast from the - * bounds of the View. + * If the outline is {@link Outline#isEmpty()} or is <code>null</code>, + * shadows will not be cast. * <p> * Only outlines that return true from {@link Outline#canClip()} may be used for clipping. * @@ -13569,12 +13593,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - // The layer is not valid if the underlying GPU resources cannot be allocated - mHardwareLayer.flushChanges(); - if (!mHardwareLayer.isValid()) { - return null; - } - mHardwareLayer.setLayerPaint(mLayerPaint); RenderNode displayList = mHardwareLayer.startRecording(); updateDisplayListIfDirty(displayList, true); @@ -16173,6 +16191,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Set this view's optical insets. + * + * <p>This method should be treated similarly to setMeasuredDimension and not as a general + * property. Views that compute their own optical insets should call it as part of measurement. + * This method does not request layout. If you are setting optical insets outside of + * measure/layout itself you will want to call requestLayout() yourself. + * </p> + * @hide + */ + public void setOpticalInsets(Insets insets) { + mLayoutInsets = insets; + } + + /** * Changes the selection state of this view. A view can be selected or not. * Note that selection is not the same as focus. Views are typically * selected in the context of an AdapterView like ListView or GridView; @@ -19760,6 +19792,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean mInTouchMode; /** + * Indicates whether the view has requested unbuffered input dispatching for the current + * event stream. + */ + boolean mUnbufferedDispatchRequested; + + /** * Indicates that ViewAncestor should trigger a global layout change * the next time it performs a traversal */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b821a3e..0f40ee7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -456,6 +456,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // views during a transition when they otherwise would have become gone/invisible private ArrayList<View> mVisibilityChangingChildren; + // Temporary holder of presorted children, only used for + // input/software draw dispatch for correctly Z ordering. + private ArrayList<View> mPreSortedChildren; + // Indicates how many of this container's child subtrees contain transient state @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; @@ -1499,13 +1503,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float y = event.getY(); final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final boolean customChildOrder = isChildrenDrawingOrderEnabled(); + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; HoverTarget lastHoverTarget = null; for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customChildOrder - ? getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1572,6 +1578,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager break; } } + if (preorderedList != null) preorderedList.clear(); } } @@ -1778,23 +1785,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final View[] children = mChildren; final float x = event.getX(); final float y = event.getY(); - final boolean customOrder = isChildrenDrawingOrderEnabled(); + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (dispatchTransformedGenericPointerEvent(event, child)) { + if (preorderedList != null) preorderedList.clear(); return true; } } + if (preorderedList != null) preorderedList.clear(); } // No child handled the event. Send it to this view group. @@ -1910,13 +1922,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; - - final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { - final int childIndex = customOrder ? - getChildDrawingOrder(childrenCount, i) : i; - final View child = children[childIndex]; + final int childIndex = customOrder + ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; @@ -1934,7 +1948,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); - mLastTouchDownIndex = childIndex; + if (preorderedList != null) { + // childIndex points into presorted list, find original index + for (int j = 0; j < childrenCount; j++) { + if (children[childIndex] == mChildren[j]) { + mLastTouchDownIndex = j; + break; + } + } + } else { + mLastTouchDownIndex = childIndex; + } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); @@ -1942,6 +1966,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager break; } } + if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { @@ -2928,7 +2953,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override protected void dispatchDraw(Canvas canvas) { - final int count = mChildrenCount; + final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; @@ -2936,15 +2961,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; final boolean buildCache = !isHardwareAccelerated(); - for (int i = 0; i < count; i++) { + for (int i = 0; i < childrenCount; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); - attachLayoutAnimationParameters(child, params, i, count); + attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); - if (buildCache) { + if (buildCache) { child.buildDrawingCache(true); } } @@ -2997,21 +3022,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean more = false; final long drawingTime = getDrawingTime(); - if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { - for (int i = 0; i < count; i++) { - final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - more |= drawChild(canvas, child, drawingTime); - } - } - } else { - for (int i = 0; i < count; i++) { - final View child = children[getChildDrawingOrder(count, i)]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - more |= drawChild(canvas, child, drawingTime); - } + + // Only use the preordered list if not HW accelerated, since the HW pipeline will do the + // draw reordering internally + final ArrayList<View> preorderedList = canvas.isHardwareAccelerated() + ? null : buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + for (int i = 0; i < childrenCount; i++) { + int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { + more |= drawChild(canvas, child, drawingTime); } } + if (preorderedList != null) preorderedList.clear(); // Draw any disappearing views that have animations if (mDisappearingChildren != null) { @@ -3096,6 +3122,47 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return i; } + private boolean hasChildWithZ() { + for (int i = 0; i < mChildrenCount; i++) { + if (mChildren[i].getZ() != 0) return true; + } + return false; + } + + /** + * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children, + * sorted first by Z, then by child drawing order (if applicable). + * + * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated + * children. + */ + private ArrayList<View> buildOrderedChildList() { + final int count = mChildrenCount; + if (count <= 1 || !hasChildWithZ()) return null; + + if (mPreSortedChildren == null) { + mPreSortedChildren = new ArrayList<View>(count); + } else { + mPreSortedChildren.ensureCapacity(count); + } + + final boolean useCustomOrder = isChildrenDrawingOrderEnabled(); + for (int i = 0; i < mChildrenCount; i++) { + // add next child (in child order) to end of list + int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i; + View nextChild = mChildren[childIndex]; + float currentZ = nextChild.getZ(); + + // insert ahead of any Views with greater Z + int insertIndex = i; + while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { + insertIndex--; + } + mPreSortedChildren.add(insertIndex, nextChild); + } + return mPreSortedChildren; + } + private void notifyAnimationListener() { mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER; mGroupFlags |= FLAG_ANIMATION_DONE; diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 3104862..af1de78 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -253,9 +253,10 @@ public class ViewPropertyAnimator { ViewPropertyAnimator(View view) { mView = view; view.ensureTransformationInfo(); - if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { - mRTBackend = new ViewPropertyAnimatorRT(view); - } + // TODO: Disabled because of b/15287046 + //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) { + // mRTBackend = new ViewPropertyAnimatorRT(view); + //} } /** @@ -1142,7 +1143,8 @@ public class ViewPropertyAnimator { // Shouldn't happen, but just to play it safe return; } - boolean useRenderNodeProperties = mView.mRenderNode != null; + + boolean hardwareAccelerated = mView.isHardwareAccelerated(); // alpha requires slightly different treatment than the other (transform) properties. // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation @@ -1150,13 +1152,13 @@ public class ViewPropertyAnimator { // We track what kinds of properties are set, and how alpha is handled when it is // set, and perform the invalidation steps appropriately. boolean alphaHandled = false; - if (!useRenderNodeProperties) { + if (!hardwareAccelerated) { mView.invalidateParentCaches(); } float fraction = animation.getAnimatedFraction(); int propertyMask = propertyBundle.mPropertyMask; if ((propertyMask & TRANSFORM_MASK) != 0) { - mView.invalidateViewProperty(false, false); + mView.invalidateViewProperty(hardwareAccelerated, false); } ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; if (valueList != null) { @@ -1172,7 +1174,7 @@ public class ViewPropertyAnimator { } } if ((propertyMask & TRANSFORM_MASK) != 0) { - if (!useRenderNodeProperties) { + if (!hardwareAccelerated) { mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 799a406..f3d1e3c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -230,6 +230,7 @@ public final class ViewRootImpl implements ViewParent, QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; boolean mProcessInputEventsScheduled; + boolean mUnbufferedInputDispatch; String mPendingInputEventQueueLengthCounterName = "pq"; InputStage mFirstInputStage; @@ -715,17 +716,6 @@ public final class ViewRootImpl implements ViewParent, if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled && forceHwAccelerated)) { - if (!HardwareRenderer.sUseRenderThread) { - // TODO: Delete - // Don't enable hardware acceleration when we're not on the main thread - if (!HardwareRenderer.sSystemRendererDisabled && - Looper.getMainLooper() != Looper.myLooper()) { - Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware " - + "acceleration outside of the main thread, aborting"); - return; - } - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } @@ -999,13 +989,27 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Notifies the HardwareRenderer that a new frame will be coming soon. + * Currently only {@link ThreadedRenderer} cares about this, and uses + * this knowledge to adjust the scheduling of off-thread animations + */ + void notifyRendererOfFramePending() { + if (mAttachInfo.mHardwareRenderer != null) { + mAttachInfo.mHardwareRenderer.notifyFramePending(); + } + } + void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); - scheduleConsumeBatchedInput(); + if (!mUnbufferedInputDispatch) { + scheduleConsumeBatchedInput(); + } + notifyRendererOfFramePending(); } } @@ -2604,7 +2608,7 @@ public final class ViewRootImpl implements ViewParent, } final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); - final Rect bounds = mView.mAttachInfo.mTmpInvalRect; + final Rect bounds = mAttachInfo.mTmpInvalRect; if (provider == null) { host.getBoundsOnScreen(bounds); } else if (mAccessibilityFocusedVirtualView != null) { @@ -3886,6 +3890,18 @@ public final class ViewRootImpl implements ViewParent, } } + @Override + protected void onDeliverToNext(QueuedInputEvent q) { + if (mUnbufferedInputDispatch + && q.mEvent instanceof MotionEvent + && ((MotionEvent)q.mEvent).isTouchEvent() + && isTerminalInputEvent(q.mEvent)) { + mUnbufferedInputDispatch = false; + scheduleConsumeBatchedInput(); + } + super.onDeliverToNext(q); + } + private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; @@ -3998,10 +4014,15 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; - if (mView.dispatchPointerEvent(event)) { - return FINISH_HANDLED; + mAttachInfo.mUnbufferedDispatchRequested = false; + boolean handled = mView.dispatchPointerEvent(event); + if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { + mUnbufferedInputDispatch = true; + if (mConsumeBatchedInputScheduled) { + scheduleConsumeBatchedInputImmediately(); + } } - return FORWARD; + return handled ? FINISH_HANDLED : FORWARD; } private int processTrackballEvent(QueuedInputEvent q) { @@ -5266,6 +5287,8 @@ public final class ViewRootImpl implements ViewParent, writer.print(" mRemoved="); writer.println(mRemoved); writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled="); writer.println(mConsumeBatchedInputScheduled); + writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled="); + writer.println(mConsumeBatchedInputImmediatelyScheduled); writer.print(innerPrefix); writer.print("mPendingInputEventCount="); writer.println(mPendingInputEventCount); writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled="); @@ -5317,7 +5340,7 @@ public final class ViewRootImpl implements ViewParent, RenderNode renderNode = view.mRenderNode; info[0]++; if (renderNode != null) { - info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */ + info[1] += renderNode.getDebugSize(); } if (view instanceof ViewGroup) { @@ -5676,6 +5699,7 @@ public final class ViewRootImpl implements ViewParent, private void finishInputEvent(QueuedInputEvent q) { Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent", q.mEvent.getSequenceNumber()); + if (q.mReceiver != null) { boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0; q.mReceiver.finishInputEvent(q.mEvent, handled); @@ -5715,15 +5739,25 @@ public final class ViewRootImpl implements ViewParent, } } + void scheduleConsumeBatchedInputImmediately() { + if (!mConsumeBatchedInputImmediatelyScheduled) { + unscheduleConsumeBatchedInput(); + mConsumeBatchedInputImmediatelyScheduled = true; + mHandler.post(mConsumeBatchedInputImmediatelyRunnable); + } + } + void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if (mInputEventReceiver != null) { - if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) { + if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) + && frameTimeNanos != -1) { // If we consumed a batch here, we want to go ahead and schedule the // consumption of batched input events on the next frame. Otherwise, we would // wait until we have more input events pending and might get starved by other - // things occurring in the process. + // things occurring in the process. If the frame time is -1, however, then + // we're in a non-batching mode, so there's no need to schedule this. scheduleConsumeBatchedInput(); } } @@ -5751,7 +5785,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void onBatchedInputEventPending() { - scheduleConsumeBatchedInput(); + if (mUnbufferedInputDispatch) { + super.onBatchedInputEventPending(); + } else { + scheduleConsumeBatchedInput(); + } } @Override @@ -5772,6 +5810,16 @@ public final class ViewRootImpl implements ViewParent, new ConsumeBatchedInputRunnable(); boolean mConsumeBatchedInputScheduled; + final class ConsumeBatchedInputImmediatelyRunnable implements Runnable { + @Override + public void run() { + doConsumeBatchedInput(-1); + } + } + final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable = + new ConsumeBatchedInputImmediatelyRunnable(); + boolean mConsumeBatchedInputImmediatelyScheduled; + final class InvalidateOnAnimationRunnable implements Runnable { private boolean mPosted; private final ArrayList<View> mViews = new ArrayList<View>(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 031ad80..4eecc6a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -218,7 +218,8 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"), @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"), @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"), - @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION") + @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"), + @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"), }) public int type; @@ -541,6 +542,12 @@ public interface WindowManager extends ViewManager { public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30; /** + * Window type: Windows in the voice interaction layer. + * @hide + */ + public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 96c0ed2..b4779f4 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -430,7 +430,7 @@ public final class WindowManagerGlobal { HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; if (renderer != null) { - renderer.dumpGfxInfo(pw); + renderer.dumpGfxInfo(pw, fd); } } @@ -447,11 +447,6 @@ public final class WindowManagerGlobal { String name = getWindowName(root); pw.printf(" %s\n %d views, %.2f kB of display lists", name, info[0], info[1] / 1024.0f); - HardwareRenderer renderer = - root.getView().mAttachInfo.mHardwareRenderer; - if (renderer != null) { - pw.printf(", %d frames rendered", renderer.getFrameCount()); - } pw.printf("\n\n"); viewsCount += info[0]; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 4fde1e4..2b4677c 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -274,6 +274,11 @@ public interface WindowManagerPolicy { public IApplicationToken getAppToken(); /** + * Return true if this window is participating in voice interaction. + */ + public boolean isVoiceInteraction(); + + /** * Return true if, at any point, the application token associated with * this window has actually displayed any windows. This is most useful * with the "starting up" window to determine if any windows were @@ -603,8 +608,15 @@ public interface WindowManagerPolicy { * Return whether the given window should forcibly hide everything * behind it. Typically returns true for the keyguard. */ - public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs); - + public boolean doesForceHide(WindowManager.LayoutParams attrs); + + + /** + * Return whether the given window can become one that passes doesForceHide() test. + * Typically returns true for the StatusBar. + */ + public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs); + /** * Determine if a window that is behind one that is force hiding * (as determined by {@link #doesForceHide}) should actually be hidden. @@ -613,7 +625,7 @@ public interface WindowManagerPolicy { * will conflict with what you set. */ public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs); - + /** * Called when the system would like to show a UI to indicate that an * application is starting. You can use this to add a @@ -1184,4 +1196,12 @@ public interface WindowManagerPolicy { * @return True if the window is a top level one. */ public boolean isTopLevelWindow(int windowType); + + /** + * Notifies the keyguard to start fading out. + * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + */ + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 628da3c..84f395a 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -427,8 +427,12 @@ public class SpellCheckerSession { @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - mHandler.sendMessage( - Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + synchronized (this) { + if (mHandler != null) { + mHandler.sendMessage(Message.obtain(mHandler, + MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results)); + } + } } } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c9eb130..9a46052 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2495,17 +2495,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Positions the selector in a way that mimics keyboard focus. If the - * selector drawable supports hotspots, this manages the focus hotspot. + * Positions the selector in a way that mimics keyboard focus. */ void positionSelectorLikeFocus(int position, View sel) { + // If we're changing position, update the visibility since the selector + // is technically being detached from the previous selection. + final Drawable selector = mSelector; + final boolean manageState = selector != null && mSelectorPosition != position + && position != INVALID_POSITION; + if (manageState) { + selector.setVisible(false, false); + } + positionSelector(position, sel); - final Drawable selector = mSelector; - if (selector != null && position != INVALID_POSITION) { + if (manageState) { final Rect bounds = mSelectorRect; final float x = bounds.exactCenterX(); final float y = bounds.exactCenterY(); + selector.setVisible(getVisibility() == VISIBLE, false); selector.setHotspot(x, y); } } @@ -2520,8 +2528,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (sel instanceof SelectionBoundsAdjuster) { ((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect); } - positionSelector(selectorRect.left, selectorRect.top, selectorRect.right, - selectorRect.bottom); + + // Adjust for selection padding. + selectorRect.left -= mSelectionLeftPadding; + selectorRect.top -= mSelectionTopPadding; + selectorRect.right += mSelectionRightPadding; + selectorRect.bottom += mSelectionBottomPadding; + + // Update the selector drawable. + final Drawable selector = mSelector; + if (selector != null) { + selector.setBounds(selectorRect); + } final boolean isChildViewEnabled = mIsChildViewEnabled; if (sel.isEnabled() != isChildViewEnabled) { @@ -2532,11 +2550,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - private void positionSelector(int l, int t, int r, int b) { - mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r - + mSelectionRightPadding, b + mSelectionBottomPadding); - } - @Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 1152e17..43f623b 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -256,6 +256,16 @@ public abstract class AbsSeekBar extends ProgressBar { } @Override + public void invalidateDrawable(Drawable dr) { + super.invalidateDrawable(dr); + + if (dr == mThumb) { + // Handle changes to thumb width and height. + requestLayout(); + } + } + + @Override void onProgressRefresh(float scale, boolean fromUser) { super.onProgressRefresh(scale, fromUser); diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 51759c5..1fddf3e 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -544,6 +544,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter public void setMenuView(ActionMenuView menuView) { mMenuView = menuView; + menuView.initialize(mMenu); } private static class SavedState implements Parcelable { diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 3975edf..acee592 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -69,6 +69,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo /** @hide */ public void setPresenter(ActionMenuPresenter presenter) { mPresenter = presenter; + mPresenter.setMenuView(this); } @Override @@ -488,7 +489,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mPresenter.dismissPopupMenus(); + dismissPopupMenus(); } /** @hide */ @@ -569,15 +570,65 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo mMenu = new MenuBuilder(context); mMenu.setCallback(new MenuBuilderCallback()); mPresenter = new ActionMenuPresenter(context); - mPresenter.setMenuView(this); mPresenter.setCallback(new ActionMenuPresenterCallback()); mMenu.addMenuPresenter(mPresenter); + mPresenter.setMenuView(this); } return mMenu; } /** + * Returns the current menu or null if one has not yet been configured. + * @hide Internal use only for action bar integration + */ + public MenuBuilder peekMenu() { + return mMenu; + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mPresenter != null && mPresenter.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mPresenter != null && mPresenter.hideOverflowMenu(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mPresenter != null && mPresenter.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mPresenter != null && mPresenter.isOverflowMenuShowPending(); + } + + /** + * Dismiss any popups associated with this menu view. + */ + public void dismissPopupMenus() { + if (mPresenter != null) { + mPresenter.dismissPopupMenus(); + } + } + + /** * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public. */ @Override @@ -601,6 +652,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo return false; } + /** @hide */ + public void setExpandedActionViewsExclusive(boolean exclusive) { + mPresenter.setExpandedActionViewsExclusive(exclusive); + } + /** * Interface responsible for receiving menu item click events if the items themselves * do not have individual item click listeners. diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 1533510..3ae9508 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -24,6 +24,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; +import android.view.RemotableViewMethod; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -156,10 +157,36 @@ public class CheckedTextView extends TextView implements Checkable { mCheckMarkWidth = 0; } mCheckMarkDrawable = d; - // Do padding resolution. This will call internalSetPadding() and do a requestLayout() if needed. + + // Do padding resolution. This will call internalSetPadding() and do a + // requestLayout() if needed. resolvePadding(); } + @RemotableViewMethod + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + + if (mCheckMarkDrawable != null) { + mCheckMarkDrawable.setVisible(visibility == VISIBLE, false); + } + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + + if (mCheckMarkDrawable != null) { + mCheckMarkDrawable.jumpToCurrentState(); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mCheckMarkDrawable || super.verifyDrawable(who); + } + /** * Gets the checkmark drawable * @@ -249,6 +276,11 @@ public class CheckedTextView extends TextView implements Checkable { } checkMarkDrawable.setBounds(mScrollX + left, top, mScrollX + right, bottom); checkMarkDrawable.draw(canvas); + + final Drawable background = getBackground(); + if (background != null) { + background.setHotspotBounds(mScrollX + left, top, mScrollX + right, bottom); + } } } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 265dbcd..2c1a77c 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -24,6 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.text.InputType; +import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; @@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout { mSpinners.removeAllViews(); // We use numeric spinners for year and day, but textual months. Ask icu4c what // order the user's locale uses for that combination. http://b/7207103. - String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", - Locale.getDefault().toString()); + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); char[] order = ICU.getDateFormatOrder(pattern); final int spinnerCount = order.length; for (int i = 0; i < spinnerCount; i++) { diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 8511601..defc26c 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -104,14 +104,16 @@ import static java.lang.Math.min; * * <h4>Excess Space Distribution</h4> * - * GridLayout's distribution of excess space is based on <em>priority</em> - * rather than <em>weight</em>. + * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight. + * In the event that no weights are specified, the previous conventions are respected and + * columns and rows are taken as flexible if their views specify some form of alignment + * within their groups. * <p> - * A child's ability to stretch is inferred from the alignment properties of - * its row and column groups (which are typically set by setting the - * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters). - * If alignment was defined along a given axis then the component - * is taken as <em>flexible</em> in that direction. If no alignment was set, + * The flexibility of a view is therefore influenced by its alignment which is, + * in turn, typically defined by setting the + * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters. + * If either a weight or alignment were defined along a given axis then the component + * is taken as <em>flexible</em> in that direction. If no weight or alignment was set, * the component is instead assumed to be <em>inflexible</em>. * <p> * Multiple components in the same row or column group are @@ -122,12 +124,16 @@ import static java.lang.Math.min; * elements is flexible if <em>one</em> of its elements is flexible. * <p> * To make a column stretch, make sure all of the components inside it define a - * gravity. To prevent a column from stretching, ensure that one of the components - * in the column does not define a gravity. + * weight or a gravity. To prevent a column from stretching, ensure that one of the components + * in the column does not define a weight or a gravity. * <p> * When the principle of flexibility does not provide complete disambiguation, * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em> - * and <em>bottom</em> edges. + * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout + * parameters as a constraint in the a set of variables that define the grid-lines along a + * given axis. During layout, GridLayout solves the constraints so as to return the unique + * solution to those constraints for which all variables are less-than-or-equal-to + * the corresponding value in any other valid solution. * * <h4>Interpretation of GONE</h4> * @@ -140,18 +146,6 @@ import static java.lang.Math.min; * had never been added to it. * These statements apply equally to rows as well as columns, and to groups of rows or columns. * - * <h5>Limitations</h5> - * - * GridLayout does not provide support for the principle of <em>weight</em>, as defined in - * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible - * to configure a GridLayout to distribute excess space between multiple components. - * <p> - * Some common use-cases may nevertheless be accommodated as follows. - * To place equal amounts of space around a component in a cell group; - * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}). - * For complete control over excess space distribution in a row or column; - * use a {@link LinearLayout} subview to hold the components in the associated cell group. - * When using either of these techniques, bear in mind that cell groups may be defined to overlap. * <p> * See {@link GridLayout.LayoutParams} for a full description of the * layout parameters used by GridLayout. @@ -1018,6 +1012,8 @@ public class GridLayout extends ViewGroup { LayoutParams lp = getLayoutParams(c); if (firstPass) { measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); + mHorizontalAxis.recordOriginalMeasurement(i); + mVerticalAxis.recordOriginalMeasurement(i); } else { boolean horizontal = (mOrientation == HORIZONTAL); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; @@ -1245,6 +1241,11 @@ public class GridLayout extends ViewGroup { public int[] locations; public boolean locationsValid = false; + public boolean hasWeights; + public boolean hasWeightsValid = false; + public int[] originalMeasurements; + public int[] deltas; + boolean orderPreserved = DEFAULT_ORDER_PRESERVED; private MutableInt parentMin = new MutableInt(0); @@ -1321,7 +1322,10 @@ public class GridLayout extends ViewGroup { // we must include views that are GONE here, see introductory javadoc LayoutParams lp = getLayoutParams(c); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; - groupBounds.getValue(i).include(GridLayout.this, c, spec, this); + int size = (spec.weight == 0) ? + getMeasurementIncludingMargin(c, horizontal) : + getOriginalMeasurements()[i] + getDeltas()[i]; + groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size); } } @@ -1693,8 +1697,94 @@ public class GridLayout extends ViewGroup { return trailingMargins; } - private void computeLocations(int[] a) { + private void solve(int[] a) { solve(getArcs(), a); + } + + private boolean computeHasWeights() { + for (int i = 0, N = getChildCount(); i < N; i++) { + LayoutParams lp = getLayoutParams(getChildAt(i)); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + if (spec.weight != 0) { + return true; + } + } + return false; + } + + private boolean hasWeights() { + if (!hasWeightsValid) { + hasWeights = computeHasWeights(); + hasWeightsValid = true; + } + return hasWeights; + } + + public int[] getOriginalMeasurements() { + if (originalMeasurements == null) { + originalMeasurements = new int[getChildCount()]; + } + return originalMeasurements; + } + + private void recordOriginalMeasurement(int i) { + if (hasWeights()) { + getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal); + } + } + + public int[] getDeltas() { + if (deltas == null) { + deltas = new int[getChildCount()]; + } + return deltas; + } + + private void shareOutDelta() { + int totalDelta = 0; + float totalWeight = 0; + for (int i = 0, N = getChildCount(); i < N; i++) { + View c = getChildAt(i); + LayoutParams lp = getLayoutParams(c); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + float weight = spec.weight; + if (weight != 0) { + int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i]; + totalDelta += delta; + totalWeight += weight; + } + } + for (int i = 0, N = getChildCount(); i < N; i++) { + LayoutParams lp = getLayoutParams(getChildAt(i)); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; + float weight = spec.weight; + if (weight != 0) { + int delta = Math.round((weight * totalDelta / totalWeight)); + deltas[i] = delta; + // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end + totalDelta -= delta; + totalWeight -= weight; + } + } + } + + private void solveAndDistributeSpace(int[] a) { + Arrays.fill(getDeltas(), 0); + solve(a); + shareOutDelta(); + arcsValid = false; + forwardLinksValid = false; + backwardLinksValid = false; + groupBoundsValid = false; + solve(a); + } + + private void computeLocations(int[] a) { + if (!hasWeights()) { + solve(a); + } else { + solveAndDistributeSpace(a); + } if (!orderPreserved) { // Solve returns the smallest solution to the constraint system for which all // values are positive. One value is therefore zero - though if the row/col @@ -1777,6 +1867,10 @@ public class GridLayout extends ViewGroup { locations = null; + originalMeasurements = null; + deltas = null; + hasWeightsValid = false; + invalidateValues(); } @@ -1810,6 +1904,9 @@ public class GridLayout extends ViewGroup { * both aspects of alignment within the cell group. It is also possible to specify a child's * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} * method. + * <p> + * The weight property is also included in Spec and specifies the proportion of any + * excess space that is due to the associated view. * * <h4>WRAP_CONTENT and MATCH_PARENT</h4> * @@ -1851,9 +1948,11 @@ public class GridLayout extends ViewGroup { * <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li> * <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li> * <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li> + * <li>{@link #rowSpec}<code>.weight</code> = 0 </li> * <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li> * <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li> * <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li> + * <li>{@link #columnSpec}<code>.weight</code> = 0 </li> * </ul> * * See {@link GridLayout} for a more complete description of the conventions @@ -1861,8 +1960,10 @@ public class GridLayout extends ViewGroup { * * @attr ref android.R.styleable#GridLayout_Layout_layout_row * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_column * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan + * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { @@ -1889,9 +1990,11 @@ public class GridLayout extends ViewGroup { private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column; private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; + private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight; private static final int ROW = R.styleable.GridLayout_Layout_layout_row; private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; + private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight; private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity; @@ -2034,11 +2137,13 @@ public class GridLayout extends ViewGroup { int column = a.getInt(COLUMN, DEFAULT_COLUMN); int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); - this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); + float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT); + this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight); int row = a.getInt(ROW, DEFAULT_ROW); int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); - this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); + float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT); + this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight); } finally { a.recycle(); } @@ -2273,10 +2378,9 @@ public class GridLayout extends ViewGroup { return before - a.getAlignmentValue(c, size, gl.getLayoutMode()); } - protected final void include(GridLayout gl, View c, Spec spec, Axis axis) { + protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) { this.flexibility &= spec.getFlexibility(); boolean horizontal = axis.horizontal; - int size = gl.getMeasurementIncludingMargin(c, horizontal); Alignment alignment = gl.getAlignment(spec.alignment, horizontal); // todo test this works correctly when the returned value is UNDEFINED int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode()); @@ -2401,36 +2505,43 @@ public class GridLayout extends ViewGroup { * <li>{@link #spec(int, int)}</li> * <li>{@link #spec(int, Alignment)}</li> * <li>{@link #spec(int, int, Alignment)}</li> + * <li>{@link #spec(int, float)}</li> + * <li>{@link #spec(int, int, float)}</li> + * <li>{@link #spec(int, Alignment, float)}</li> + * <li>{@link #spec(int, int, Alignment, float)}</li> * </ul> * */ public static class Spec { static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); + static final float DEFAULT_WEIGHT = 0; final boolean startDefined; final Interval span; final Alignment alignment; + final float weight; - private Spec(boolean startDefined, Interval span, Alignment alignment) { + private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { this.startDefined = startDefined; this.span = span; this.alignment = alignment; + this.weight = weight; } - private Spec(boolean startDefined, int start, int size, Alignment alignment) { - this(startDefined, new Interval(start, start + size), alignment); + private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { + this(startDefined, new Interval(start, start + size), alignment, weight); } final Spec copyWriteSpan(Interval span) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final Spec copyWriteAlignment(Alignment alignment) { - return new Spec(startDefined, span, alignment); + return new Spec(startDefined, span, alignment, weight); } final int getFlexibility() { - return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; + return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; } /** @@ -2478,6 +2589,7 @@ public class GridLayout extends ViewGroup { * <ul> * <li> {@code spec.span = [start, start + size]} </li> * <li> {@code spec.alignment = alignment} </li> + * <li> {@code spec.weight = weight} </li> * </ul> * <p> * To leave the start index undefined, use the value {@link #UNDEFINED}. @@ -2485,9 +2597,55 @@ public class GridLayout extends ViewGroup { * @param start the start * @param size the size * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, int size, Alignment alignment, float weight) { + return new Spec(start != UNDEFINED, start, size, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, alignment, weight)}. + * + * @param start the start + * @param alignment the alignment + * @param weight the weight + */ + public static Spec spec(int start, Alignment alignment, float weight) { + return spec(start, 1, alignment, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, default_alignment, weight)} - + * where {@code default_alignment} is specified in + * {@link android.widget.GridLayout.LayoutParams}. + * + * @param start the start + * @param size the size + * @param weight the weight + */ + public static Spec spec(int start, int size, float weight) { + return spec(start, size, UNDEFINED_ALIGNMENT, weight); + } + + /** + * Equivalent to: {@code spec(start, 1, weight)}. + * + * @param start the start + * @param weight the weight + */ + public static Spec spec(int start, float weight) { + return spec(start, 1, weight); + } + + /** + * Equivalent to: {@code spec(start, size, alignment, 0f)}. + * + * @param start the start + * @param size the size + * @param alignment the alignment */ public static Spec spec(int start, int size, Alignment alignment) { - return new Spec(start != UNDEFINED, start, size, alignment); + return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); } /** diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index eedacb5..572302a 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -664,7 +664,7 @@ public class ImageView extends View { InputStream stream = null; try { stream = mContext.getContentResolver().openInputStream(mUri); - d = Drawable.createFromStreamThemed(stream, null, mContext.getTheme()); + d = Drawable.createFromStream(stream, null); } catch (Exception e) { Log.w("ImageView", "Unable to open content: " + mUri, e); } finally { diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 0c3715d..b49938c 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -357,9 +357,8 @@ public class ProgressBar extends View { Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); shapeDrawable.getPaint().setShader(bitmapShader); - // Ensure the color filter and tint are propagated. - shapeDrawable.setTint(bitmap.getTint()); - shapeDrawable.setTintMode(bitmap.getTintMode()); + // Ensure the tint and filter are propagated in the correct order. + shapeDrawable.setTint(bitmap.getTint(), bitmap.getTintMode()); shapeDrawable.setColorFilter(bitmap.getColorFilter()); return clip ? new ClipDrawable( diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index 0203301..c8917e0 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -574,7 +574,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene throw new FileNotFoundException("Failed to open " + uri); } try { - return Drawable.createFromStreamThemed(stream, null, mContext.getTheme()); + return Drawable.createFromStream(stream, null); } finally { try { stream.close(); diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index ad1a023..c5c6e64 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -951,9 +951,8 @@ public class Switch extends CompoundButton { final int[] myDrawableState = getDrawableState(); - if (mThumbDrawable != null && mThumbDrawable.setState(myDrawableState)) { - // Handle changes to thumb width and height. - requestLayout(); + if (mThumbDrawable != null) { + mThumbDrawable.setState(myDrawableState); } if (mTrackDrawable != null) { @@ -964,6 +963,16 @@ public class Switch extends CompoundButton { } @Override + public void invalidateDrawable(Drawable drawable) { + super.invalidateDrawable(drawable); + + if (drawable == mThumbDrawable) { + // Handle changes to thumb width and height. + requestLayout(); + } + } + + @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; } diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index f903346..419c582 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -18,13 +18,17 @@ package android.widget; import android.annotation.NonNull; +import android.app.ActionBar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; +import android.view.CollapsibleActionView; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; @@ -32,7 +36,15 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.Window; import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuPresenter; +import com.android.internal.view.menu.MenuView; +import com.android.internal.view.menu.SubMenuBuilder; +import com.android.internal.widget.DecorToolbar; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.util.ArrayList; import java.util.List; @@ -80,14 +92,25 @@ import java.util.List; * layout is discouraged on API 21 devices and newer.</p> */ public class Toolbar extends ViewGroup { + private static final String TAG = "Toolbar"; + private ActionMenuView mMenuView; private TextView mTitleTextView; private TextView mSubtitleTextView; private ImageButton mNavButtonView; private ImageView mLogoView; + private Drawable mCollapseIcon; + private ImageButton mCollapseButtonView; + View mExpandedActionView; + private int mTitleTextAppearance; private int mSubtitleTextAppearance; + private int mNavButtonStyle; + + private int mButtonGravity; + + private int mMaxButtonHeight; private int mTitleMarginStart; private int mTitleMarginEnd; @@ -104,6 +127,8 @@ public class Toolbar extends ViewGroup { // Clear me after use. private final ArrayList<View> mTempViews = new ArrayList<View>(); + private final int[] mTempMargins = new int[2]; + private OnMenuItemClickListener mOnMenuItemClickListener; private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener = @@ -117,6 +142,10 @@ public class Toolbar extends ViewGroup { } }; + private ToolbarWidgetWrapper mWrapper; + private ActionMenuPresenter mOuterActionMenuPresenter; + private ExpandedActionViewMenuPresenter mExpandedMenuPresenter; + public Toolbar(Context context) { this(context, null); } @@ -137,7 +166,9 @@ public class Toolbar extends ViewGroup { mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0); mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0); + mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0); mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity); + mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP); mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0); @@ -162,6 +193,8 @@ public class Toolbar extends ViewGroup { mTitleMarginBottom = marginBottom; } + mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1); + final int contentInsetStart = a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart, RtlSpacingHelper.UNDEFINED); @@ -180,6 +213,8 @@ public class Toolbar extends ViewGroup { mContentInsets.setRelative(contentInsetStart, contentInsetEnd); } + mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon); + final CharSequence title = a.getText(R.styleable.Toolbar_title); if (!TextUtils.isEmpty(title)) { setTitle(title); @@ -187,7 +222,7 @@ public class Toolbar extends ViewGroup { final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle); if (!TextUtils.isEmpty(subtitle)) { - setSubtitle(title); + setSubtitle(subtitle); } a.recycle(); } @@ -211,6 +246,110 @@ public class Toolbar extends ViewGroup { setLogo(getContext().getDrawable(resId)); } + /** @hide */ + public boolean canShowOverflowMenu() { + return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved(); + } + + /** + * Check whether the overflow menu is currently showing. This may not reflect + * a pending show operation in progress. + * + * @return true if the overflow menu is currently showing + */ + public boolean isOverflowMenuShowing() { + return mMenuView != null && mMenuView.isOverflowMenuShowing(); + } + + /** @hide */ + public boolean isOverflowMenuShowPending() { + return mMenuView != null && mMenuView.isOverflowMenuShowPending(); + } + + /** + * Show the overflow items from the associated menu. + * + * @return true if the menu was able to be shown, false otherwise + */ + public boolean showOverflowMenu() { + return mMenuView != null && mMenuView.showOverflowMenu(); + } + + /** + * Hide the overflow items from the associated menu. + * + * @return true if the menu was able to be hidden, false otherwise + */ + public boolean hideOverflowMenu() { + return mMenuView != null && mMenuView.hideOverflowMenu(); + } + + /** @hide */ + public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) { + if (menu == null && mMenuView == null) { + return; + } + + ensureMenuView(); + final MenuBuilder oldMenu = mMenuView.peekMenu(); + if (oldMenu == menu) { + return; + } + + if (oldMenu != null) { + oldMenu.removeMenuPresenter(mOuterActionMenuPresenter); + oldMenu.removeMenuPresenter(mExpandedMenuPresenter); + } + + final Context context = getContext(); + + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + + outerPresenter.setExpandedActionViewsExclusive(true); + if (menu != null) { + menu.addMenuPresenter(outerPresenter); + menu.addMenuPresenter(mExpandedMenuPresenter); + } else { + outerPresenter.initForMenu(context, null); + mExpandedMenuPresenter.initForMenu(context, null); + outerPresenter.updateMenuView(true); + mExpandedMenuPresenter.updateMenuView(true); + } + mMenuView.setPresenter(outerPresenter); + mOuterActionMenuPresenter = outerPresenter; + } + + /** + * Dismiss all currently showing popup menus, including overflow or submenus. + */ + public void dismissPopupMenus() { + if (mMenuView != null) { + mMenuView.dismissPopupMenus(); + } + } + + /** @hide */ + public boolean isTitleTruncated() { + if (mTitleTextView == null) { + return false; + } + + final Layout titleLayout = mTitleTextView.getLayout(); + if (titleLayout == null) { + return false; + } + + final int lineCount = titleLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + if (titleLayout.getEllipsisCount(i) > 0) { + return true; + } + } + return false; + } + /** * Set a logo drawable. * @@ -222,9 +361,7 @@ public class Toolbar extends ViewGroup { */ public void setLogo(Drawable drawable) { if (drawable != null) { - if (mLogoView == null) { - mLogoView = new ImageView(getContext()); - } + ensureLogoView(); if (mLogoView.getParent() == null) { addSystemView(mLogoView); } @@ -268,8 +405,8 @@ public class Toolbar extends ViewGroup { * @param description Description to set */ public void setLogoDescription(CharSequence description) { - if (!TextUtils.isEmpty(description) && mLogoView == null) { - mLogoView = new ImageView(getContext()); + if (!TextUtils.isEmpty(description)) { + ensureLogoView(); } if (mLogoView != null) { mLogoView.setContentDescription(description); @@ -285,10 +422,48 @@ public class Toolbar extends ViewGroup { return mLogoView != null ? mLogoView.getContentDescription() : null; } + private void ensureLogoView() { + if (mLogoView == null) { + mLogoView = new ImageView(getContext()); + } + } + + /** + * Check whether this Toolbar is currently hosting an expanded action view. + * + * <p>An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar + * has an expanded action view it can be collapsed using the {@link #collapseActionView()} + * method.</p> + * + * @return true if the Toolbar has an expanded action view + */ + public boolean hasExpandedActionView() { + return mExpandedMenuPresenter != null && + mExpandedMenuPresenter.mCurrentExpandedItem != null; + } + /** - * Return the current title displayed in the toolbar. + * Collapse a currently expanded action view. If this Toolbar does not have an + * expanded action view this method has no effect. + * + * <p>An action view may be expanded either directly from the + * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p> * - * @return The current title + * @see #hasExpandedActionView() + */ + public void collapseActionView() { + final MenuItemImpl item = mExpandedMenuPresenter == null ? null : + mExpandedMenuPresenter.mCurrentExpandedItem; + if (item != null) { + item.collapseActionView(); + } + } + + /** + * Returns the title of this toolbar. + * + * @return The current title. */ public CharSequence getTitle() { return mTitleText; @@ -319,6 +494,8 @@ public class Toolbar extends ViewGroup { if (mTitleTextView == null) { final Context context = getContext(); mTitleTextView = new TextView(context); + mTitleTextView.setSingleLine(); + mTitleTextView.setEllipsize(TextUtils.TruncateAt.END); mTitleTextView.setTextAppearance(context, mTitleTextAppearance); } if (mTitleTextView.getParent() == null) { @@ -365,6 +542,8 @@ public class Toolbar extends ViewGroup { if (mSubtitleTextView == null) { final Context context = getContext(); mSubtitleTextView = new TextView(context); + mSubtitleTextView.setSingleLine(); + mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END); mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance); } if (mSubtitleTextView.getParent() == null) { @@ -380,6 +559,28 @@ public class Toolbar extends ViewGroup { } /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setTitleTextAppearance(Context context, int resId) { + mTitleTextAppearance = resId; + if (mTitleTextView != null) { + mTitleTextView.setTextAppearance(context, resId); + } + } + + /** + * Sets the text color, size, style, hint color, and highlight color + * from the specified TextAppearance resource. + */ + public void setSubtitleTextAppearance(Context context, int resId) { + mSubtitleTextAppearance = resId; + if (mSubtitleTextView != null) { + mSubtitleTextView.setTextAppearance(context, resId); + } + } + + /** * Set the icon to use for the toolbar's navigation button. * * <p>The navigation button appears at the start of the toolbar if present. Setting an icon @@ -395,6 +596,30 @@ public class Toolbar extends ViewGroup { } /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param description Content description to set + */ + public void setNavigationContentDescription(CharSequence description) { + ensureNavButtonView(); + mNavButtonView.setContentDescription(description); + } + + /** + * Set a content description for the navigation button if one is present. The content + * description will be read via screen readers or other accessibility systems to explain + * the action of the navigation button. + * + * @param resId Resource ID of a content description string to set + */ + public void setNavigationContentDescription(int resId) { + ensureNavButtonView(); + mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null); + } + + /** * Set the icon to use for the toolbar's navigation button. * * <p>The navigation button appears at the start of the toolbar if present. Setting an icon @@ -480,12 +705,32 @@ public class Toolbar extends ViewGroup { * @return The toolbar's Menu */ public Menu getMenu() { + ensureMenu(); + return mMenuView.getMenu(); + } + + private void ensureMenu() { + ensureMenuView(); + if (mMenuView.peekMenu() == null) { + // Initialize a new menu for the first time. + final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu(); + if (mExpandedMenuPresenter == null) { + mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); + } + mMenuView.setExpandedActionViewsExclusive(true); + menu.addMenuPresenter(mExpandedMenuPresenter); + } + } + + private void ensureMenuView() { if (mMenuView == null) { mMenuView = new ActionMenuView(getContext()); mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mMenuView.setLayoutParams(lp); addSystemView(mMenuView); } - return mMenuView.getMenu(); } private MenuInflater getMenuInflater() { @@ -634,7 +879,27 @@ public class Toolbar extends ViewGroup { private void ensureNavButtonView() { if (mNavButtonView == null) { - mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle); + mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + mNavButtonView.setLayoutParams(lp); + } + } + + private void ensureCollapseButtonView() { + if (mCollapseButtonView == null) { + mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); + mCollapseButtonView.setImageDrawable(mCollapseIcon); + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mCollapseButtonView.setLayoutParams(lp); + mCollapseButtonView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + collapseActionView(); + } + }); } } @@ -657,39 +922,121 @@ public class Toolbar extends ViewGroup { super.onRestoreInstanceState(ss.getSuperState()); } + private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed, + int parentHeightSpec, int heightUsed, int heightConstraint) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childWidthSpec = getChildMeasureSpec(parentWidthSpec, + mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + int childHeightSpec = getChildMeasureSpec(parentHeightSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + final int childHeightMode = MeasureSpec.getMode(childHeightSpec); + if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) { + final int size = childHeightMode != MeasureSpec.UNSPECIFIED ? + Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) : + heightConstraint; + childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } + child.measure(childWidthSpec, childHeightSpec); + } + + /** + * Returns the width + uncollapsed margins + */ + private int measureChildCollapseMargins(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int leftDiff = lp.leftMargin - collapsingMargins[0]; + final int rightDiff = lp.rightMargin - collapsingMargins[1]; + final int leftMargin = Math.max(0, leftDiff); + final int rightMargin = Math.max(0, rightDiff); + final int hMargins = leftMargin + rightMargin; + collapsingMargins[0] = Math.max(0, -leftDiff); + collapsingMargins[1] = Math.max(0, -rightDiff); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + return child.getMeasuredWidth() + hMargins; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; int childState = 0; + final int[] collapsingMargins = mTempMargins; + final int marginStartIndex; + final int marginEndIndex; + if (isLayoutRtl()) { + marginStartIndex = 1; + marginEndIndex = 0; + } else { + marginStartIndex = 0; + marginEndIndex = 1; + } + // System views measure first. int navWidth = 0; if (shouldLayout(mNavButtonView)) { - measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0); + measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView); height = Math.max(height, mNavButtonView.getMeasuredHeight() + getVerticalMargins(mNavButtonView)); childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState()); } - width += Math.max(getContentInsetStart(), navWidth); + if (shouldLayout(mCollapseButtonView)) { + measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width, + heightMeasureSpec, 0, mMaxButtonHeight); + navWidth = mCollapseButtonView.getMeasuredWidth() + + getHorizontalMargins(mCollapseButtonView); + height = Math.max(height, mCollapseButtonView.getMeasuredHeight() + + getVerticalMargins(mCollapseButtonView)); + childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState()); + } + + final int contentInsetStart = getContentInsetStart(); + width += Math.max(contentInsetStart, navWidth); + collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth); int menuWidth = 0; if (shouldLayout(mMenuView)) { - measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0); + measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0, + mMaxButtonHeight); menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView); height = Math.max(height, mMenuView.getMeasuredHeight() + getVerticalMargins(mMenuView)); childState = combineMeasuredStates(childState, mMenuView.getMeasuredState()); } - width += Math.max(getContentInsetEnd(), menuWidth); + final int contentInsetEnd = getContentInsetEnd(); + width += Math.max(contentInsetEnd, menuWidth); + collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth); + + if (shouldLayout(mExpandedActionView)) { + width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); + height = Math.max(height, mExpandedActionView.getMeasuredHeight() + + getVerticalMargins(mExpandedActionView)); + childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState()); + } if (shouldLayout(mLogoView)) { - measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0); - width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView); + width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); height = Math.max(height, mLogoView.getMeasuredHeight() + getVerticalMargins(mLogoView)); childState = combineMeasuredStates(childState, mLogoView.getMeasuredState()); @@ -700,17 +1047,18 @@ public class Toolbar extends ViewGroup { final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom; final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd; if (shouldLayout(mTitleTextView)) { - measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins, - heightMeasureSpec, titleVertMargins); + titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec, + width + titleHorizMargins, heightMeasureSpec, titleVertMargins, + collapsingMargins); titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView); titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView); childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState()); } if (shouldLayout(mSubtitleTextView)) { - measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins, - heightMeasureSpec, titleHeight + titleVertMargins); - titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() + - getHorizontalMargins(mSubtitleTextView)); + titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView, + widthMeasureSpec, width + titleHorizMargins, + heightMeasureSpec, titleHeight + titleVertMargins, + collapsingMargins)); titleHeight += mSubtitleTextView.getMeasuredHeight() + getVerticalMargins(mSubtitleTextView); childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState()); @@ -723,13 +1071,13 @@ public class Toolbar extends ViewGroup { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType == LayoutParams.SYSTEM || !shouldLayout(child)) { + if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) { // We already got all system views above. Skip them and GONE views. continue; } - measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0); - width += child.getMeasuredWidth() + getHorizontalMargins(child); + width += measureChildCollapseMargins(child, widthMeasureSpec, width, + heightMeasureSpec, 0, collapsingMargins); height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child)); childState = combineMeasuredStates(childState, child.getMeasuredState()); } @@ -760,30 +1108,51 @@ public class Toolbar extends ViewGroup { int left = paddingLeft; int right = width - paddingRight; + final int[] collapsingMargins = mTempMargins; + collapsingMargins[0] = collapsingMargins[1] = 0; + if (shouldLayout(mNavButtonView)) { if (isRtl) { - right = layoutChildRight(mNavButtonView, right); + right = layoutChildRight(mNavButtonView, right, collapsingMargins); } else { - left = layoutChildLeft(mNavButtonView, left); + left = layoutChildLeft(mNavButtonView, left, collapsingMargins); + } + } + + if (shouldLayout(mCollapseButtonView)) { + if (isRtl) { + right = layoutChildRight(mCollapseButtonView, right, collapsingMargins); + } else { + left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins); } } if (shouldLayout(mMenuView)) { if (isRtl) { - left = layoutChildLeft(mMenuView, left); + left = layoutChildLeft(mMenuView, left, collapsingMargins); } else { - right = layoutChildRight(mMenuView, right); + right = layoutChildRight(mMenuView, right, collapsingMargins); } } + collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left); + collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right)); left = Math.max(left, getContentInsetLeft()); right = Math.min(right, width - paddingRight - getContentInsetRight()); + if (shouldLayout(mExpandedActionView)) { + if (isRtl) { + right = layoutChildRight(mExpandedActionView, right, collapsingMargins); + } else { + left = layoutChildLeft(mExpandedActionView, left, collapsingMargins); + } + } + if (shouldLayout(mLogoView)) { if (isRtl) { - right = layoutChildRight(mLogoView, right); + right = layoutChildRight(mLogoView, right, collapsingMargins); } else { - left = layoutChildLeft(mLogoView, left); + left = layoutChildLeft(mLogoView, left, collapsingMargins); } } @@ -801,79 +1170,83 @@ public class Toolbar extends ViewGroup { if (layoutTitle || layoutSubtitle) { int titleTop; + final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView; + final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView; + final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams(); + final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams(); + switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: - titleTop = getPaddingTop(); + titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop; break; default: case Gravity.CENTER_VERTICAL: - final View child = layoutTitle ? mTitleTextView : mSubtitleTextView; - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int space = height - paddingTop - paddingBottom; int spaceAbove = (space - titleHeight) / 2; - if (spaceAbove < lp.topMargin + mTitleMarginTop) { - spaceAbove = lp.topMargin + mTitleMarginTop; + if (spaceAbove < toplp.topMargin + mTitleMarginTop) { + spaceAbove = toplp.topMargin + mTitleMarginTop; } else { final int spaceBelow = height - paddingBottom - titleHeight - spaceAbove - paddingTop; - if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) { + if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) { spaceAbove = Math.max(0, spaceAbove - - (lp.bottomMargin + mTitleMarginBottom - spaceBelow)); + (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow)); } } titleTop = paddingTop + spaceAbove; break; case Gravity.BOTTOM: - titleTop = height - paddingBottom - titleHeight; + titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom - + titleHeight; break; } if (isRtl) { + final int rd = mTitleMarginStart - collapsingMargins[1]; + right -= Math.max(0, rd); + collapsingMargins[1] = Math.max(0, -rd); int titleRight = right; int subtitleRight = right; - titleTop += mTitleMarginTop; + if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); - titleRight -= lp.rightMargin + mTitleMarginStart; - titleTop += lp.topMargin; final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); - titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd; + titleRight = titleLeft - mTitleMarginEnd; titleTop = titleBottom + lp.bottomMargin; } if (layoutSubtitle) { final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); - subtitleRight -= lp.rightMargin + mTitleMarginStart; titleTop += lp.topMargin; final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth(); final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); - subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd; + subtitleRight = subtitleRight - mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } right = Math.max(titleRight, subtitleRight); } else { + final int ld = mTitleMarginStart - collapsingMargins[0]; + left += Math.max(0, ld); + collapsingMargins[0] = Math.max(0, -ld); int titleLeft = left; int subtitleLeft = left; - titleTop += mTitleMarginTop; + if (layoutTitle) { final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); - titleLeft += lp.leftMargin + mTitleMarginStart; - titleTop += lp.topMargin; final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth(); final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); - titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd; + titleLeft = titleRight + mTitleMarginEnd; titleTop = titleBottom + lp.bottomMargin; } if (layoutSubtitle) { final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); - subtitleLeft += lp.leftMargin + mTitleMarginStart; titleTop += lp.topMargin; final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth(); final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); - subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd; + subtitleLeft = subtitleRight + mTitleMarginEnd; titleTop = subtitleBottom + lp.bottomMargin; } left = Math.max(titleLeft, subtitleLeft); @@ -886,19 +1259,19 @@ public class Toolbar extends ViewGroup { addCustomViewsWithGravity(mTempViews, Gravity.LEFT); final int leftViewsCount = mTempViews.size(); for (int i = 0; i < leftViewsCount; i++) { - left = layoutChildLeft(mTempViews.get(i), left); + left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins); } addCustomViewsWithGravity(mTempViews, Gravity.RIGHT); final int rightViewsCount = mTempViews.size(); for (int i = 0; i < rightViewsCount; i++) { - right = layoutChildRight(mTempViews.get(i), right); + right = layoutChildRight(mTempViews.get(i), right, collapsingMargins); } // Centered views try to center with respect to the whole bar, but views pinned // to the left or right can push the mass of centered views to one side or the other. - addCustomViewsWithGravity(mTempViews, Gravity.CENTER); - final int centerViewsWidth = getViewListMeasuredWidth(mTempViews); + addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL); + final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins); final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2; final int halfCenterViewsWidth = centerViewsWidth / 2; int centerLeft = parentCenter - halfCenterViewsWidth; @@ -911,25 +1284,35 @@ public class Toolbar extends ViewGroup { final int centerViewsCount = mTempViews.size(); for (int i = 0; i < centerViewsCount; i++) { - centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft); + centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins); } mTempViews.clear(); } - private int getViewListMeasuredWidth(List<View> views) { + private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) { + int collapseLeft = collapsingMargins[0]; + int collapseRight = collapsingMargins[1]; int width = 0; final int count = views.size(); for (int i = 0; i < count; i++) { final View v = views.get(i); final LayoutParams lp = (LayoutParams) v.getLayoutParams(); - width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin; + final int l = lp.leftMargin - collapseLeft; + final int r = lp.rightMargin - collapseRight; + final int leftMargin = Math.max(0, l); + final int rightMargin = Math.max(0, r); + collapseLeft = Math.max(0, -l); + collapseRight = Math.max(0, -r); + width += leftMargin + v.getMeasuredWidth() + rightMargin; } return width; } - private int layoutChildLeft(View child, int left) { + private int layoutChildLeft(View child, int left, int[] collapsingMargins) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - left += lp.leftMargin; + final int l = lp.leftMargin - collapsingMargins[0]; + left += Math.max(0, l); + collapsingMargins[0] = Math.max(0, -l); final int top = getChildTop(child); final int childWidth = child.getMeasuredWidth(); child.layout(left, top, left + childWidth, top + child.getMeasuredHeight()); @@ -937,9 +1320,11 @@ public class Toolbar extends ViewGroup { return left; } - private int layoutChildRight(View child, int right) { + private int layoutChildRight(View child, int right, int[] collapsingMargins) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - right -= lp.rightMargin; + final int r = lp.rightMargin - collapsingMargins[1]; + right -= Math.max(0, r); + collapsingMargins[1] = Math.max(0, -r); final int top = getChildTop(child); final int childWidth = child.getMeasuredWidth(); child.layout(right - childWidth, top, right, top + child.getMeasuredHeight()); @@ -1007,17 +1392,16 @@ public class Toolbar extends ViewGroup { for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) && + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && getChildHorizontalGravity(lp.gravity) == absGrav) { views.add(child); } - } } else { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) && + if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && getChildHorizontalGravity(lp.gravity) == absGrav) { views.add(child); } @@ -1054,14 +1438,16 @@ public class Toolbar extends ViewGroup { } @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return super.generateLayoutParams(attrs); + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); } @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { if (p instanceof LayoutParams) { return new LayoutParams((LayoutParams) p); + } else if (p instanceof ActionBar.LayoutParams) { + return new LayoutParams((ActionBar.LayoutParams) p); } else if (p instanceof MarginLayoutParams) { return new LayoutParams((MarginLayoutParams) p); } else { @@ -1070,7 +1456,7 @@ public class Toolbar extends ViewGroup { } @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @@ -1083,6 +1469,25 @@ public class Toolbar extends ViewGroup { return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM; } + /** @hide */ + public DecorToolbar getWrapper() { + if (mWrapper == null) { + mWrapper = new ToolbarWidgetWrapper(this); + } + return mWrapper; + } + + private void setChildVisibilityForExpandedActionView(boolean expand) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) { + child.setVisibility(expand ? GONE : VISIBLE); + } + } + } + /** * Interface responsible for receiving menu item click events if the items themselves * do not have individual item click listeners. @@ -1103,44 +1508,15 @@ public class Toolbar extends ViewGroup { * * @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity */ - public static class LayoutParams extends MarginLayoutParams { - /** - * Gravity for the view associated with these LayoutParams. - * - * @see android.view.Gravity - */ - @ViewDebug.ExportedProperty(category = "layout", mapping = { - @ViewDebug.IntToString(from = -1, to = "NONE"), - @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), - @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), - @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), - @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), - @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), - @ViewDebug.IntToString(from = Gravity.START, to = "START"), - @ViewDebug.IntToString(from = Gravity.END, to = "END"), - @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), - @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), - @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), - @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"), - @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), - @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") - }) - public int gravity = Gravity.NO_GRAVITY; - + public static class LayoutParams extends ActionBar.LayoutParams { static final int CUSTOM = 0; static final int SYSTEM = 1; + static final int EXPANDED = 2; int mViewType = CUSTOM; public LayoutParams(@NonNull Context c, AttributeSet attrs) { super(c, attrs); - - TypedArray a = c.obtainStyledAttributes(attrs, - com.android.internal.R.styleable.Toolbar_LayoutParams); - gravity = a.getInt( - com.android.internal.R.styleable.Toolbar_LayoutParams_layout_gravity, - Gravity.NO_GRAVITY); - a.recycle(); } public LayoutParams(int width, int height) { @@ -1160,7 +1536,11 @@ public class Toolbar extends ViewGroup { public LayoutParams(LayoutParams source) { super(source); - this.gravity = source.gravity; + mViewType = source.mViewType; + } + + public LayoutParams(ActionBar.LayoutParams source) { + super(source); } public LayoutParams(MarginLayoutParams source) { @@ -1199,4 +1579,126 @@ public class Toolbar extends ViewGroup { } }; } + + private class ExpandedActionViewMenuPresenter implements MenuPresenter { + MenuBuilder mMenu; + MenuItemImpl mCurrentExpandedItem; + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + // Clear the expanded action view when menus change. + if (mMenu != null && mCurrentExpandedItem != null) { + mMenu.collapseItemActionView(mCurrentExpandedItem); + } + mMenu = menu; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + return null; + } + + @Override + public void updateMenuView(boolean cleared) { + // Make sure the expanded item we have is still there. + if (mCurrentExpandedItem != null) { + boolean found = false; + + if (mMenu != null) { + final int count = mMenu.size(); + for (int i = 0; i < count; i++) { + final MenuItem item = mMenu.getItem(i); + if (item == mCurrentExpandedItem) { + found = true; + break; + } + } + } + + if (!found) { + // The item we had expanded disappeared. Collapse. + collapseItemActionView(mMenu, mCurrentExpandedItem); + } + } + } + + @Override + public void setCallback(Callback cb) { + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + ensureCollapseButtonView(); + if (mCollapseButtonView.getParent() != Toolbar.this) { + addView(mCollapseButtonView); + } + mExpandedActionView = item.getActionView(); + mCurrentExpandedItem = item; + if (mExpandedActionView.getParent() != Toolbar.this) { + final LayoutParams lp = generateDefaultLayoutParams(); + lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); + lp.mViewType = LayoutParams.EXPANDED; + mExpandedActionView.setLayoutParams(lp); + addView(mExpandedActionView); + } + + setChildVisibilityForExpandedActionView(true); + requestLayout(); + item.setActionViewExpanded(true); + + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); + } + + return true; + } + + @Override + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + // Do this before detaching the actionview from the hierarchy, in case + // it needs to dismiss the soft keyboard, etc. + if (mExpandedActionView instanceof CollapsibleActionView) { + ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed(); + } + + removeView(mExpandedActionView); + removeView(mCollapseButtonView); + mExpandedActionView = null; + + setChildVisibilityForExpandedActionView(false); + mCurrentExpandedItem = null; + requestLayout(); + item.setActionViewExpanded(false); + + return true; + } + + @Override + public int getId() { + return 0; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + } } diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 664f9db..5547a10 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -1026,7 +1026,7 @@ public class AlertController { ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout; if (mCursor == null) { adapter = (mAdapter != null) ? mAdapter - : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems); + : new CheckedItemAdapter(mContext, layout, R.id.text1, mItems); } else { adapter = new SimpleCursorAdapter(mContext, layout, mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1}); @@ -1081,4 +1081,20 @@ public class AlertController { } } + private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { + public CheckedItemAdapter(Context context, int resource, int textViewResourceId, + CharSequence[] objects) { + super(context, resource, textViewResourceId, objects); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public long getItemId(int position) { + return position; + } + } } diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 03d3b22..77f0dec 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,16 +25,18 @@ import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, String containerId, String key, String resFileName, String publicResFileName, boolean isExternal, - boolean isForwardLocked); + boolean isForwardLocked, in String abiOverride); int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams, in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold); + PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold, + in String abiOverride); boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold); - boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked); + boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride); ObbInfo getObbInfo(in String filename); long calculateDirectorySize(in String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ long[] getFileSystemStats(in String path); void clearDirectory(in String directory); - long calculateInstalledSize(in String packagePath, boolean isForwardLocked); + long calculateInstalledSize(in String packagePath, boolean isForwardLocked, + in String abiOverride); } diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl index 737906a..2900595 100644 --- a/core/java/com/android/internal/app/IVoiceInteractor.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl @@ -26,7 +26,9 @@ import com.android.internal.app.IVoiceInteractorRequest; */ interface IVoiceInteractor { IVoiceInteractorRequest startConfirmation(String callingPackage, - IVoiceInteractorCallback callback, String prompt, in Bundle extras); + IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras); + IVoiceInteractorRequest startAbortVoice(String callingPackage, + IVoiceInteractorCallback callback, CharSequence message, in Bundle extras); IVoiceInteractorRequest startCommand(String callingPackage, IVoiceInteractorCallback callback, String command, in Bundle extras); boolean[] supportsCommands(String callingPackage, in String[] commands); diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl index c6f93e1..8dbf9d4 100644 --- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractorRequest; oneway interface IVoiceInteractorCallback { void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, in Bundle result); + void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result); void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result); void deliverCancel(IVoiceInteractorRequest request); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 47ef65a..01e5d40 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -35,8 +35,8 @@ import java.util.Set; /* - * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be - * passed in and out of a managed profile. + * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of + * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile. */ public class IntentForwarderActivity extends Activity { diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 41f3337..7e11850 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -1100,7 +1100,7 @@ public final class ProcessStats implements Parcelable { public boolean evaluateSystemProperties(boolean update) { boolean changed = false; - String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.1", + String runtime = SystemProperties.get("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary()); if (!Objects.equals(runtime, mRuntime)) { changed = true; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 591267e..183dd05 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -484,8 +484,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte mList.clear(); if (mBaseResolveList != null) { - currentResolveList = mBaseResolveList; - mOrigResolveList = null; + currentResolveList = mOrigResolveList = mBaseResolveList; } else { currentResolveList = mOrigResolveList = mPm.queryIntentActivities( mIntent, PackageManager.MATCH_DEFAULT_ONLY diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index afb6f7c..6056bf2 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -23,37 +23,50 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.view.ActionMode; +import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.view.Window; import android.widget.SpinnerAdapter; import android.widget.Toolbar; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.widget.DecorToolbar; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.util.ArrayList; -import java.util.Map; public class ToolbarActionBar extends ActionBar { private Toolbar mToolbar; - private View mCustomView; - - private int mDisplayOptions; - - private int mNavResId; - private int mIconResId; - private int mLogoResId; - private Drawable mNavDrawable; - private Drawable mIconDrawable; - private Drawable mLogoDrawable; - private int mTitleResId; - private int mSubtitleResId; - private CharSequence mTitle; - private CharSequence mSubtitle; + private DecorToolbar mDecorToolbar; + private Window.Callback mWindowCallback; private boolean mLastMenuVisibility; private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = new ArrayList<OnMenuVisibilityListener>(); - public ToolbarActionBar(Toolbar toolbar) { + private final Runnable mMenuInvalidator = new Runnable() { + @Override + public void run() { + populateOptionsMenu(); + } + }; + + private final Toolbar.OnMenuItemClickListener mMenuClicker = + new Toolbar.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); + } + }; + + public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) { mToolbar = toolbar; + mDecorToolbar = new ToolbarWidgetWrapper(toolbar); + mWindowCallback = windowCallback; + toolbar.setOnMenuItemClickListener(mMenuClicker); + mDecorToolbar.setWindowTitle(title); } @Override @@ -63,19 +76,8 @@ public class ToolbarActionBar extends ActionBar { @Override public void setCustomView(View view, LayoutParams layoutParams) { - if (mCustomView != null) { - mToolbar.removeView(mCustomView); - } - mCustomView = view; - if (view != null) { - mToolbar.addView(view, generateLayoutParams(layoutParams)); - } - } - - private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) { - final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp); - result.gravity = lp.gravity; - return result; + view.setLayoutParams(layoutParams); + mDecorToolbar.setCustomView(view); } @Override @@ -86,48 +88,22 @@ public class ToolbarActionBar extends ActionBar { @Override public void setIcon(int resId) { - mIconResId = resId; - mIconDrawable = null; - updateToolbarLogo(); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable icon) { - mIconResId = 0; - mIconDrawable = icon; - updateToolbarLogo(); + mDecorToolbar.setIcon(icon); } @Override public void setLogo(int resId) { - mLogoResId = resId; - mLogoDrawable = null; - updateToolbarLogo(); + mDecorToolbar.setLogo(resId); } @Override public void setLogo(Drawable logo) { - mLogoResId = 0; - mLogoDrawable = logo; - updateToolbarLogo(); - } - - private void updateToolbarLogo() { - Drawable drawable = null; - if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) { - final int resId; - if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) { - resId = mLogoResId; - drawable = mLogoDrawable; - } else { - resId = mIconResId; - drawable = mIconDrawable; - } - if (resId != 0) { - drawable = mToolbar.getContext().getDrawable(resId); - } - } - mToolbar.setLogo(drawable); + mDecorToolbar.setLogo(logo); } @Override @@ -219,42 +195,22 @@ public class ToolbarActionBar extends ActionBar { @Override public void setTitle(CharSequence title) { - mTitle = title; - mTitleResId = 0; - updateToolbarTitle(); + mDecorToolbar.setTitle(title); } @Override public void setTitle(int resId) { - mTitleResId = resId; - mTitle = null; - updateToolbarTitle(); + mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); } @Override public void setSubtitle(CharSequence subtitle) { - mSubtitle = subtitle; - mSubtitleResId = 0; - updateToolbarTitle(); + mDecorToolbar.setSubtitle(subtitle); } @Override public void setSubtitle(int resId) { - mSubtitleResId = resId; - mSubtitle = null; - updateToolbarTitle(); - } - - private void updateToolbarTitle() { - final Context context = mToolbar.getContext(); - CharSequence title = null; - CharSequence subtitle = null; - if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) { - title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle; - subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle; - } - mToolbar.setTitle(title); - mToolbar.setSubtitle(subtitle); + mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); } @Override @@ -264,9 +220,8 @@ public class ToolbarActionBar extends ActionBar { @Override public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) { - final int oldOptions = mDisplayOptions; - mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask); - final int optionsChanged = oldOptions ^ mDisplayOptions; + mDecorToolbar.setDisplayOptions((options & mask) | + mDecorToolbar.getDisplayOptions() & ~mask); } @Override @@ -301,7 +256,7 @@ public class ToolbarActionBar extends ActionBar { @Override public View getCustomView() { - return mCustomView; + return mDecorToolbar.getCustomView(); } @Override @@ -327,7 +282,7 @@ public class ToolbarActionBar extends ActionBar { @Override public int getDisplayOptions() { - return mDisplayOptions; + return mDecorToolbar.getDisplayOptions(); } @Override @@ -425,6 +380,54 @@ public class ToolbarActionBar extends ActionBar { return mToolbar.getVisibility() == View.VISIBLE; } + @Override + public boolean openOptionsMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean invalidateOptionsMenu() { + mToolbar.removeCallbacks(mMenuInvalidator); + mToolbar.postOnAnimation(mMenuInvalidator); + return true; + } + + @Override + public boolean collapseActionView() { + if (mToolbar.hasExpandedActionView()) { + mToolbar.collapseActionView(); + return true; + } + return false; + } + + void populateOptionsMenu() { + final Menu menu = mToolbar.getMenu(); + final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; + if (mb != null) { + mb.stopDispatchingItemsChanged(); + } + try { + menu.clear(); + if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || + !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { + menu.clear(); + } + } finally { + if (mb != null) { + mb.startDispatchingItemsChanged(); + } + } + } + + @Override + public boolean onMenuKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP) { + openOptionsMenu(); + } + return true; + } + public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { mMenuVisibilityListeners.add(listener); } diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index a238ae3..c0b5b97 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -18,7 +18,10 @@ package com.android.internal.app; import android.animation.ValueAnimator; import android.content.res.TypedArray; +import android.view.ViewGroup; import android.view.ViewParent; +import android.widget.AdapterView; +import android.widget.Toolbar; import com.android.internal.R; import com.android.internal.view.ActionBarPolicy; import com.android.internal.view.menu.MenuBuilder; @@ -28,6 +31,7 @@ import com.android.internal.widget.ActionBarContainer; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.ActionBarOverlayLayout; import com.android.internal.widget.ActionBarView; +import com.android.internal.widget.DecorToolbar; import com.android.internal.widget.ScrollingTabContainerView; import android.animation.Animator; @@ -55,6 +59,7 @@ import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AnimationUtils; import android.widget.SpinnerAdapter; +import com.android.internal.widget.ToolbarWidgetWrapper; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -77,7 +82,7 @@ public class WindowDecorActionBar extends ActionBar implements private ActionBarOverlayLayout mOverlayLayout; private ActionBarContainer mContainerView; - private ActionBarView mActionView; + private DecorToolbar mDecorToolbar; private ActionBarContextView mContextView; private ActionBarContainer mSplitView; private View mContentView; @@ -187,7 +192,7 @@ public class WindowDecorActionBar extends ActionBar implements if (mOverlayLayout != null) { mOverlayLayout.setActionBarVisibilityCallback(this); } - mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); + mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar)); mContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); mContainerView = (ActionBarContainer) decor.findViewById( @@ -195,18 +200,17 @@ public class WindowDecorActionBar extends ActionBar implements mSplitView = (ActionBarContainer) decor.findViewById( com.android.internal.R.id.split_action_bar); - if (mActionView == null || mContextView == null || mContainerView == null) { + if (mDecorToolbar == null || mContextView == null || mContainerView == null) { throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + "with a compatible window decor layout"); } - mContext = mActionView.getContext(); - mActionView.setContextView(mContextView); - mContextDisplayMode = mActionView.isSplitActionBar() ? + mContext = mDecorToolbar.getContext(); + mContextDisplayMode = mDecorToolbar.isSplit() ? CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; // This was initially read from the action bar style - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; if (homeAsUp) { mDisplayHomeAsUpSet = true; @@ -225,6 +229,17 @@ public class WindowDecorActionBar extends ActionBar implements a.recycle(); } + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + public void onConfigurationChanged(Configuration newConfig) { setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); } @@ -233,11 +248,11 @@ public class WindowDecorActionBar extends ActionBar implements mHasEmbeddedTabs = hasEmbeddedTabs; // Switch tab layout configuration if needed if (!mHasEmbeddedTabs) { - mActionView.setEmbeddedTabView(null); + mDecorToolbar.setEmbeddedTabView(null); mContainerView.setTabContainer(mTabScrollView); } else { mContainerView.setTabContainer(null); - mActionView.setEmbeddedTabView(mTabScrollView); + mDecorToolbar.setEmbeddedTabView(mTabScrollView); } final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; if (mTabScrollView != null) { @@ -250,7 +265,7 @@ public class WindowDecorActionBar extends ActionBar implements mTabScrollView.setVisibility(View.GONE); } } - mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode); + mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); } @@ -263,7 +278,7 @@ public class WindowDecorActionBar extends ActionBar implements if (mHasEmbeddedTabs) { tabScroller.setVisibility(View.VISIBLE); - mActionView.setEmbeddedTabView(tabScroller); + mDecorToolbar.setEmbeddedTabView(tabScroller); } else { if (getNavigationMode() == NAVIGATION_MODE_TABS) { tabScroller.setVisibility(View.VISIBLE); @@ -326,7 +341,8 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setCustomView(int resId) { - setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false)); + setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, + mDecorToolbar.getViewGroup(), false)); } @Override @@ -356,7 +372,7 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setHomeButtonEnabled(boolean enable) { - mActionView.setHomeButtonEnabled(enable); + mDecorToolbar.setHomeButtonEnabled(enable); } @Override @@ -370,12 +386,12 @@ public class WindowDecorActionBar extends ActionBar implements } public void setSelectedNavigationItem(int position) { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: selectTab(mTabs.get(position)); break; case NAVIGATION_MODE_LIST: - mActionView.setDropdownSelectedPosition(position); + mDecorToolbar.setDropdownSelectedPosition(position); break; default: throw new IllegalStateException( @@ -399,26 +415,26 @@ public class WindowDecorActionBar extends ActionBar implements } public void setTitle(CharSequence title) { - mActionView.setTitle(title); + mDecorToolbar.setTitle(title); } public void setSubtitle(CharSequence subtitle) { - mActionView.setSubtitle(subtitle); + mDecorToolbar.setSubtitle(subtitle); } public void setDisplayOptions(int options) { if ((options & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions(options); + mDecorToolbar.setDisplayOptions(options); } public void setDisplayOptions(int options, int mask) { - final int current = mActionView.getDisplayOptions(); + final int current = mDecorToolbar.getDisplayOptions(); if ((mask & DISPLAY_HOME_AS_UP) != 0) { mDisplayHomeAsUpSet = true; } - mActionView.setDisplayOptions((options & mask) | (current & ~mask)); + mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask)); } public void setBackgroundDrawable(Drawable d) { @@ -436,23 +452,23 @@ public class WindowDecorActionBar extends ActionBar implements } public View getCustomView() { - return mActionView.getCustomNavigationView(); + return mDecorToolbar.getCustomView(); } public CharSequence getTitle() { - return mActionView.getTitle(); + return mDecorToolbar.getTitle(); } public CharSequence getSubtitle() { - return mActionView.getSubtitle(); + return mDecorToolbar.getSubtitle(); } public int getNavigationMode() { - return mActionView.getNavigationMode(); + return mDecorToolbar.getNavigationMode(); } public int getDisplayOptions() { - return mActionView.getDisplayOptions(); + return mDecorToolbar.getDisplayOptions(); } public ActionMode startActionMode(ActionMode.Callback callback) { @@ -572,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements return; } - final FragmentTransaction trans = mActionView.isInEditMode() ? null : + final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null : mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); if (mSelectedTab == tab) { @@ -828,13 +844,18 @@ public class WindowDecorActionBar extends ActionBar implements hideForActionMode(); } - mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); + mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); - if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) { + if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() && + isCollapsed(mDecorToolbar.getViewGroup())) { mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); } } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0; + } + public Context getThemedContext() { if (mThemedContext == null) { TypedValue outValue = new TypedValue(); @@ -854,27 +875,27 @@ public class WindowDecorActionBar extends ActionBar implements @Override public boolean isTitleTruncated() { - return mActionView != null && mActionView.isTitleTruncated(); + return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); } @Override public void setHomeAsUpIndicator(Drawable indicator) { - mActionView.setHomeAsUpIndicator(indicator); + mDecorToolbar.setNavigationIcon(indicator); } @Override public void setHomeAsUpIndicator(int resId) { - mActionView.setHomeAsUpIndicator(resId); + mDecorToolbar.setNavigationIcon(resId); } @Override public void setHomeActionContentDescription(CharSequence description) { - mActionView.setHomeActionContentDescription(description); + mDecorToolbar.setNavigationContentDescription(description); } @Override public void setHomeActionContentDescription(int resId) { - mActionView.setHomeActionContentDescription(resId); + mDecorToolbar.setNavigationContentDescription(resId); } @Override @@ -938,7 +959,8 @@ public class WindowDecorActionBar extends ActionBar implements // Clear out the context mode views after the animation finishes mContextView.closeMode(); - mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + mDecorToolbar.getViewGroup().sendAccessibilityEvent( + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); mActionMode = null; @@ -1178,28 +1200,27 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setCustomView(View view) { - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setCustomView(View view, LayoutParams layoutParams) { view.setLayoutParams(layoutParams); - mActionView.setCustomNavigationView(view); + mDecorToolbar.setCustomView(view); } @Override public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { - mActionView.setDropdownAdapter(adapter); - mActionView.setCallback(callback); + mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); } @Override public int getSelectedNavigationIndex() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mSelectedTab != null ? mSelectedTab.getPosition() : -1; case NAVIGATION_MODE_LIST: - return mActionView.getDropdownSelectedPosition(); + return mDecorToolbar.getDropdownSelectedPosition(); default: return -1; } @@ -1207,12 +1228,11 @@ public class WindowDecorActionBar extends ActionBar implements @Override public int getNavigationItemCount() { - switch (mActionView.getNavigationMode()) { + switch (mDecorToolbar.getNavigationMode()) { case NAVIGATION_MODE_TABS: return mTabs.size(); case NAVIGATION_MODE_LIST: - SpinnerAdapter adapter = mActionView.getDropdownAdapter(); - return adapter != null ? adapter.getCount() : 0; + return mDecorToolbar.getDropdownItemCount(); default: return 0; } @@ -1225,7 +1245,7 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setNavigationMode(int mode) { - final int oldMode = mActionView.getNavigationMode(); + final int oldMode = mDecorToolbar.getNavigationMode(); switch (oldMode) { case NAVIGATION_MODE_TABS: mSavedTabPosition = getSelectedNavigationIndex(); @@ -1238,7 +1258,7 @@ public class WindowDecorActionBar extends ActionBar implements mOverlayLayout.requestFitSystemWindows(); } } - mActionView.setNavigationMode(mode); + mDecorToolbar.setNavigationMode(mode); switch (mode) { case NAVIGATION_MODE_TABS: ensureTabsExist(); @@ -1249,7 +1269,7 @@ public class WindowDecorActionBar extends ActionBar implements } break; } - mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); + mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); } @@ -1261,30 +1281,30 @@ public class WindowDecorActionBar extends ActionBar implements @Override public void setIcon(int resId) { - mActionView.setIcon(resId); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable icon) { - mActionView.setIcon(icon); + mDecorToolbar.setIcon(icon); } public boolean hasIcon() { - return mActionView.hasIcon(); + return mDecorToolbar.hasIcon(); } @Override public void setLogo(int resId) { - mActionView.setLogo(resId); + mDecorToolbar.setLogo(resId); } @Override public void setLogo(Drawable logo) { - mActionView.setLogo(logo); + mDecorToolbar.setLogo(logo); } public boolean hasLogo() { - return mActionView.hasLogo(); + return mDecorToolbar.hasLogo(); } public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { @@ -1292,4 +1312,24 @@ public class WindowDecorActionBar extends ActionBar implements setDisplayHomeAsUpEnabled(enable); } } + + static class NavItemSelectedListener implements AdapterView.OnItemSelectedListener { + private final OnNavigationListener mListener; + + public NavItemSelectedListener(OnNavigationListener listener) { + mListener = listener; + } + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (mListener != null) { + mListener.onNavigationItemSelected(position, id); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + // Do nothing + } + } } diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java deleted file mode 100644 index 4c276b7..0000000 --- a/core/java/com/android/internal/backup/BackupConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.backup; - -/** - * Constants used internally between the backup manager and its transports - */ -public class BackupConstants { - public static final int TRANSPORT_OK = 0; - public static final int TRANSPORT_ERROR = 1; - public static final int TRANSPORT_NOT_INITIALIZED = 2; - public static final int AGENT_ERROR = 3; - public static final int AGENT_UNKNOWN = 4; -} diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 1e37fd9..d10451b 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -178,7 +178,7 @@ interface IBackupTransport { /** * Get the data for the application returned by {@link #nextRestorePackage}. * @param data An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #nextRestorePackage}. + * @return the same error codes as {@link #startRestore}. */ int getRestoreData(in ParcelFileDescriptor outFd); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 446ef55..7292116 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -18,6 +18,7 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; import android.app.backup.RestoreSet; import android.content.ComponentName; import android.content.Context; @@ -47,7 +48,7 @@ import static android.system.OsConstants.*; * later restoring from there. For testing only. */ -public class LocalTransport extends IBackupTransport.Stub { +public class LocalTransport extends BackupTransport { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; @@ -103,7 +104,7 @@ public class LocalTransport extends IBackupTransport.Stub { public int initializeDevice() { if (DEBUG) Log.v(TAG, "wiping all data"); deleteContents(mCurrentSetDir); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { @@ -165,7 +166,7 @@ public class LocalTransport extends IBackupTransport.Stub { entity.write(buf, 0, dataSize); } catch (IOException e) { Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } finally { entity.close(); } @@ -173,11 +174,11 @@ public class LocalTransport extends IBackupTransport.Stub { entityFile.delete(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { // oops, something went wrong. abort the operation and return error. Log.v(TAG, "Exception reading backup input:", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } @@ -207,17 +208,17 @@ public class LocalTransport extends IBackupTransport.Stub { } packageDir.delete(); } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } // Restore handling static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { + public RestoreSet[] getAvailableRestoreSets() { long[] existing = new long[POSSIBLE_SETS.length + 1]; int num = 0; @@ -248,7 +249,7 @@ public class LocalTransport extends IBackupTransport.Stub { mRestorePackage = -1; mRestoreToken = token; mRestoreDataDir = new File(mDataDir, Long.toString(token)); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public String nextRestorePackage() { @@ -280,7 +281,7 @@ public class LocalTransport extends IBackupTransport.Stub { ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error Log.e(TAG, "No keys for package: " + packageDir); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } // We expect at least some data if the directory exists in the first place @@ -301,10 +302,10 @@ public class LocalTransport extends IBackupTransport.Stub { in.close(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { Log.e(TAG, "Unable to read backup records", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java index d05699a..77ac313 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -32,6 +32,6 @@ public class LocalTransportService extends Service { @Override public IBinder onBind(Intent intent) { - return sTransport; + return sTransport.getBinder(); } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index ba419f9..dab3aff 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -20,6 +20,7 @@ import android.content.pm.PackageManager; import android.util.Slog; import java.io.File; +import java.io.IOException; /** * Native libraries helper. @@ -141,4 +142,18 @@ public class NativeLibraryHelper { return deletedFiles; } + + // We don't care about the other return values for now. + private static final int BITCODE_PRESENT = 1; + + public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException { + final int returnVal = hasRenderscriptBitcode(handle.apkHandle); + if (returnVal < 0) { + throw new IOException("Error scanning APK, code: " + returnVal); + } + + return (returnVal == BITCODE_PRESENT); + } + + private static native int hasRenderscriptBitcode(long apkHandle); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 495d5c6..7dbde69 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -33,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.TreeMap; /** @@ -116,6 +118,24 @@ public class InputMethodSubtypeSwitchingController { + " mIsSystemLanguage=" + mIsSystemLanguage + "}"; } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof ImeSubtypeListItem) { + final ImeSubtypeListItem that = (ImeSubtypeListItem)o; + if (!Objects.equals(this.mImi, that.mImi)) { + return false; + } + if (this.mSubtypeId != that.mSubtypeId) { + return false; + } + return true; + } + return false; + } } private static class InputMethodAndSubtypeList { @@ -211,54 +231,233 @@ public class InputMethodSubtypeSwitchingController { } } - private final InputMethodSettings mSettings; - private InputMethodAndSubtypeList mSubtypeList; + private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + } - @VisibleForTesting - public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return null; + private static class StaticRotationList { + private final List<ImeSubtypeListItem> mImeSubtypeList; + public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + mImeSubtypeList = imeSubtypeList; } - if (imList.size() <= 1) { - return null; - } - // Here we have two rotation groups, depending on the returned boolean value of - // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. - final boolean expectedValueOfSupportsSwitchingToNextInputMethod = - imi.supportsSwitchingToNextInputMethod(); - final int N = imList.size(); - final int currentSubtypeId = - subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, - subtype.hashCode()) : NOT_A_SUBTYPE_ID; - for (int i = 0; i < N; ++i) { - final ImeSubtypeListItem isli = imList.get(i); - // Skip until the current IME/subtype is found. - if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { - continue; - } - // Found the current IME/subtype. Start searching the next IME/subtype from here. - for (int j = 0; j < N - 1; ++j) { - final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); - // Skip if the candidate doesn't belong to the expected rotation group. - if (expectedValueOfSupportsSwitchingToNextInputMethod != - candidate.mImi.supportsSwitchingToNextInputMethod()) { - continue; + + /** + * Returns the index of the specified input method and subtype in the given list. + * @param imi The {@link InputMethodInfo} to be searched. + * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method + * does not have a subtype. + * @return The index in the given list. -1 if not found. + */ + private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + // Skip until the current IME/subtype is found. + if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + return i; } + } + return -1; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (mImeSubtypeList.size() <= 1) { + return null; + } + final int currentIndex = getIndex(imi, subtype); + if (currentIndex < 0) { + return null; + } + final int N = mImeSubtypeList.size(); + for (int offset = 1; offset < N; ++offset) { + // Start searching the next IME/subtype from the next of the current index. + final int candidateIndex = (currentIndex + offset) % N; + final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. - if (onlyCurrentIme && !candidate.mImi.equals(imi)) { + if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } - // No appropriate IME/subtype is found in the list. Give up. return null; } - // The current IME/subtype is not found in the list. Give up. - return null; } + private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + private final List<ImeSubtypeListItem> mImeSubtypeList; + private final int[] mUsageHistoryOfSubtypeListItemIndex; + + private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + mImeSubtypeList = imeSubtypeListItems; + mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; i++) { + mUsageHistoryOfSubtypeListItemIndex[i] = i; + } + } + + /** + * Returns the index of the specified object in + * {@link #mUsageHistoryOfSubtypeListItemIndex}. + * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" + * so as not to be confused with the index in {@link #mImeSubtypeList}. + * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. + */ + private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int usageRank = 0; usageRank < N; usageRank++) { + final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (subtypeListItem.mImi.equals(imi) && + subtypeListItem.mSubtypeId == currentSubtypeId) { + return usageRank; + } + } + // Not found in the known IME/Subtype list. + return -1; + } + + public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentUsageRank = getUsageRank(imi, subtype); + // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 + if (currentUsageRank <= 0) { + return; + } + final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank]; + System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0, + mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank); + mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + int currentUsageRank = getUsageRank(imi, subtype); + if (currentUsageRank < 0) { + if (DEBUG) { + Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype); + } + return null; + } + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int i = 1; i < N; i++) { + final int subtypeListItemRank = (currentUsageRank + i) % N; + final int subtypeListItemIndex = + mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { + continue; + } + return subtypeListItem; + } + return null; + } + } + + @VisibleForTesting + public static class ControllerImpl { + private final DynamicRotationList mSwitchingAwareRotationList; + private final StaticRotationList mSwitchingUnawareRotationList; + + public static ControllerImpl createFrom(final ControllerImpl currentInstance, + final List<ImeSubtypeListItem> sortedEnabledItems) { + DynamicRotationList switchingAwareRotationList = null; + { + final List<ImeSubtypeListItem> switchingAwareImeSubtypes = + filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingAwareRotationList != null && + Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } + if (switchingAwareRotationList == null) { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); + } + } + + StaticRotationList switchingUnawareRotationList = null; + { + final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( + sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingUnawareRotationList != null && + Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } + if (switchingUnawareRotationList == null) { + switchingUnawareRotationList = + new StaticRotationList(switchingUnawareImeSubtypes); + } + } + + return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); + } + + private ControllerImpl(final DynamicRotationList switchingAwareRotationList, + final StaticRotationList switchingUnawareRotationList) { + mSwitchingAwareRotationList = switchingAwareRotationList; + mSwitchingUnawareRotationList = switchingUnawareRotationList; + } + + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } + } + + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return; + } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } + } + + private static List<ImeSubtypeListItem> filterImeSubtypeList( + final List<ImeSubtypeListItem> items, + final boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int ALL_ITEMS_COUNT = items.size(); + for (int i = 0; i < ALL_ITEMS_COUNT; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() == + supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + } + + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeList mSubtypeList; + private ControllerImpl mController; + private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; resetCircularListLocked(context); @@ -269,19 +468,31 @@ public class InputMethodSubtypeSwitchingController { return new InputMethodSubtypeSwitchingController(settings, context); } - // TODO: write unit tests for this method and the logic that determines the next subtype public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - // TODO: Implement this. + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return; + } + mController.onUserActionLocked(imi, subtype); } public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); + mController = ControllerImpl.createFrom(mController, + mSubtypeList.getSortedInputMethodAndSubtypeList()); } - public ImeSubtypeListItem getNextInputMethodLocked( - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), - onlyCurrentIme, imi, subtype); + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return null; + } + return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 24e55e4..240d520 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -2006,6 +2006,11 @@ public final class BatteryStatsImpl extends BatteryStats { } } + @Override + public void commitCurrentHistoryBatchLocked() { + mHistoryLastWritten.cmd = HistoryItem.CMD_NULL; + } + void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) { if (!mHaveBatteryLevel || !mRecordingHistory) { return; @@ -2342,7 +2347,16 @@ public final class BatteryStatsImpl extends BatteryStats { // Only care about partial wake locks, since full wake locks // will be canceled when the user puts the screen to sleep. aggregateLastWakeupUptimeLocked(uptime); - historyName = historyName == null || mRecordAllWakeLocks ? name : historyName; + if (historyName == null) { + historyName = name; + } + if (mRecordAllWakeLocks) { + if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName, + uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, + HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid); + } + } if (mWakeLockNesting == 0) { mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " @@ -2352,7 +2366,8 @@ public final class BatteryStatsImpl extends BatteryStats { mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid; mWakeLockImportant = !unimportantForLogging; addHistoryRecordLocked(elapsedRealtime, uptime); - } else if (!mRecordAllWakeLocks && !mWakeLockImportant && !unimportantForLogging) { + } else if (!mWakeLockImportant && !unimportantForLogging + && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) { if (mHistoryLastWritten.wakelockTag != null) { // We'll try to update the last tag. mHistoryLastWritten.wakelockTag = null; @@ -2362,14 +2377,6 @@ public final class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); } mWakeLockImportant = true; - } else if (mRecordAllWakeLocks) { - if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName, - uid, 0)) { - mWakeLockNesting++; - return; - } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_WAKE_LOCK_START, - historyName, uid); } mWakeLockNesting++; } @@ -2387,28 +2394,23 @@ public final class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (type == WAKE_TYPE_PARTIAL) { mWakeLockNesting--; - historyName = historyName == null || mRecordAllWakeLocks ? name : historyName; + if (mRecordAllWakeLocks) { + if (historyName == null) { + historyName = name; + } + if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, + uid, 0)) { + addHistoryEventLocked(elapsedRealtime, uptime, + HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid); + } + } if (mWakeLockNesting == 0) { mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + Integer.toHexString(mHistoryCur.states)); - if (mRecordAllWakeLocks - || (historyName != null && !historyName.equals(mInitialAcquireWakeName)) - || uid != mInitialAcquireWakeUid) { - mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag; - mHistoryCur.wakelockTag.string = historyName; - mHistoryCur.wakelockTag.uid = uid; - } mInitialAcquireWakeName = null; mInitialAcquireWakeUid = -1; addHistoryRecordLocked(elapsedRealtime, uptime); - } else if (mRecordAllWakeLocks) { - if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, - uid, 0)) { - return; - } - addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_WAKE_LOCK_FINISH, - historyName, uid); } } if (uid >= 0) { diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index 40834ba..17685fd 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -22,9 +22,6 @@ import android.os.Looper; import android.os.Message; public class HandlerCaller { - - public final Context mContext; - final Looper mMainLooper; final Handler mH; @@ -47,7 +44,6 @@ public class HandlerCaller { public HandlerCaller(Context context, Looper looper, Callback callback, boolean asyncHandler) { - mContext = context; mMainLooper = looper != null ? looper : context.getMainLooper(); mH = new MyHandler(mMainLooper, asyncHandler); mCallback = callback; diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index b78c70f..a5421f5 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -56,4 +56,13 @@ interface IKeyguardService { oneway void dispatch(in MotionEvent event); oneway void launchCamera(); oneway void onBootCompleted(); + + /** + * Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper + * and keyguard flag. + * + * @param startTime the start time of the animation in uptime milliseconds + * @param fadeoutDuration the duration of the exit animation, in milliseconds + */ + oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index a56fa36..d66ef83 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -169,6 +169,15 @@ public class ArrayUtils return false; } + public static boolean contains(long[] array, long value) { + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(long[] array) { long total = 0; for (long value : array) { @@ -229,6 +238,14 @@ public class ArrayUtils return array; } + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ public static int[] appendInt(int[] cur, int val) { if (cur == null) { return new int[] { val }; @@ -264,4 +281,48 @@ public class ArrayUtils } return cur; } + + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ + public static long[] appendLong(long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + public static long[] removeLong(long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } } diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index f6722a6..c0d1e88 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -183,6 +183,33 @@ public class Preconditions { } /** + * Ensures that the argument int value is within the inclusive range. + * + * @param value a int value + * @param lower the lower endpoint of the inclusive range + * @param upper the upper endpoint of the inclusive range + * @param valueName the name of the argument to use if the check fails + * + * @return the validated int value + * + * @throws IllegalArgumentException if {@code value} was not within the range + */ + public static int checkArgumentInRange(int value, int lower, int upper, + String valueName) { + if (value < lower) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too low)", valueName, lower, upper)); + } else if (value > upper) { + throw new IllegalArgumentException( + String.format( + "%s is out of range of [%d, %d] (too high)", valueName, lower, upper)); + } + + return value; + } + + /** * Ensures that the array is not {@code null}, and none if its elements are {@code null}. * * @param value an array of boxed objects diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 81e67d8..af966b1 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -57,9 +57,9 @@ public class Protocol { public static final int BASE_DNS_PINGER = 0x00050000; public static final int BASE_NSD_MANAGER = 0x00060000; public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000; - public static final int BASE_CONNECTIVITY_SERVICE = 0x00080000; + public static final int BASE_CONNECTIVITY_MANAGER = 0x00080000; public static final int BASE_NETWORK_AGENT = 0x00081000; public static final int BASE_NETWORK_MONITOR = 0x00082000; - public static final int BASE_CONNECTIVITY_MANAGER = 0x00083000; + public static final int BASE_NETWORK_FACTORY = 0x00083000; //TODO: define all used protocols } diff --git a/core/java/com/android/internal/util/VirtualRefBasePtr.java b/core/java/com/android/internal/util/VirtualRefBasePtr.java index 0bd4d3a..52306f1 100644 --- a/core/java/com/android/internal/util/VirtualRefBasePtr.java +++ b/core/java/com/android/internal/util/VirtualRefBasePtr.java @@ -32,11 +32,17 @@ public final class VirtualRefBasePtr { return mNativePtr; } + public void release() { + if (mNativePtr != 0) { + nDecStrong(mNativePtr); + mNativePtr = 0; + } + } + @Override protected void finalize() throws Throwable { try { - nDecStrong(mNativePtr); - mNativePtr = 0; + release(); } finally { super.finalize(); } diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index 5b59599..dca9921 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -220,28 +220,74 @@ public class XmlUtils { * @see #readMapXml */ public static final void writeMapXml(Map val, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeMapXml(val, name, out, null); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * @param callback Method to call when an Object type is not recognized. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { out.startTag(null, "null"); out.endTag(null, "null"); return; } - Set s = val.entrySet(); - Iterator i = s.iterator(); - out.startTag(null, "map"); if (name != null) { out.attribute(null, "name", name); } + writeMapXml(val, out, callback); + + out.endTag(null, "map"); + } + + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). This method presumes that the start tag and + * name attribute have already been written and does not write an end tag. + * + * @param val The map to be flattened. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + * + * @hide + */ + public static final void writeMapXml(Map val, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { + if (val == null) { + return; + } + + Set s = val.entrySet(); + Iterator i = s.iterator(); + while (i.hasNext()) { Map.Entry e = (Map.Entry)i.next(); - writeValueXml(e.getValue(), (String)e.getKey(), out); + writeValueXml(e.getValue(), (String)e.getKey(), out, callback); } - - out.endTag(null, "map"); } /** @@ -387,6 +433,123 @@ public class XmlUtils { } /** + * Flatten a long[] into an XmlSerializer. The list can later be read back + * with readThisLongArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeLongArrayXml(long[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "long-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Long.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "long-array"); + } + + /** + * Flatten a double[] into an XmlSerializer. The list can later be read back + * with readThisDoubleArrayXml(). + * + * @param val The double array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeDoubleArrayXml(double[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "double-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", Double.toString(val[i])); + out.endTag(null, "item"); + } + + out.endTag(null, "double-array"); + } + + /** + * Flatten a String[] into an XmlSerializer. The list can later be read back + * with readThisStringArrayXml(). + * + * @param val The long array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeStringArrayXml(String[] val, String name, XmlSerializer out) + throws XmlPullParserException, java.io.IOException { + + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "string-array"); + if (name != null) { + out.attribute(null, "name", name); + } + + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + + for (int i=0; i<N; i++) { + out.startTag(null, "item"); + out.attribute(null, "value", val[i]); + out.endTag(null, "item"); + } + + out.endTag(null, "string-array"); + } + + /** * Flatten an object's value into an XmlSerializer. The value can later * be read back with readThisValueXml(). * @@ -403,8 +566,29 @@ public class XmlUtils { * @see #readValueXml */ public static final void writeValueXml(Object v, String name, XmlSerializer out) - throws XmlPullParserException, java.io.IOException - { + throws XmlPullParserException, java.io.IOException { + writeValueXml(v, name, out, null); + } + + /** + * Flatten an object's value into an XmlSerializer. The value can later + * be read back with readThisValueXml(). + * + * Currently supported value types are: null, String, Integer, Long, + * Float, Double Boolean, Map, List. + * + * @param v The object to be flattened. + * @param name Name attribute to include with this value's tag, or null + * for none. + * @param out XmlSerializer to write the object into. + * @param callback Handler for Object types not recognized. + * + * @see #writeMapXml + * @see #writeListXml + * @see #readValueXml + */ + private static final void writeValueXml(Object v, String name, XmlSerializer out, + WriteMapCallback callback) throws XmlPullParserException, java.io.IOException { String typeStr; if (v == null) { out.startTag(null, "null"); @@ -437,14 +621,23 @@ public class XmlUtils { } else if (v instanceof int[]) { writeIntArrayXml((int[])v, name, out); return; + } else if (v instanceof long[]) { + writeLongArrayXml((long[])v, name, out); + return; + } else if (v instanceof double[]) { + writeDoubleArrayXml((double[])v, name, out); + return; + } else if (v instanceof String[]) { + writeStringArrayXml((String[])v, name, out); + return; } else if (v instanceof Map) { writeMapXml((Map)v, name, out); return; } else if (v instanceof List) { - writeListXml((List)v, name, out); + writeListXml((List) v, name, out); return; } else if (v instanceof Set) { - writeSetXml((Set)v, name, out); + writeSetXml((Set) v, name, out); return; } else if (v instanceof CharSequence) { // XXX This is to allow us to at least write something if @@ -457,6 +650,9 @@ public class XmlUtils { out.text(v.toString()); out.endTag(null, "string"); return; + } else if (callback != null) { + callback.writeUnknownObject(v, name, out); + return; } else { throw new RuntimeException("writeValueXml: unable to write value " + v); } @@ -550,14 +746,35 @@ public class XmlUtils { * @see #readMapXml */ public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, - String[] name) throws XmlPullParserException, java.io.IOException + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisMapXml(parser, endTag, name, null); + } + + /** + * Read a HashMap object from an XmlPullParser. The XML data could + * previously have been generated by writeMapXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + * @hide + */ + public static final HashMap<String, ?> readThisMapXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { HashMap<String, Object> map = new HashMap<String, Object>(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); map.put(name[0], val); } else if (eventType == parser.END_TAG) { if (parser.getName().equals(endTag)) { @@ -587,15 +804,34 @@ public class XmlUtils { * * @see #readListXml */ - public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException - { + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + return readThisListXml(parser, endTag, name, null); + } + + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + private static final ArrayList readThisListXml(XmlPullParser parser, String endTag, + String[] name, ReadMapCallback callback) + throws XmlPullParserException, java.io.IOException { ArrayList list = new ArrayList(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); list.add(val); //System.out.println("Adding to list: " + val); } else if (eventType == parser.END_TAG) { @@ -611,7 +847,29 @@ public class XmlUtils { throw new XmlPullParserException( "Document ended before " + endTag + " end tag"); } - + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * <em>after</em> the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws java.io.IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + return readThisSetXml(parser, endTag, name, null); + } + /** * Read a HashSet object from an XmlPullParser. The XML data could previously * have been generated by writeSetXml(). The XmlPullParser must be positioned @@ -628,15 +886,16 @@ public class XmlUtils { * @throws java.io.IOException * * @see #readSetXml + * @hide */ - public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) - throws XmlPullParserException, java.io.IOException { + private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { HashSet set = new HashSet(); int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - Object val = readThisValueXml(parser, name); + Object val = readThisValueXml(parser, name, callback); set.add(val); //System.out.println("Adding to set: " + val); } else if (eventType == parser.END_TAG) { @@ -681,6 +940,7 @@ public class XmlUtils { throw new XmlPullParserException( "Not a number in num attribute in byte-array"); } + parser.next(); int[] array = new int[num]; int i = 0; @@ -722,6 +982,187 @@ public class XmlUtils { } /** + * Read a long[] object from an XmlPullParser. The XML data could + * previously have been generated by writeLongArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated long[]. + * + * @see #readListXml + */ + public static final long[] readThisLongArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in long-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in long-array"); + } + parser.next(); + + long[] array = new long[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Long.parseLong(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a double[] object from an XmlPullParser. The XML data could + * previously have been generated by writeDoubleArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "double-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated double[]. + * + * @see #readListXml + */ + public static final double[] readThisDoubleArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in double-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in double-array"); + } + parser.next(); + + double[] array = new double[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Double.parseDouble(parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** + * Read a String[] object from an XmlPullParser. The XML data could + * previously have been generated by writeStringArrayXml(). The XmlPullParser + * must be positioned <em>after</em> the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "string-array". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated String[]. + * + * @see #readListXml + */ + public static final String[] readThisStringArrayXml(XmlPullParser parser, String endTag, + String[] name) throws XmlPullParserException, java.io.IOException { + + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need num attribute in string-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in num attribute in string-array"); + } + parser.next(); + + String[] array = new String[num]; + int i = 0; + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = parser.getAttributeValue(null, "value"); + } catch (NullPointerException e) { + throw new XmlPullParserException("Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException("Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException("Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException("Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException("Document ended before " + endTag + " end tag"); + } + + /** * Read a flattened object from an XmlPullParser. The XML data could * previously have been written with writeMapXml(), writeListXml(), or * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the @@ -743,7 +1184,7 @@ public class XmlUtils { int eventType = parser.getEventType(); do { if (eventType == parser.START_TAG) { - return readThisValueXml(parser, name); + return readThisValueXml(parser, name, null); } else if (eventType == parser.END_TAG) { throw new XmlPullParserException( "Unexpected end tag at: " + parser.getName()); @@ -758,9 +1199,8 @@ public class XmlUtils { "Unexpected end of document"); } - private static final Object readThisValueXml(XmlPullParser parser, String[] name) - throws XmlPullParserException, java.io.IOException - { + private static final Object readThisValueXml(XmlPullParser parser, String[] name, + ReadMapCallback callback) throws XmlPullParserException, java.io.IOException { final String valueName = parser.getAttributeValue(null, "name"); final String tagName = parser.getName(); @@ -794,11 +1234,25 @@ public class XmlUtils { } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) { // all work already done by readThisPrimitiveValueXml } else if (tagName.equals("int-array")) { - parser.next(); res = readThisIntArrayXml(parser, "int-array", name); name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (tagName.equals("long-array")) { + res = readThisLongArrayXml(parser, "long-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("double-array")) { + res = readThisDoubleArrayXml(parser, "double-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("string-array")) { + res = readThisStringArrayXml(parser, "string-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; } else if (tagName.equals("map")) { parser.next(); res = readThisMapXml(parser, "map", name); @@ -817,9 +1271,12 @@ public class XmlUtils { name[0] = valueName; //System.out.println("Returning value for " + valueName + ": " + res); return res; + } else if (callback != null) { + res = callback.readThisUnknownObjectXml(parser, tagName); + name[0] = valueName; + return res; } else { - throw new XmlPullParserException( - "Unknown tag: " + tagName); + throw new XmlPullParserException("Unknown tag: " + tagName); } // Skip through to end tag. @@ -967,4 +1424,39 @@ public class XmlUtils { throws IOException { out.attribute(null, name, Boolean.toString(value)); } + + /** @hide */ + public interface WriteMapCallback { + /** + * Called from writeMapXml when an Object type is not recognized. The implementer + * must write out the entire element including start and end tags. + * + * @param v The object to be written out + * @param name The mapping key for v. Must be written into the "name" attribute of the + * start tag. + * @param out The XML output stream. + * @throws XmlPullParserException on unrecognized Object type. + * @throws IOException on XmlSerializer serialization errors. + * @hide + */ + public void writeUnknownObject(Object v, String name, XmlSerializer out) + throws XmlPullParserException, IOException; + } + + /** @hide */ + public interface ReadMapCallback { + /** + * Called from readThisMapXml when a START_TAG is not recognized. The input stream + * is positioned within the start tag so that attributes can be read using in.getAttribute. + * + * @param in the XML input stream + * @param tag the START_TAG that was not recognized. + * @return the Object parsed from the stream which will be put into the map. + * @throws XmlPullParserException if the START_TAG is not recognized. + * @throws IOException on XmlPullParser serialization errors. + * @hide + */ + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException; + } } diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index 183478f..9e7ff93 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -34,7 +34,7 @@ import android.view.animation.DecelerateInterpolator; public abstract class AbsActionBarView extends ViewGroup { protected ActionMenuView mMenuView; protected ActionMenuPresenter mActionMenuPresenter; - protected ActionBarContainer mSplitView; + protected ViewGroup mSplitView; protected boolean mSplitActionBar; protected boolean mSplitWhenNarrow; protected int mContentHeight; @@ -74,7 +74,7 @@ public abstract class AbsActionBarView extends ViewGroup { setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); a.recycle(); if (mSplitWhenNarrow) { - setSplitActionBar(getContext().getResources().getBoolean( + setSplitToolbar(getContext().getResources().getBoolean( com.android.internal.R.bool.split_action_bar_is_narrow)); } if (mActionMenuPresenter != null) { @@ -86,7 +86,7 @@ public abstract class AbsActionBarView extends ViewGroup { * Sets whether the bar should be split right now, no questions asked. * @param split true if the bar should split */ - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { mSplitActionBar = split; } @@ -107,7 +107,7 @@ public abstract class AbsActionBarView extends ViewGroup { return mContentHeight; } - public void setSplitView(ActionBarContainer splitView) { + public void setSplitView(ViewGroup splitView) { mSplitView = splitView; } @@ -214,6 +214,10 @@ public abstract class AbsActionBarView extends ViewGroup { return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved(); } + public boolean canShowOverflowMenu() { + return isOverflowReserved() && getVisibility() == VISIBLE; + } + public void dismissPopupMenus() { if (mActionMenuPresenter != null) { mActionMenuPresenter.dismissPopupMenus(); diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index ed07514..790b611 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -36,7 +36,7 @@ import android.widget.FrameLayout; public class ActionBarContainer extends FrameLayout { private boolean mIsTransitioning; private View mTabContainer; - private ActionBarView mActionBarView; + private View mActionBarView; private Drawable mBackground; private Drawable mStackedBackground; @@ -76,7 +76,7 @@ public class ActionBarContainer extends FrameLayout { @Override public void onFinishInflate() { super.onFinishInflate(); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mActionBarView = findViewById(com.android.internal.R.id.action_bar); } public void setPrimaryBackground(Drawable bg) { @@ -251,6 +251,10 @@ public class ActionBarContainer extends FrameLayout { return null; } + private boolean isCollapsed(View view) { + return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0; + } + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mActionBarView == null && @@ -263,7 +267,7 @@ public class ActionBarContainer extends FrameLayout { if (mActionBarView == null) return; final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams(); - final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 : + final int actionBarViewHeight = isCollapsed(mActionBarView) ? 0 : mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { @@ -298,9 +302,8 @@ public class ActionBarContainer extends FrameLayout { } } else { if (mBackground != null) { - final ActionBarView actionBarView = mActionBarView; - mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(), - actionBarView.getRight(), actionBarView.getBottom()); + mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), + mActionBarView.getRight(), mActionBarView.getBottom()); needsInvalidate = true; } mIsStacked = hasTabs; diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index e10070f..6ff77a0 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -83,7 +83,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes); - setBackgroundDrawable(a.getDrawable( + setBackground(a.getDrawable( com.android.internal.R.styleable.ActionMode_background)); mTitleStyleRes = a.getResourceId( com.android.internal.R.styleable.ActionMode_titleTextStyle, 0); @@ -109,7 +109,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi } @Override - public void setSplitActionBar(boolean split) { + public void setSplitToolbar(boolean split) { if (mSplitActionBar != split) { if (mActionMenuPresenter != null) { // Mode is already active; move everything over and adjust the menu itself. @@ -137,7 +137,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi mSplitView.addView(mMenuView, layoutParams); } } - super.setSplitActionBar(split); + super.setSplitToolbar(split); } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 7ab4bed..8a9cb22 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -39,6 +39,7 @@ import android.view.ViewPropertyAnimator; import android.view.Window; import android.view.WindowInsets; import android.widget.OverScroller; +import android.widget.Toolbar; import com.android.internal.view.menu.MenuPresenter; /** @@ -59,7 +60,7 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar private ActionBarContainer mActionBarTop; // Some interior UI elements. - private ActionBarView mActionBarView; + private DecorToolbar mDecorToolbar; // Content overlay drawable - generally the action bar's shadow private Drawable mWindowContentOverlay; @@ -401,7 +402,7 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar topInset = mActionBarTop.getMeasuredHeight(); } - if (mActionBarView.isSplitActionBar()) { + if (mDecorToolbar.isSplit()) { // If action bar is split, adjust bottom insets for it. if (mActionBarBottom != null) { if (stable) { @@ -563,12 +564,23 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar mContent = findViewById(com.android.internal.R.id.content); mActionBarTop = (ActionBarContainer) findViewById( com.android.internal.R.id.action_bar_container); - mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar)); mActionBarBottom = (ActionBarContainer) findViewById( com.android.internal.R.id.split_action_bar); } } + private DecorToolbar getDecorToolbar(View view) { + if (view instanceof DecorToolbar) { + return (DecorToolbar) view; + } else if (view instanceof Toolbar) { + return ((Toolbar) view).getWrapper(); + } else { + throw new IllegalStateException("Can't make a decor toolbar out of " + + view.getClass().getSimpleName()); + } + } + public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { if (hideOnContentScroll != mHideOnContentScroll) { mHideOnContentScroll = hideOnContentScroll; @@ -648,9 +660,9 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar final int action = event.getAction(); // Collapse any expanded action views. - if (mActionBarView != null && mActionBarView.hasExpandedActionView()) { + if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { if (action == KeyEvent.ACTION_UP) { - mActionBarView.collapseActionView(); + mDecorToolbar.collapseActionView(); } return true; } @@ -662,19 +674,19 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar @Override public void setWindowCallback(Window.Callback cb) { pullChildren(); - mActionBarView.setWindowCallback(cb); + mDecorToolbar.setWindowCallback(cb); } @Override public void setWindowTitle(CharSequence title) { pullChildren(); - mActionBarView.setWindowTitle(title); + mDecorToolbar.setWindowTitle(title); } @Override public CharSequence getTitle() { pullChildren(); - return mActionBarView.getTitle(); + return mDecorToolbar.getTitle(); } @Override @@ -682,10 +694,10 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar pullChildren(); switch (windowFeature) { case Window.FEATURE_PROGRESS: - mActionBarView.initProgress(); + mDecorToolbar.initProgress(); break; case Window.FEATURE_INDETERMINATE_PROGRESS: - mActionBarView.initIndeterminateProgress(); + mDecorToolbar.initIndeterminateProgress(); break; case Window.FEATURE_ACTION_BAR_OVERLAY: setOverlayMode(true); @@ -704,15 +716,15 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } if (splitActionBar) { pullChildren(); - if (mActionBarBottom != null) { - mActionBarView.setSplitView(mActionBarBottom); - mActionBarView.setSplitActionBar(splitActionBar); - mActionBarView.setSplitWhenNarrow(splitWhenNarrow); + if (mActionBarBottom != null && mDecorToolbar.canSplit()) { + mDecorToolbar.setSplitView(mActionBarBottom); + mDecorToolbar.setSplitToolbar(splitActionBar); + mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow); final ActionBarContextView cab = (ActionBarContextView) findViewById( com.android.internal.R.id.action_context_bar); cab.setSplitView(mActionBarBottom); - cab.setSplitActionBar(splitActionBar); + cab.setSplitToolbar(splitActionBar); cab.setSplitWhenNarrow(splitWhenNarrow); } else if (splitActionBar) { Log.e(TAG, "Requested split action bar with " + @@ -724,91 +736,91 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar @Override public boolean hasIcon() { pullChildren(); - return mActionBarView.hasIcon(); + return mDecorToolbar.hasIcon(); } @Override public boolean hasLogo() { pullChildren(); - return mActionBarView.hasLogo(); + return mDecorToolbar.hasLogo(); } @Override public void setIcon(int resId) { pullChildren(); - mActionBarView.setIcon(resId); + mDecorToolbar.setIcon(resId); } @Override public void setIcon(Drawable d) { pullChildren(); - mActionBarView.setIcon(d); + mDecorToolbar.setIcon(d); } @Override public void setLogo(int resId) { pullChildren(); - mActionBarView.setLogo(resId); + mDecorToolbar.setLogo(resId); } @Override public boolean canShowOverflowMenu() { pullChildren(); - return mActionBarView.isOverflowReserved() && mActionBarView.getVisibility() == VISIBLE; + return mDecorToolbar.canShowOverflowMenu(); } @Override public boolean isOverflowMenuShowing() { pullChildren(); - return mActionBarView.isOverflowMenuShowing(); + return mDecorToolbar.isOverflowMenuShowing(); } @Override public boolean isOverflowMenuShowPending() { pullChildren(); - return mActionBarView.isOverflowMenuShowPending(); + return mDecorToolbar.isOverflowMenuShowPending(); } @Override public boolean showOverflowMenu() { pullChildren(); - return mActionBarView.showOverflowMenu(); + return mDecorToolbar.showOverflowMenu(); } @Override public boolean hideOverflowMenu() { pullChildren(); - return mActionBarView.hideOverflowMenu(); + return mDecorToolbar.hideOverflowMenu(); } @Override public void setMenuPrepared() { pullChildren(); - mActionBarView.setMenuPrepared(); + mDecorToolbar.setMenuPrepared(); } @Override public void setMenu(Menu menu, MenuPresenter.Callback cb) { pullChildren(); - mActionBarView.setMenu(menu, cb); + mDecorToolbar.setMenu(menu, cb); } @Override public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { pullChildren(); - mActionBarView.saveHierarchyState(toolbarStates); + mDecorToolbar.saveHierarchyState(toolbarStates); } @Override public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { pullChildren(); - mActionBarView.restoreHierarchyState(toolbarStates); + mDecorToolbar.restoreHierarchyState(toolbarStates); } @Override public void dismissPopups() { pullChildren(); - mActionBarView.dismissPopupMenus(); + mDecorToolbar.dismissPopupMenus(); } public static class LayoutParams extends MarginLayoutParams { diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 60631b9..af82778 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -18,7 +18,6 @@ package com.android.internal.widget; import android.animation.LayoutTransition; import android.app.ActionBar; -import android.app.ActionBar.OnNavigationListener; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -63,7 +62,7 @@ import com.android.internal.view.menu.SubMenuBuilder; /** * @hide */ -public class ActionBarView extends AbsActionBarView { +public class ActionBarView extends AbsActionBarView implements DecorToolbar { private static final String TAG = "ActionBarView"; /** @@ -117,8 +116,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mUserTitle; private boolean mIncludeTabs; - private boolean mIsCollapsable; - private boolean mIsCollapsed; + private boolean mIsCollapsible; private boolean mWasHomeEnabled; // Was it enabled before action view expansion? private MenuBuilder mOptionsMenu; @@ -129,7 +127,7 @@ public class ActionBarView extends AbsActionBarView { private ActionMenuItem mLogoNavItem; private SpinnerAdapter mSpinnerAdapter; - private OnNavigationListener mCallback; + private AdapterView.OnItemSelectedListener mNavItemSelectedListener; private Runnable mTabSelector; @@ -138,18 +136,6 @@ public class ActionBarView extends AbsActionBarView { Window.Callback mWindowCallback; - private final AdapterView.OnItemSelectedListener mNavItemSelectedListener = - new AdapterView.OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (mCallback != null) { - mCallback.onNavigationItemSelected(position, id); - } - } - public void onNothingSelected(AdapterView parent) { - // Do nothing - } - }; - private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() { @Override public void onClick(View v) { @@ -178,8 +164,6 @@ public class ActionBarView extends AbsActionBarView { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, com.android.internal.R.attr.actionBarStyle, 0); - ApplicationInfo appInfo = context.getApplicationInfo(); - PackageManager pm = context.getPackageManager(); mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD); mTitle = a.getText(R.styleable.ActionBar_title); @@ -260,7 +244,7 @@ public class ActionBarView extends AbsActionBarView { } if (mHomeDescriptionRes != 0) { - setHomeActionContentDescription(mHomeDescriptionRes); + setNavigationContentDescription(mHomeDescriptionRes); } if (mTabScrollView != null && mIncludeTabs) { @@ -313,7 +297,7 @@ public class ActionBarView extends AbsActionBarView { } @Override - public void setSplitActionBar(boolean splitActionBar) { + public void setSplitToolbar(boolean splitActionBar) { if (mSplitActionBar != splitActionBar) { if (mMenuView != null) { final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); @@ -349,18 +333,26 @@ public class ActionBarView extends AbsActionBarView { mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); } } - super.setSplitActionBar(splitActionBar); + super.setSplitToolbar(splitActionBar); } } - public boolean isSplitActionBar() { + public boolean isSplit() { return mSplitActionBar; } + public boolean canSplit() { + return true; + } + public boolean hasEmbeddedTabs() { return mIncludeTabs; } + public void setEmbeddedTabView(View view) { + setEmbeddedTabView((ScrollingTabContainerView) view); + } + public void setEmbeddedTabView(ScrollingTabContainerView tabs) { if (mTabScrollView != null) { removeView(mTabScrollView); @@ -376,10 +368,6 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCallback(OnNavigationListener callback) { - mCallback = callback; - } - public void setMenuPrepared() { mMenuPrepared = true; } @@ -473,7 +461,7 @@ public class ActionBarView extends AbsActionBarView { } } - public void setCustomNavigationView(View view) { + public void setCustomView(View view) { final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0; if (showCustom) { ActionBarTransition.beginDelayedTransition(this); @@ -765,15 +753,16 @@ public class ActionBarView extends AbsActionBarView { } } - public void setDropdownAdapter(SpinnerAdapter adapter) { + public void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener l) { mSpinnerAdapter = adapter; + mNavItemSelectedListener = l; if (mSpinner != null) { mSpinner.setAdapter(adapter); } } - public SpinnerAdapter getDropdownAdapter() { - return mSpinnerAdapter; + public int getDropdownItemCount() { + return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0; } public void setDropdownSelectedPosition(int position) { @@ -784,7 +773,7 @@ public class ActionBarView extends AbsActionBarView { return mSpinner.getSelectedItemPosition(); } - public View getCustomNavigationView() { + public View getCustomView() { return mCustomNavView; } @@ -797,6 +786,11 @@ public class ActionBarView extends AbsActionBarView { } @Override + public ViewGroup getViewGroup() { + return this; + } + + @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { // Used by custom nav views if they don't supply layout params. Everything else // added to an ActionBarView should have them already. @@ -860,12 +854,8 @@ public class ActionBarView extends AbsActionBarView { mContextView = view; } - public void setCollapsable(boolean collapsable) { - mIsCollapsable = collapsable; - } - - public boolean isCollapsed() { - return mIsCollapsed; + public void setCollapsible(boolean collapsible) { + mIsCollapsible = collapsible; } /** @@ -893,7 +883,7 @@ public class ActionBarView extends AbsActionBarView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int childCount = getChildCount(); - if (mIsCollapsable) { + if (mIsCollapsible) { int visibleChildren = 0; for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); @@ -915,11 +905,9 @@ public class ActionBarView extends AbsActionBarView { if (visibleChildren == 0) { // No size for an empty action bar when collapsable. setMeasuredDimension(0, 0); - mIsCollapsed = true; return; } } - mIsCollapsed = false; int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { @@ -1323,20 +1311,20 @@ public class ActionBarView extends AbsActionBarView { } } - public void setHomeAsUpIndicator(Drawable indicator) { + public void setNavigationIcon(Drawable indicator) { mHomeLayout.setUpIndicator(indicator); } - public void setHomeAsUpIndicator(int resId) { + public void setNavigationIcon(int resId) { mHomeLayout.setUpIndicator(resId); } - public void setHomeActionContentDescription(CharSequence description) { + public void setNavigationContentDescription(CharSequence description) { mHomeDescription = description; updateHomeAccessibility(mUpGoerFive.isEnabled()); } - public void setHomeActionContentDescription(int resId) { + public void setNavigationContentDescription(int resId) { mHomeDescriptionRes = resId; mHomeDescription = resId != 0 ? getResources().getText(resId) : null; updateHomeAccessibility(mUpGoerFive.isEnabled()); diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java new file mode 100644 index 0000000..ee6988e --- /dev/null +++ b/core/java/com/android/internal/widget/DecorToolbar.java @@ -0,0 +1,94 @@ +/* + * 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.internal.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.util.SparseArray; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.SpinnerAdapter; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Common interface for a toolbar that sits as part of the window decor. + * Layouts that control window decor use this as a point of interaction with different + * bar implementations. + * + * @hide + */ +public interface DecorToolbar { + ViewGroup getViewGroup(); + Context getContext(); + boolean isSplit(); + boolean hasExpandedActionView(); + void collapseActionView(); + void setWindowCallback(Window.Callback cb); + void setWindowTitle(CharSequence title); + CharSequence getTitle(); + void setTitle(CharSequence title); + CharSequence getSubtitle(); + void setSubtitle(CharSequence subtitle); + void initProgress(); + void initIndeterminateProgress(); + boolean canSplit(); + void setSplitView(ViewGroup splitView); + void setSplitToolbar(boolean split); + void setSplitWhenNarrow(boolean splitWhenNarrow); + boolean hasIcon(); + boolean hasLogo(); + void setIcon(int resId); + void setIcon(Drawable d); + void setLogo(int resId); + void setLogo(Drawable d); + boolean canShowOverflowMenu(); + boolean isOverflowMenuShowing(); + boolean isOverflowMenuShowPending(); + boolean showOverflowMenu(); + boolean hideOverflowMenu(); + void setMenuPrepared(); + void setMenu(Menu menu, MenuPresenter.Callback cb); + void dismissPopupMenus(); + + int getDisplayOptions(); + void setDisplayOptions(int opts); + void setEmbeddedTabView(View tabView); + boolean hasEmbeddedTabs(); + boolean isTitleTruncated(); + void setCollapsible(boolean collapsible); + void setHomeButtonEnabled(boolean enable); + int getNavigationMode(); + void setNavigationMode(int mode); + void setDropdownParams(SpinnerAdapter adapter, AdapterView.OnItemSelectedListener listener); + void setDropdownSelectedPosition(int position); + int getDropdownSelectedPosition(); + int getDropdownItemCount(); + void setCustomView(View view); + View getCustomView(); + void animateToVisibility(int visibility); + void setNavigationIcon(Drawable icon); + void setNavigationIcon(int resId); + void setNavigationContentDescription(CharSequence description); + void setNavigationContentDescription(int resId); + void saveHierarchyState(SparseArray<Parcelable> toolbarStates); + void restoreHierarchyState(SparseArray<Parcelable> toolbarStates); +} diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index 9501f92..c70841b 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -16,6 +16,8 @@ package com.android.internal.widget; +import com.android.internal.widget.ILockSettingsObserver; + /** {@hide} */ interface ILockSettings { void setBoolean(in String key, in boolean value, in int userId); @@ -32,4 +34,6 @@ interface ILockSettings { boolean havePattern(int userId); boolean havePassword(int userId); void removeUser(int userId); + void registerObserver(in ILockSettingsObserver observer); + void unregisterObserver(in ILockSettingsObserver observer); } diff --git a/core/java/com/android/internal/widget/ILockSettingsObserver.aidl b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl new file mode 100644 index 0000000..6c354d8 --- /dev/null +++ b/core/java/com/android/internal/widget/ILockSettingsObserver.aidl @@ -0,0 +1,22 @@ +/* + * 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.internal.widget; + +/** {@hide} */ +oneway interface ILockSettingsObserver { + void onLockSettingChanged(in String key, in int userId); +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 2882b54..d31c5cc 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -43,7 +43,6 @@ import android.view.View; import android.widget.Button; import com.android.internal.R; -import com.android.internal.telephony.ITelephony; import com.google.android.collect.Lists; import java.security.MessageDigest; @@ -199,8 +198,8 @@ public class LockPatternUtils { private ILockSettings getLockSettings() { if (mLockSettingsService == null) { - mLockSettingsService = ILockSettings.Stub.asInterface( - (IBinder) ServiceManager.getService("lock_settings")); + mLockSettingsService = LockPatternUtilsCache.getInstance( + ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"))); } return mLockSettingsService; } @@ -1360,19 +1359,11 @@ public class LockPatternUtils { /** * Resumes a call in progress. Typically launched from the EmergencyCall button * on various lockscreens. - * - * @return true if we were able to tell InCallScreen to show. */ - public boolean resumeCall() { - ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); - try { - if (phone != null && phone.showCallScreen()) { - return true; - } - } catch (RemoteException e) { - // What can we do? - } - return false; + public void resumeCall() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.showCallScreen(); } private void finishBiometricWeak() { diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java new file mode 100644 index 0000000..624f67c --- /dev/null +++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java @@ -0,0 +1,243 @@ +/* + * 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.internal.widget; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; + +/** + * A decorator for {@link ILockSettings} that caches the key-value responses in memory. + * + * Specifically, the return values of {@link #getString(String, String, int)}, + * {@link #getLong(String, long, int)} and {@link #getBoolean(String, boolean, int)} are cached. + */ +public class LockPatternUtilsCache implements ILockSettings { + + private static final String HAS_LOCK_PATTERN_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPatternCacheKey"; + private static final String HAS_LOCK_PASSWORD_CACHE_KEY + = "LockPatternUtils.Cache.HasLockPasswordCacheKey"; + + private static LockPatternUtilsCache sInstance; + + private final ILockSettings mService; + + /** Only access when holding {@code mCache} lock. */ + private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); + + /** Only access when holding {@link #mCache} lock. */ + private final CacheKey mCacheKey = new CacheKey(); + + + public static synchronized LockPatternUtilsCache getInstance(ILockSettings service) { + if (sInstance == null) { + sInstance = new LockPatternUtilsCache(service); + } + return sInstance; + } + + // ILockSettings + + private LockPatternUtilsCache(ILockSettings service) { + mService = service; + try { + service.registerObserver(mObserver); + } catch (RemoteException e) { + // Not safe to do caching without the observer. System process has probably died + // anyway, so crashing here is fine. + throw new RuntimeException(e); + } + } + + public void setBoolean(String key, boolean value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setBoolean(key, value, userId); + putCache(key, userId, value); + } + + public void setLong(String key, long value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setLong(key, value, userId); + putCache(key, userId, value); + } + + public void setString(String key, String value, int userId) throws RemoteException { + invalidateCache(key, userId); + mService.setString(key, value, userId); + putCache(key, userId, value); + } + + public long getLong(String key, long defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Long) { + return (long) value; + } + long result = mService.getLong(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public String getString(String key, String defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof String) { + return (String) value; + } + String result = mService.getString(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { + Object value = peekCache(key, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.getBoolean(key, defaultValue, userId); + putCache(key, userId, result); + return result; + } + + @Override + public void setLockPattern(String pattern, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); + mService.setLockPattern(pattern, userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null); + } + + @Override + public boolean checkPattern(String pattern, int userId) throws RemoteException { + return mService.checkPattern(pattern, userId); + } + + @Override + public void setLockPassword(String password, int userId) throws RemoteException { + invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); + mService.setLockPassword(password, userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null); + } + + @Override + public boolean checkPassword(String password, int userId) throws RemoteException { + return mService.checkPassword(password, userId); + } + + @Override + public boolean checkVoldPassword(int userId) throws RemoteException { + return mService.checkVoldPassword(userId); + } + + @Override + public boolean havePattern(int userId) throws RemoteException { + Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePattern(userId); + putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result); + return result; + } + + @Override + public boolean havePassword(int userId) throws RemoteException { + Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId); + if (value instanceof Boolean) { + return (boolean) value; + } + boolean result = mService.havePassword(userId); + putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result); + return result; + } + + @Override + public void removeUser(int userId) throws RemoteException { + mService.removeUser(userId); + } + + @Override + public void registerObserver(ILockSettingsObserver observer) throws RemoteException { + mService.registerObserver(observer); + } + + @Override + public void unregisterObserver(ILockSettingsObserver observer) throws RemoteException { + mService.unregisterObserver(observer); + } + + @Override + public IBinder asBinder() { + return mService.asBinder(); + } + + // Caching + + private Object peekCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + return mCache.get(mCacheKey.set(key, userId)); + } + } + + private void putCache(String key, int userId, Object value) { + synchronized (mCache) { + // Create a new key, because this will be stored in the map. + mCache.put(new CacheKey().set(key, userId), value); + } + } + + private void invalidateCache(String key, int userId) { + synchronized (mCache) { + // Safe to reuse mCacheKey, because it is not stored in the map. + mCache.remove(mCacheKey.set(key, userId)); + } + } + + private final ILockSettingsObserver mObserver = new ILockSettingsObserver.Stub() { + @Override + public void onLockSettingChanged(String key, int userId) throws RemoteException { + invalidateCache(key, userId); + } + }; + + private static final class CacheKey { + String key; + int userId; + + public CacheKey set(String key, int userId) { + this.key = key; + this.userId = userId; + return this; + } + + public CacheKey copy() { + return new CacheKey().set(key, userId); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CacheKey)) + return false; + CacheKey o = (CacheKey) obj; + return userId == o.userId && key.equals(o.key); + } + + @Override + public int hashCode() { + return key.hashCode() ^ userId; + } + } +} diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 36ed344..d841d53 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -57,6 +57,7 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) private static final boolean PROFILE_DRAWING = false; + private final CellState[][] mCellStates; private boolean mDrawingProfilingStarted = false; private Paint mPaint = new Paint(); @@ -187,6 +188,12 @@ public class LockPatternView extends View { } } + public static class CellState { + public float scale = 1.0f; + public float translateY = 0.0f; + public float alpha = 1.0f; + } + /** * How to display the current pattern. */ @@ -296,6 +303,18 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } + mPaint.setFilterBitmap(true); + + mCellStates = new CellState[3][3]; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + mCellStates[i][j] = new CellState(); + } + } + } + + public CellState[][] getCellStates() { + return mCellStates; } private Bitmap getBitmapFor(int resId) { @@ -873,18 +892,22 @@ public class LockPatternView extends View { //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2); for (int j = 0; j < 3; j++) { float leftX = paddingLeft + j * squareWidth; - drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]); + float scale = mCellStates[i][j].scale; + mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255)); + float translationY = mCellStates[i][j].translateY; + drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]); } } + // Reset the alpha to draw normally + mPaint.setAlpha(255); + // TODO: the path should be created and cached every time we hit-detect a cell // only the last segment of the path should be computed here // draw the path of the pattern (unless we are in stealth mode) final boolean drawPath = !mInStealthMode; // draw the arrows associated with the path (unless we are in stealth mode) - boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; - mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms if (drawPath) { for (int i = 0; i < count - 1; i++) { Cell cell = pattern.get(i); @@ -898,7 +921,8 @@ public class LockPatternView extends View { } float leftX = paddingLeft + cell.column * squareWidth; - float topY = paddingTop + cell.row * squareHeight; + float topY = paddingTop + cell.row * squareHeight + + mCellStates[cell.row][cell.column].translateY; drawArrow(canvas, leftX, topY, cell, next); } @@ -919,6 +943,9 @@ public class LockPatternView extends View { float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); + + // Respect translation in animation + centerY += mCellStates[cell.row][cell.column].translateY; if (i == 0) { currentPath.moveTo(centerX, centerY); } else { @@ -933,8 +960,6 @@ public class LockPatternView extends View { } canvas.drawPath(currentPath, mPathPaint); } - - mPaint.setFilterBitmap(oldFlag); // restore default flag } private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) { @@ -979,7 +1004,8 @@ public class LockPatternView extends View { * @param topY * @param partOfPattern Whether this circle is part of the pattern. */ - private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) { + private void drawCircle(Canvas canvas, float leftX, float topY, float scale, + boolean partOfPattern) { Bitmap outerCircle; Bitmap innerCircle; @@ -1019,7 +1045,7 @@ public class LockPatternView extends View { mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY); mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2); - mCircleMatrix.preScale(sx, sy); + mCircleMatrix.preScale(sx * scale, sy * scale); mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2); canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint); diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java new file mode 100644 index 0000000..3e15c32 --- /dev/null +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -0,0 +1,541 @@ +/* + * 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.internal.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.ActionBar; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ActionMenuPresenter; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.Toolbar; +import com.android.internal.R; +import com.android.internal.view.menu.ActionMenuItem; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuPresenter; + +/** + * Internal class used to interact with the Toolbar widget without + * exposing interface methods to the public API. + * + * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView + * so that either variant acting as a + * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave + * in the same way.</p> + * + * @hide + */ +public class ToolbarWidgetWrapper implements DecorToolbar { + private static final String TAG = "ToolbarWidgetWrapper"; + + private static final int AFFECTS_LOGO_MASK = + ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; + + private Toolbar mToolbar; + + private int mDisplayOpts; + private View mTabView; + private Spinner mSpinner; + private View mCustomView; + + private Drawable mIcon; + private Drawable mLogo; + private Drawable mNavIcon; + + private boolean mTitleSet; + private CharSequence mTitle; + private CharSequence mSubtitle; + + private Window.Callback mWindowCallback; + private boolean mMenuPrepared; + private ActionMenuPresenter mActionMenuPresenter; + + public ToolbarWidgetWrapper(Toolbar toolbar) { + mToolbar = toolbar; + + mTitle = toolbar.getTitle(); + mSubtitle = toolbar.getSubtitle(); + mTitleSet = !TextUtils.isEmpty(mTitle); + + final TypedArray a = toolbar.getContext().obtainStyledAttributes(null, + R.styleable.ActionBar, R.attr.actionBarStyle, 0); + + final CharSequence title = a.getText(R.styleable.ActionBar_title); + if (!TextUtils.isEmpty(title)) { + setTitle(title); + } + + final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); + if (!TextUtils.isEmpty(subtitle)) { + setSubtitle(subtitle); + } + + final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); + if (logo != null) { + setLogo(logo); + } + + final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); + if (icon != null) { + setIcon(icon); + } + + final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); + if (navIcon != null) { + setNavigationIcon(navIcon); + } + + setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); + + final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0); + if (customNavId != 0) { + setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, + mToolbar, false)); + setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); + } + + final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); + if (height > 0) { + final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); + lp.height = height; + mToolbar.setLayoutParams(lp); + } + + final int contentInsetStart = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetStart, 0); + final int contentInsetEnd = a.getDimensionPixelOffset( + R.styleable.ActionBar_contentInsetEnd, 0); + if (contentInsetStart > 0 || contentInsetEnd > 0) { + mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd); + } + + final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); + if (titleTextStyle != 0) { + mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); + } + + final int subtitleTextStyle = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0); + if (subtitleTextStyle != 0) { + mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); + } + + a.recycle(); + + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), + 0, android.R.id.home, 0, 0, mTitle); + @Override + public void onClick(View v) { + if (mWindowCallback != null && mMenuPrepared) { + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); + } + } + }); + } + + @Override + public ViewGroup getViewGroup() { + return mToolbar; + } + + @Override + public Context getContext() { + return mToolbar.getContext(); + } + + @Override + public boolean isSplit() { + return false; + } + + @Override + public boolean hasExpandedActionView() { + return mToolbar.hasExpandedActionView(); + } + + @Override + public void collapseActionView() { + mToolbar.collapseActionView(); + } + + @Override + public void setWindowCallback(Window.Callback cb) { + mWindowCallback = cb; + } + + @Override + public void setWindowTitle(CharSequence title) { + // "Real" title always trumps window title. + if (!mTitleSet) { + setTitleInt(title); + } + } + + @Override + public CharSequence getTitle() { + return mToolbar.getTitle(); + } + + @Override + public void setTitle(CharSequence title) { + mTitleSet = true; + setTitleInt(title); + } + + private void setTitleInt(CharSequence title) { + mTitle = title; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(title); + } + } + + @Override + public CharSequence getSubtitle() { + return mToolbar.getSubtitle(); + } + + @Override + public void setSubtitle(CharSequence subtitle) { + mSubtitle = subtitle; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setSubtitle(subtitle); + } + } + + @Override + public void initProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public void initIndeterminateProgress() { + Log.i(TAG, "Progress display unsupported"); + } + + @Override + public boolean canSplit() { + return false; + } + + @Override + public void setSplitView(ViewGroup splitView) { + } + + @Override + public void setSplitToolbar(boolean split) { + if (split) { + throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); + } + } + + @Override + public void setSplitWhenNarrow(boolean splitWhenNarrow) { + // Ignore. + } + + @Override + public boolean hasIcon() { + return mIcon != null; + } + + @Override + public boolean hasLogo() { + return mLogo != null; + } + + @Override + public void setIcon(int resId) { + setIcon(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setIcon(Drawable d) { + mIcon = d; + updateToolbarLogo(); + } + + @Override + public void setLogo(int resId) { + setLogo(resId != 0 ? getContext().getDrawable(resId) : null); + } + + @Override + public void setLogo(Drawable d) { + mLogo = d; + updateToolbarLogo(); + } + + private void updateToolbarLogo() { + Drawable logo = null; + if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { + if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { + logo = mLogo != null ? mLogo : mIcon; + } else { + logo = mIcon; + } + } + mToolbar.setLogo(logo); + } + + @Override + public boolean canShowOverflowMenu() { + return mToolbar.canShowOverflowMenu(); + } + + @Override + public boolean isOverflowMenuShowing() { + return mToolbar.isOverflowMenuShowing(); + } + + @Override + public boolean isOverflowMenuShowPending() { + return mToolbar.isOverflowMenuShowPending(); + } + + @Override + public boolean showOverflowMenu() { + return mToolbar.showOverflowMenu(); + } + + @Override + public boolean hideOverflowMenu() { + return mToolbar.hideOverflowMenu(); + } + + @Override + public void setMenuPrepared() { + mMenuPrepared = true; + } + + @Override + public void setMenu(Menu menu, MenuPresenter.Callback cb) { + if (mActionMenuPresenter == null) { + mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); + mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); + } + mActionMenuPresenter.setCallback(cb); + mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); + } + + @Override + public void dismissPopupMenus() { + mToolbar.dismissPopupMenus(); + } + + @Override + public int getDisplayOptions() { + return mDisplayOpts; + } + + @Override + public void setDisplayOptions(int newOpts) { + final int oldOpts = mDisplayOpts; + final int changed = oldOpts ^ newOpts; + mDisplayOpts = newOpts; + if (changed != 0) { + if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(mNavIcon); + } else { + mToolbar.setNavigationIcon(null); + } + } + + if ((changed & AFFECTS_LOGO_MASK) != 0) { + updateToolbarLogo(); + } + + if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { + mToolbar.setTitle(mTitle); + mToolbar.setSubtitle(mSubtitle); + } else { + mToolbar.setTitle(null); + mToolbar.setSubtitle(null); + } + } + + if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { + if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } else { + mToolbar.removeView(mCustomView); + } + } + } + } + + @Override + public void setEmbeddedTabView(View tabView) { + mTabView = tabView; + } + + @Override + public boolean hasEmbeddedTabs() { + return mTabView != null; + } + + @Override + public boolean isTitleTruncated() { + return mToolbar.isTitleTruncated(); + } + + @Override + public void setCollapsible(boolean collapsible) { + // Ignore + } + + @Override + public void setHomeButtonEnabled(boolean enable) { + // Ignore + } + + @Override + public int getNavigationMode() { + return 0; + } + + @Override + public void setNavigationMode(int mode) { + if (mode != ActionBar.NAVIGATION_MODE_STANDARD) { + throw new IllegalArgumentException( + "Navigation modes not supported in this configuration"); + } + } + + @Override + public void setDropdownParams(SpinnerAdapter adapter, + AdapterView.OnItemSelectedListener listener) { + if (mSpinner == null) { + mSpinner = new Spinner(getContext()); + } + mSpinner.setAdapter(adapter); + mSpinner.setOnItemSelectedListener(listener); + } + + @Override + public void setDropdownSelectedPosition(int position) { + if (mSpinner == null) { + throw new IllegalStateException( + "Can't set dropdown selected position without an adapter"); + } + mSpinner.setSelection(position); + } + + @Override + public int getDropdownSelectedPosition() { + return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; + } + + @Override + public int getDropdownItemCount() { + return mSpinner != null ? mSpinner.getCount() : 0; + } + + @Override + public void setCustomView(View view) { + if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.removeView(mCustomView); + } + mCustomView = view; + if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { + mToolbar.addView(mCustomView); + } + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public void animateToVisibility(int visibility) { + if (visibility == View.GONE) { + mToolbar.animate().translationY(mToolbar.getHeight()).alpha(0) + .setListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + @Override + public void onAnimationEnd(Animator animation) { + if (!mCanceled) { + mToolbar.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + mCanceled = true; + } + }); + } else if (visibility == View.VISIBLE) { + mToolbar.animate().translationY(0).alpha(1) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mToolbar.setVisibility(View.VISIBLE); + } + }); + } + } + + @Override + public void setNavigationIcon(Drawable icon) { + mNavIcon = icon; + if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + mToolbar.setNavigationIcon(icon); + } + } + + @Override + public void setNavigationIcon(int resId) { + setNavigationIcon(mToolbar.getContext().getDrawable(resId)); + } + + @Override + public void setNavigationContentDescription(CharSequence description) { + mToolbar.setNavigationContentDescription(description); + } + + @Override + public void setNavigationContentDescription(int resId) { + mToolbar.setNavigationContentDescription(resId); + } + + @Override + public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.saveHierarchyState(toolbarStates); + } + + @Override + public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) { + mToolbar.restoreHierarchyState(toolbarStates); + } + +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 2d72494..a1cd7f7 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -57,7 +57,6 @@ LOCAL_SRC_FILES:= \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_GraphicBuffer.cpp \ - android_view_GLRenderer.cpp \ android_view_GLES20Canvas.cpp \ android_view_HardwareLayer.cpp \ android_view_ThreadedRenderer.cpp \ @@ -138,6 +137,7 @@ LOCAL_SRC_FILES:= \ android_hardware_Camera.cpp \ android_hardware_camera2_CameraMetadata.cpp \ android_hardware_camera2_legacy_LegacyCameraDevice.cpp \ + android_hardware_camera2_DngCreator.cpp \ android_hardware_SensorManager.cpp \ android_hardware_SerialPort.cpp \ android_hardware_UsbDevice.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2d350e0..e0c5e96 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -80,6 +80,7 @@ extern int register_android_opengl_jni_GLES31Ext(JNIEnv* env); extern int register_android_hardware_Camera(JNIEnv *env); extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env); extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env); +extern int register_android_hardware_camera2_DngCreator(JNIEnv *env); extern int register_android_hardware_SensorManager(JNIEnv *env); extern int register_android_hardware_SerialPort(JNIEnv *env); extern int register_android_hardware_UsbDevice(JNIEnv *env); @@ -129,7 +130,6 @@ extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_RenderNodeAnimator(JNIEnv* env); extern int register_android_view_GraphicBuffer(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); -extern int register_android_view_GLRenderer(JNIEnv* env); extern int register_android_view_HardwareLayer(JNIEnv* env); extern int register_android_view_ThreadedRenderer(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); @@ -788,7 +788,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) } // libart tolerates libdvm flags, but not vice versa, so only pass some options if libart. - property_get("persist.sys.dalvik.vm.lib.1", dalvikVmLibBuf, "libdvm.so"); + property_get("persist.sys.dalvik.vm.lib.2", dalvikVmLibBuf, "libart.so"); bool libart = (strncmp(dalvikVmLibBuf, "libart", 6) == 0); if (libart) { @@ -1213,7 +1213,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_RenderNodeAnimator), REG_JNI(register_android_view_GraphicBuffer), REG_JNI(register_android_view_GLES20Canvas), - REG_JNI(register_android_view_GLRenderer), REG_JNI(register_android_view_HardwareLayer), REG_JNI(register_android_view_ThreadedRenderer), REG_JNI(register_android_view_Surface), @@ -1286,6 +1285,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice), + REG_JNI(register_android_hardware_camera2_DngCreator), REG_JNI(register_android_hardware_SensorManager), REG_JNI(register_android_hardware_SerialPort), REG_JNI(register_android_hardware_UsbDevice), diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 7aa241a..928a7f8 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -128,8 +128,7 @@ static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream, int sampleSize, bool ditherImage) { - SkImageInfo bitmapInfo; - if (!bitmap->asImageInfo(&bitmapInfo)) { + if (kUnknown_SkColorType == bitmap->colorType()) { ALOGW("bitmap has unknown configuration so no memory has been allocated"); return NULL; } @@ -137,9 +136,9 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream, SkImageRef* pr; // only use ashmem for large images, since mmaps come at a price if (bitmap->getSize() >= 32 * 1024) { - pr = new SkImageRef_ashmem(bitmapInfo, stream, sampleSize); + pr = new SkImageRef_ashmem(bitmap->info(), stream, sampleSize); } else { - pr = new SkImageRef_GlobalPool(bitmapInfo, stream, sampleSize); + pr = new SkImageRef_GlobalPool(bitmap->info(), stream, sampleSize); } pr->setDitherImage(ditherImage); bitmap->setPixelRef(pr)->unref(); diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp index ef57e3d..d17f46c 100644 --- a/core/jni/android/graphics/Camera.cpp +++ b/core/jni/android/graphics/Camera.cpp @@ -3,6 +3,8 @@ #include "SkCamera.h" +#include "GraphicsJNI.h" + static jfieldID gNativeInstanceFieldID; static void Camera_constructor(JNIEnv* env, jobject obj) { @@ -93,7 +95,7 @@ static void Camera_getMatrix(JNIEnv* env, jobject obj, jlong matrixHandle) { } static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) { - SkCanvas* native_canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle); jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID); Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle); v->applyToCanvas((SkCanvas*)native_canvas); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index f7acbd7..5fca582 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -42,22 +42,26 @@ #include <utils/Log.h> -static uint32_t get_thread_msec() { -#if defined(HAVE_POSIX_CLOCKS) - struct timespec tm; +namespace android { - clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tm); +// Holds an SkCanvas reference plus additional native data. +class NativeCanvasWrapper { +public: + NativeCanvasWrapper(SkCanvas* canvas) + : mCanvas(canvas) { } - return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; -#else - struct timeval tv; + SkCanvas* getCanvas() const { + return mCanvas.get(); + } - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000LL + tv.tv_usec / 1000; -#endif -} + void setCanvas(SkCanvas* canvas) { + SkASSERT(canvas); + mCanvas.reset(canvas); + } -namespace android { +private: + SkAutoTUnref<SkCanvas> mCanvas; +}; class ClipCopier : public SkCanvas::ClipVisitor { public: @@ -86,27 +90,30 @@ static jboolean hasNonEmptyClip(const SkCanvas& canvas) { class SkCanvasGlue { public: - static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); - canvas->unref(); + // Get the SkCanvas for a given native handle. + static inline SkCanvas* getNativeCanvas(jlong nativeHandle) { + SkASSERT(nativeHandle); + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + SkCanvas* canvas = wrapper->getCanvas(); + SkASSERT(canvas); + + return canvas; } - static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + // Construct an SkCanvas from the bitmap. + static SkCanvas* createCanvas(SkBitmap* bitmap) { if (bitmap) { - return reinterpret_cast<jlong>(new SkCanvas(*bitmap)); - } else { - // Create an empty bitmap device to prevent callers from crashing - // if they attempt to draw into this canvas. - SkBitmap emptyBitmap; - return reinterpret_cast<jlong>(new SkCanvas(emptyBitmap)); + return SkNEW_ARGS(SkCanvas, (*bitmap)); } + + // Create an empty bitmap device to prevent callers from crashing + // if they attempt to draw into this canvas. + SkBitmap emptyBitmap; + return new SkCanvas(emptyBitmap); } - static void copyCanvasState(JNIEnv* env, jobject clazz, - jlong srcCanvasHandle, jlong dstCanvasHandle) { - SkCanvas* srcCanvas = reinterpret_cast<SkCanvas*>(srcCanvasHandle); - SkCanvas* dstCanvas = reinterpret_cast<SkCanvas*>(dstCanvasHandle); + // Copy the canvas matrix & clip state. + static void copyCanvasState(SkCanvas* srcCanvas, SkCanvas* dstCanvas) { if (srcCanvas && dstCanvas) { dstCanvas->setMatrix(srcCanvas->getTotalMatrix()); if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) { @@ -116,6 +123,42 @@ public: } } + // Native JNI handlers + static void finalizer(JNIEnv* env, jobject clazz, jlong nativeHandle) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle); + delete wrapper; + } + + // Native wrapper constructor used by Canvas(Bitmap) + static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) { + // No check - 0 is a valid bitmapHandle. + SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkCanvas* canvas = createCanvas(bitmap); + + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Native wrapper constructor used by Canvas(native_canvas) + static jlong initCanvas(JNIEnv* env, jobject, jlong canvasHandle) { + SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas)); + } + + // Set the given bitmap as the new draw target (wrapped in a new SkCanvas), + // optionally copying canvas matrix & clip state. + static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, + jboolean copyState) { + NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(canvasHandle); + SkCanvas* newCanvas = createCanvas(reinterpret_cast<SkBitmap*>(bitmapHandle)); + NPE_CHECK_RETURN_VOID(env, newCanvas); + + if (copyState == JNI_TRUE) { + copyCanvasState(wrapper->getCanvas(), newCanvas); + } + + // setCanvas() unrefs the old canvas. + wrapper->setCanvas(newCanvas); + } static void freeCaches(JNIEnv* env, jobject) { // these are called in no particular order @@ -163,7 +206,7 @@ public: static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds, jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect* bounds_ = NULL; SkRect storage; @@ -177,7 +220,7 @@ public: static jint saveLayer4F(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t, jfloat r, jfloat b, jlong paintHandle, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect bounds; bounds.set(l, t, r, b); @@ -188,7 +231,7 @@ public: static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds, jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect* bounds_ = NULL; SkRect storage; if (bounds != NULL) { @@ -203,7 +246,7 @@ public: static jint saveLayerAlpha4F(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t, jfloat r, jfloat b, jint alpha, jint flags) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect bounds; bounds.set(l, t, r, b); int result = canvas->saveLayerAlpha(&bounds, alpha, @@ -259,14 +302,14 @@ public: static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); canvas->concat(*matrix); } static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); if (NULL == matrix) { canvas->resetMatrix(); @@ -318,8 +361,8 @@ public: static jboolean clipRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jint op) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect rect; - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); rect.set(left, top, right, bottom); canvas->clipRect(rect, static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -327,7 +370,7 @@ public: static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle), static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -335,7 +378,7 @@ public: static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong deviceRgnHandle, jint op) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle); canvas->clipRegion(*deviceRgn, static_cast<SkRegion::Op>(op)); return hasNonEmptyClip(*canvas); @@ -343,13 +386,13 @@ public: static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle)); } static jboolean quickReject__RectF(JNIEnv* env, jobject, jlong canvasHandle, jobject rect) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect rect_; GraphicsJNI::jrectf_to_rect(env, rect, &rect_); bool result = canvas->quickReject(rect_); @@ -358,7 +401,7 @@ public: static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle)); return result ? JNI_TRUE : JNI_FALSE; } @@ -366,7 +409,7 @@ public: static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; r.set(left, top, right, bottom); bool result = canvas->quickReject(r); @@ -375,32 +418,32 @@ public: static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(0xFF, r, g, b); } static void drawARGB(JNIEnv* env, jobject, jlong canvasHandle, jint a, jint r, jint g, jint b) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawARGB(a, r, g, b); } static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle, jint color) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); canvas->drawColor(color); } static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle); canvas->drawColor(color, SkPorterDuff::ToXfermodeMode(mode)); } static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPaint(*paint); } @@ -461,14 +504,14 @@ public: static void drawLine__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY, jfloat stopX, jfloat stopY, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawLine(startX, startY, stopX, stopY, *paint); } static void drawRect__RectFPaint(JNIEnv* env, jobject, jlong canvasHandle, jobject rect, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect rect_; GraphicsJNI::jrectf_to_rect(env, rect, &rect_); @@ -478,14 +521,14 @@ public: static void drawRect__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawRectCoords(left, top, right, bottom, *paint); } static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect oval; GraphicsJNI::jrectf_to_rect(env, joval, &oval); @@ -494,7 +537,7 @@ public: static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy, jfloat radius, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawCircle(cx, cy, radius, *paint); } @@ -502,7 +545,7 @@ public: static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jobject joval, jfloat startAngle, jfloat sweepAngle, jboolean useCenter, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect oval; GraphicsJNI::jrectf_to_rect(env, joval, &oval); @@ -512,7 +555,7 @@ public: static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); canvas->drawRoundRect(rect, rx, ry, *paint); @@ -520,7 +563,7 @@ public: static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); canvas->drawPath(*path, *paint); @@ -531,7 +574,7 @@ public: jfloat left, jfloat top, jlong paintHandle, jint canvasDensity, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -591,7 +634,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRectF, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -604,7 +647,7 @@ public: jlong bitmapHandle, jobject srcIRect, jobject dstRect, jlong paintHandle, jint screenDensity, jint bitmapDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkRect dst; @@ -616,9 +659,8 @@ public: static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, jintArray jcolors, jint offset, jint stride, jfloat x, jfloat y, jint width, jint height, - jboolean hasAlpha, jlong paintHandle) - { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + jboolean hasAlpha, jlong paintHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); SkBitmap bitmap; bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : @@ -638,7 +680,7 @@ public: static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, jlong matrixHandle, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -649,7 +691,7 @@ public: jlong bitmapHandle, jint meshWidth, jint meshHeight, jfloatArray jverts, jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -759,7 +801,7 @@ public: jintArray jcolors, jint colorIndex, jshortArray jindices, jint indexIndex, jint indexCount, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -799,7 +841,7 @@ public: jcharArray text, jint index, jint count, jfloat x, jfloat y, jint flags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, NULL); @@ -812,7 +854,7 @@ public: jint start, jint end, jfloat x, jfloat y, jint flags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, NULL); @@ -939,10 +981,10 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l } static void drawTextRun___CIIIIFFIPaintTypeface( - JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, - jint count, jint contextIndex, jint contextCount, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, + jint count, jint contextIndex, jint contextCount, + jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); @@ -953,10 +995,10 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l } static void drawTextRun__StringIIIIFFIPaintTypeface( - JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, - jint end, jint contextStart, jint contextEnd, - jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start, + jint end, jint contextStart, jint contextEnd, + jfloat x, jfloat y, jint dirFlags, jlong paintHandle, jlong typefaceHandle) { + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle); @@ -971,7 +1013,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawPosText___CII_FPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL; jsize textCount = text ? env->GetArrayLength(text) : NULL; @@ -1002,7 +1044,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l jlong canvasHandle, jstring text, jfloatArray pos, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const void* text_ = text ? env->GetStringChars(text, NULL) : NULL; int byteLength = text ? env->GetStringLength(text) : 0; @@ -1032,7 +1074,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index, jint count, jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -1045,7 +1087,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject, jlong canvasHandle, jstring text, jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkPath* path = reinterpret_cast<SkPath*>(pathHandle); SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); const jchar* text_ = env->GetStringChars(text, NULL); @@ -1084,7 +1126,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkRect r; SkIRect ir; bool result = getHardClipBounds(canvas, &r); @@ -1100,7 +1142,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = getNativeCanvas(canvasHandle); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle); *matrix = canvas->getTotalMatrix(); } @@ -1108,8 +1150,9 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static JNINativeMethod gCanvasMethods[] = { {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer}, - {"initRaster","(J)J", (void*) SkCanvasGlue::initRaster}, - {"copyNativeCanvasState","(JJ)V", (void*) SkCanvasGlue::copyCanvasState}, + {"initRaster", "(J)J", (void*) SkCanvasGlue::initRaster}, + {"initCanvas", "(J)J", (void*) SkCanvasGlue::initCanvas}, + {"native_setBitmap", "(JJZ)V", (void*) SkCanvasGlue::setBitmap}, {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque}, {"getWidth","()I", (void*) SkCanvasGlue::getWidth}, {"getHeight","()I", (void*) SkCanvasGlue::getHeight}, @@ -1224,4 +1267,11 @@ int register_android_graphics_Canvas(JNIEnv* env) { return result; } +} // namespace android + +// GraphicsJNI helper for external clients. +// We keep the implementation here to avoid exposing NativeCanvasWrapper +// externally. +SkCanvas* GraphicsJNI::getNativeCanvas(jlong nativeHandle) { + return android::SkCanvasGlue::getNativeCanvas(nativeHandle); } diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/DrawFilter.cpp index fbfa2ec..3275875 100644 --- a/core/jni/android/graphics/DrawFilter.cpp +++ b/core/jni/android/graphics/DrawFilter.cpp @@ -30,6 +30,35 @@ namespace android { +// Custom version of SkPaintFlagsDrawFilter that also calls setFilterLevel. +class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter { +public: + CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags, + SkPaint::FilterLevel desiredLevel) + : SkPaintFlagsDrawFilter(clearFlags, setFlags) + , fDesiredLevel(desiredLevel) { + } + + virtual bool filter(SkPaint* paint, Type type) { + SkPaintFlagsDrawFilter::filter(paint, type); + paint->setFilterLevel(fDesiredLevel); + return true; + } + +private: + const SkPaint::FilterLevel fDesiredLevel; +}; + +// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it. +static inline bool hadFiltering(jint& flags) { + // Equivalent to the Java Paint's FILTER_BITMAP_FLAG. + static const uint32_t sFilterBitmapFlag = 0x02; + + const bool result = (flags & sFilterBitmapFlag) != 0; + flags &= ~sFilterBitmapFlag; + return result; +} + class SkDrawFilterGlue { public: @@ -40,12 +69,25 @@ public: static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz, jint clearFlags, jint setFlags) { - // trim off any out-of-range bits - clearFlags &= SkPaint::kAllFlags; - setFlags &= SkPaint::kAllFlags; - if (clearFlags | setFlags) { - SkDrawFilter* filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags); + // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no + // longer has a Skia equivalent flag (instead it corresponds to + // calling setFilterLevel), and keep track of which group(s), if + // any, had the flag set. + const bool turnFilteringOn = hadFiltering(setFlags); + const bool turnFilteringOff = hadFiltering(clearFlags); + + SkDrawFilter* filter; + if (turnFilteringOn) { + // Turning filtering on overrides turning it off. + filter = new CompatFlagsDrawFilter(clearFlags, setFlags, + SkPaint::kLow_FilterLevel); + } else if (turnFilteringOff) { + filter = new CompatFlagsDrawFilter(clearFlags, setFlags, + SkPaint::kNone_FilterLevel); + } else { + filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags); + } return reinterpret_cast<jlong>(filter); } else { return NULL; diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 041790f..dd6b36f 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -23,6 +23,9 @@ #include "GraphicsJNI.h" #include <ScopedPrimitiveArray.h> #include <ScopedUtfChars.h> +#include <android_runtime/android_util_AssetManager.h> +#include <androidfw/AssetManager.h> +#include "Utils.h" #ifdef USE_MINIKIN #include <minikin/FontFamily.h> @@ -31,9 +34,14 @@ namespace android { -static jlong FontFamily_create(JNIEnv* env, jobject clazz) { +static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) { #ifdef USE_MINIKIN - return (jlong)new FontFamily(); + FontLanguage fontLanguage; + if (lang != NULL) { + ScopedUtfChars str(env, lang); + fontLanguage = FontLanguage(str.c_str(), str.size()); + } + return (jlong)new FontFamily(fontLanguage, variant); #else return 0; #endif @@ -46,19 +54,60 @@ static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) { #endif } +#ifdef USE_MINIKIN +static jboolean addSkTypeface(FontFamily* family, SkTypeface* face) { + MinikinFont* minikinFont = new MinikinFontSkia(face); + bool result = family->addFont(minikinFont); + minikinFont->Unref(); + return result; +} +#endif + static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jstring path) { #ifdef USE_MINIKIN NPE_CHECK_RETURN_ZERO(env, path); ScopedUtfChars str(env, path); - ALOGD("addFont %s", str.c_str()); SkTypeface* face = SkTypeface::CreateFromFile(str.c_str()); if (face == NULL) { ALOGE("addFont failed to create font %s", str.c_str()); return false; } - MinikinFont* minikinFont = new MinikinFontSkia(face); FontFamily* fontFamily = (FontFamily*)familyPtr; - return fontFamily->addFont(minikinFont); + return addSkTypeface(fontFamily, face); +#else + return false; +#endif +} + +static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPtr, + jobject jassetMgr, jstring jpath) { +#ifdef USE_MINIKIN + NPE_CHECK_RETURN_ZERO(env, jassetMgr); + NPE_CHECK_RETURN_ZERO(env, jpath); + + AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr); + if (NULL == mgr) { + return false; + } + + ScopedUtfChars str(env, jpath); + Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER); + if (NULL == asset) { + return false; + } + + SkStream* stream = new AssetStreamAdaptor(asset, + AssetStreamAdaptor::kYes_OwnAsset, + AssetStreamAdaptor::kYes_HasMemoryBase); + SkTypeface* face = SkTypeface::CreateFromStream(stream); + // Note: SkTypeface::CreateFromStream holds its own reference to the stream + stream->unref(); + if (face == NULL) { + ALOGE("addFontFromAsset failed to create font %s", str.c_str()); + return false; + } + FontFamily* fontFamily = (FontFamily*)familyPtr; + return addSkTypeface(fontFamily, face); #else return false; #endif @@ -67,9 +116,11 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gFontFamilyMethods[] = { - { "nCreateFamily", "()J", (void*)FontFamily_create }, + { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/lang/String;)Z", (void*)FontFamily_addFont }, + { "nAddFontFromAsset", "(JLandroid/content/res/AssetManager;Ljava/lang/String;)Z", + (void*)FontFamily_addFontFromAsset }, }; int register_android_graphics_FontFamily(JNIEnv* env) diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index dce185d..64ad223 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -320,7 +320,7 @@ SkCanvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) { SkASSERT(canvas); SkASSERT(env->IsInstanceOf(canvas, gCanvas_class)); jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID); - SkCanvas* c = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* c = getNativeCanvas(canvasHandle); SkASSERT(c); return c; } @@ -698,7 +698,7 @@ int register_android_graphics_Graphics(JNIEnv* env) "nativeInt", "I"); gCanvas_class = make_globalref(env, "android/graphics/Canvas"); - gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvas", "J"); + gCanvas_nativeInstanceID = getFieldIDCheck(env, gCanvas_class, "mNativeCanvasWrapper", "J"); gPaint_class = make_globalref(env, "android/graphics/Paint"); gPaint_nativeInstanceID = getFieldIDCheck(env, gPaint_class, "mNativePaint", "J"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index db7b6d9..73dd11b 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -45,6 +45,7 @@ public: static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point); static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf); + static SkCanvas* getNativeCanvas(jlong nativeHandle); static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas); static SkPaint* getNativePaint(JNIEnv*, jobject paint); static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint); diff --git a/core/jni/android/graphics/MaskFilter.cpp b/core/jni/android/graphics/MaskFilter.cpp index 2113330..b394905 100644 --- a/core/jni/android/graphics/MaskFilter.cpp +++ b/core/jni/android/graphics/MaskFilter.cpp @@ -21,8 +21,7 @@ public: static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) { SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius); - SkMaskFilter* filter = SkBlurMaskFilter::Create( - (SkBlurMaskFilter::BlurStyle)blurStyle, sigma); + SkMaskFilter* filter = SkBlurMaskFilter::Create((SkBlurStyle)blurStyle, sigma); ThrowIAE_IfNull(env, filter); return reinterpret_cast<jlong>(filter); } diff --git a/core/jni/android/graphics/MinikinSkia.h b/core/jni/android/graphics/MinikinSkia.h index 7a8954d..1cc2c51 100644 --- a/core/jni/android/graphics/MinikinSkia.h +++ b/core/jni/android/graphics/MinikinSkia.h @@ -18,6 +18,7 @@ namespace android { class MinikinFontSkia : public MinikinFont { public: + // Note: this takes ownership of the reference (will unref on dtor) explicit MinikinFontSkia(SkTypeface *typeface); ~MinikinFontSkia(); diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp index ee04d6f..79381ad 100644 --- a/core/jni/android/graphics/MinikinUtils.cpp +++ b/core/jni/android/graphics/MinikinUtils.cpp @@ -28,11 +28,17 @@ void MinikinUtils::SetLayoutProperties(Layout* layout, SkPaint* paint, int flags layout->setFontCollection(resolvedFace->fFontCollection); FontStyle style = resolvedFace->fStyle; char css[256]; - sprintf(css, "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d", + int off = snprintf(css, sizeof(css), + "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d;", (int)paint->getTextSize(), style.getWeight() * 100, style.getItalic() ? "italic" : "normal", flags); + SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag(); + off += snprintf(css + off, sizeof(css) - off, " lang: %s;", langString.c_str()); + SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant(); + const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact"; + off += snprintf(css + off, sizeof(css) - off, " -minikin-variant: %s;", varstr); layout->setProperties(css); } diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp index 855d267..ab5bdb0 100644 --- a/core/jni/android/graphics/NinePatch.cpp +++ b/core/jni/android/graphics/NinePatch.cpp @@ -119,7 +119,7 @@ public: static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); @@ -138,7 +138,7 @@ public: static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect, jlong bitmapHandle, jlong chunkHandle, jlong paintHandle, jint destDensity, jint srcDensity) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle); const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle); diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp index bac8ef7..a8a3dae 100644 --- a/core/jni/android/graphics/Picture.cpp +++ b/core/jni/android/graphics/Picture.cpp @@ -56,7 +56,7 @@ public: static void draw(JNIEnv* env, jobject, jlong canvasHandle, jlong pictureHandle) { - SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle); + SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle); SkPicture* picture = reinterpret_cast<SkPicture*>(pictureHandle); SkASSERT(canvas); SkASSERT(picture); diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp index 958cd85..ff52b07 100644 --- a/core/jni/android/graphics/TypefaceImpl.cpp +++ b/core/jni/android/graphics/TypefaceImpl.cpp @@ -171,7 +171,9 @@ TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size } void TypefaceImpl_unref(TypefaceImpl* face) { - face->fFontCollection->Unref(); + if (face != NULL) { + face->fFontCollection->Unref(); + } delete face; } diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index fcf8f83..ddcc396 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -51,6 +51,8 @@ struct SensorOffsets jfieldID fifoMaxEventCount; jfieldID stringType; jfieldID requiredPermission; + jfieldID maxDelay; + jfieldID isWakeUpSensor; } gSensorOffsets; @@ -78,6 +80,8 @@ nativeClassInit (JNIEnv *_env, jclass _this) sensorOffsets.stringType = _env->GetFieldID(sensorClass, "mStringType", "Ljava/lang/String;"); sensorOffsets.requiredPermission = _env->GetFieldID(sensorClass, "mRequiredPermission", "Ljava/lang/String;"); + sensorOffsets.maxDelay = _env->GetFieldID(sensorClass, "mMaxDelay", "I"); + sensorOffsets.isWakeUpSensor = _env->GetFieldID(sensorClass, "mWakeUpSensor", "Z"); } static jint @@ -112,6 +116,8 @@ nativeGetNextSensor(JNIEnv *env, jclass clazz, jobject sensor, jint next) env->SetObjectField(sensor, sensorOffsets.stringType, stringType); env->SetObjectField(sensor, sensorOffsets.requiredPermission, requiredPermission); + env->SetIntField(sensor, sensorOffsets.maxDelay, list->getMaxDelay()); + env->SetBooleanField(sensor, sensorOffsets.isWakeUpSensor, list->isWakeUpSensor()); next++; return size_t(next) < count ? next : 0; } @@ -160,7 +166,8 @@ private: ASensorEvent buffer[16]; while ((n = q->read(buffer, 16)) > 0) { for (int i=0 ; i<n ; i++) { - if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER) { + if (buffer[i].type == SENSOR_TYPE_STEP_COUNTER || + buffer[i].type == SENSOR_TYPE_WAKE_UP_STEP_COUNTER) { // step-counter returns a uint64, but the java API only deals with floats float value = float(buffer[i].u64.step_counter); env->SetFloatArrayRegion(mScratch, 0, 1, &value); @@ -175,11 +182,26 @@ private: gBaseEventQueueClassInfo.dispatchFlushCompleteEvent, buffer[i].meta_data.sensor); } else { + int8_t status; + switch (buffer[i].type) { + case SENSOR_TYPE_ORIENTATION: + case SENSOR_TYPE_MAGNETIC_FIELD: + case SENSOR_TYPE_ACCELEROMETER: + case SENSOR_TYPE_GYROSCOPE: + status = buffer[i].vector.status; + break; + case SENSOR_TYPE_HEART_RATE: + status = buffer[i].heart_rate.status; + break; + default: + status = SENSOR_STATUS_ACCURACY_HIGH; + break; + } env->CallVoidMethod(mReceiverObject, gBaseEventQueueClassInfo.dispatchSensorEvent, buffer[i].sensor, mScratch, - buffer[i].vector.status, + status, buffer[i].timestamp); } if (env->ExceptionCheck()) { diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 0d2df80..7935329 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -39,6 +39,9 @@ #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedPrimitiveArray.h> +#include <sys/types.h> // for socketpair +#include <sys/socket.h> // for socketpair + #if defined(LOG_NNDEBUG) #if !LOG_NNDEBUG #define ALOGVV ALOGV @@ -351,6 +354,119 @@ static void CameraMetadata_writeValues(JNIEnv *env, jobject thiz, jint tag, jbyt } } +struct DumpMetadataParams { + int writeFd; + const CameraMetadata* metadata; +}; + +static void* CameraMetadata_writeMetadataThread(void* arg) { + DumpMetadataParams* p = static_cast<DumpMetadataParams*>(arg); + + /* + * Write the dumped data, and close the writing side FD. + */ + p->metadata->dump(p->writeFd, /*verbosity*/2); + + if (close(p->writeFd) < 0) { + ALOGE("%s: Failed to close writeFd (errno = %#x, message = '%s')", + __FUNCTION__, errno, strerror(errno)); + } + + return NULL; +} + +static void CameraMetadata_dump(JNIEnv *env, jobject thiz) { + ALOGV("%s", __FUNCTION__); + CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz); + if (metadata == NULL) { + return; + } + + /* + * Create a socket pair for local streaming read/writes. + * + * The metadata will be dumped into the write side, + * and then read back out (and logged) via the read side. + */ + + int writeFd, readFd; + { + + int sv[2]; + if (socketpair(AF_LOCAL, SOCK_STREAM, /*protocol*/0, &sv[0]) < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to create socketpair (errno = %#x, message = '%s')", + errno, strerror(errno)); + return; + } + writeFd = sv[0]; + readFd = sv[1]; + } + + /* + * Create a thread for doing the writing. + * + * The reading and writing must be concurrent, otherwise + * the write will block forever once it exhausts the capped + * buffer size (from getsockopt). + */ + pthread_t writeThread; + DumpMetadataParams params = { + writeFd, + metadata + }; + + { + int threadRet = pthread_create(&writeThread, /*attr*/NULL, + CameraMetadata_writeMetadataThread, (void*)¶ms); + + if (threadRet != 0) { + close(writeFd); + + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to create thread for writing (errno = %#x, message = '%s')", + threadRet, strerror(threadRet)); + } + } + + /* + * Read out a byte until stream is complete. Write completed lines + * to ALOG. + */ + { + char out[] = {'\0', '\0'}; // large enough to append as a string + String8 logLine; + + // Read one byte at a time! Very slow but avoids complicated \n scanning. + ssize_t res; + while ((res = TEMP_FAILURE_RETRY(read(readFd, &out[0], /*count*/1))) > 0) { + if (out[0] == '\n') { + ALOGD("%s", logLine.string()); + logLine.clear(); + } else { + logLine.append(out); + } + } + + if (res < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Failed to read from fd (errno = %#x, message = '%s')", + errno, strerror(errno)); + //return; + } else if (!logLine.isEmpty()) { + ALOGD("%s", logLine.string()); + } + } + + int res; + + // Join until thread finishes. Ensures params/metadata is valid until then. + if ((res = pthread_join(writeThread, /*retval*/NULL)) != 0) { + ALOGE("%s: Failed to join thread (errno = %#x, message = '%s')", + __FUNCTION__, res, strerror(res)); + } +} + static void CameraMetadata_readFromParcel(JNIEnv *env, jobject thiz, jobject parcel) { ALOGV("%s", __FUNCTION__); CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, thiz); @@ -436,6 +552,9 @@ static JNINativeMethod gCameraMetadataMethods[] = { { "nativeWriteValues", "(I[B)V", (void *)CameraMetadata_writeValues }, + { "nativeDump", + "()V", + (void *)CameraMetadata_dump }, // Parcelable interface { "nativeReadFromParcel", "(Landroid/os/Parcel;)V", diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp new file mode 100644 index 0000000..7b686e7 --- /dev/null +++ b/core/jni/android_hardware_camera2_DngCreator.cpp @@ -0,0 +1,774 @@ +/* + * Copyright 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "DngCreator_JNI" + +#include <system/camera_metadata.h> +#include <camera/CameraMetadata.h> +#include <img_utils/DngUtils.h> +#include <img_utils/TagDefinitions.h> +#include <img_utils/TiffIfd.h> +#include <img_utils/TiffWriter.h> +#include <img_utils/Output.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/StrongPointer.h> +#include <utils/RefBase.h> +#include <cutils/properties.h> + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_hardware_camera2_CameraMetadata.h" + +#include <jni.h> +#include <JNIHelp.h> + +using namespace android; +using namespace img_utils; + +#define BAIL_IF_INVALID(expr, jnienv, tagId) \ + if ((expr) != OK) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Invalid metadata for tag %x", tagId); \ + return; \ + } + +#define BAIL_IF_EMPTY(entry, jnienv, tagId) \ + if (entry.count == 0) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Missing metadata fields for tag %x", tagId); \ + return; \ + } + +#define ANDROID_DNGCREATOR_CTX_JNI_ID "mNativeContext" + +static struct { + jfieldID mNativeContext; +} gDngCreatorClassInfo; + +static struct { + jmethodID mWriteMethod; +} gOutputStreamClassInfo; + +enum { + BITS_PER_SAMPLE = 16, + BYTES_PER_SAMPLE = 2, + TIFF_IFD_0 = 0 +}; + +// ---------------------------------------------------------------------------- + +// This class is not intended to be used across JNI calls. +class JniOutputStream : public Output, public LightRefBase<JniOutputStream> { +public: + JniOutputStream(JNIEnv* env, jobject outStream); + + virtual ~JniOutputStream(); + + status_t open(); + status_t write(const uint8_t* buf, size_t offset, size_t count); + status_t close(); +private: + enum { + BYTE_ARRAY_LENGTH = 1024 + }; + jobject mOutputStream; + JNIEnv* mEnv; + jbyteArray mByteArray; +}; + +JniOutputStream::JniOutputStream(JNIEnv* env, jobject outStream) : mOutputStream(outStream), + mEnv(env) { + mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH); + if (mByteArray == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array."); + } +} + +JniOutputStream::~JniOutputStream() { + mEnv->DeleteLocalRef(mByteArray); +} + +status_t JniOutputStream::open() { + // Do nothing + return OK; +} + +status_t JniOutputStream::write(const uint8_t* buf, size_t offset, size_t count) { + while(count > 0) { + size_t len = BYTE_ARRAY_LENGTH; + len = (count > len) ? len : count; + mEnv->SetByteArrayRegion(mByteArray, 0, len, reinterpret_cast<const jbyte*>(buf + offset)); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + mEnv->CallVoidMethod(mOutputStream, gOutputStreamClassInfo.mWriteMethod, mByteArray, + 0, len); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + count -= len; + offset += len; + } + return OK; +} + +status_t JniOutputStream::close() { + // Do nothing + return OK; +} + +// ---------------------------------------------------------------------------- + +extern "C" { + +static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz, + gDngCreatorClassInfo.mNativeContext)); +} + +static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) { + ALOGV("%s:", __FUNCTION__); + TiffWriter* current = DngCreator_getCreator(env, thiz); + if (writer != NULL) { + writer->incStrong((void*) DngCreator_setCreator); + } + if (current) { + current->decStrong((void*) DngCreator_setCreator); + } + env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext, + reinterpret_cast<jlong>(writer.get())); +} + +static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { + ALOGV("%s:", __FUNCTION__); + + gDngCreatorClassInfo.mNativeContext = env->GetFieldID(clazz, + ANDROID_DNGCREATOR_CTX_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gDngCreatorClassInfo.mNativeContext == NULL, + "can't find android/hardware/camera2/DngCreator.%s", + ANDROID_DNGCREATOR_CTX_JNI_ID); + + jclass outputStreamClazz = env->FindClass("java/io/OutputStream"); + LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class"); + gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V"); + LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method"); +} + +static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr, + jobject resultsPtr) { + ALOGV("%s:", __FUNCTION__); + CameraMetadata characteristics; + CameraMetadata results; + if (CameraMetadata_getNativeMetadata(env, characteristicsPtr, &characteristics) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for camera characteristics."); + return; + } + if (CameraMetadata_getNativeMetadata(env, resultsPtr, &results) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for capture results."); + return; + } + + sp<TiffWriter> writer = new TiffWriter(); + + writer->addIfd(TIFF_IFD_0); + + status_t err = OK; + + const uint32_t samplesPerPixel = 1; + const uint32_t bitsPerSample = BITS_PER_SAMPLE; + const uint32_t bitsPerByte = BITS_PER_SAMPLE / BYTES_PER_SAMPLE; + uint32_t imageWidth = 0; + uint32_t imageHeight = 0; + + OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; + + // TODO: Greensplit. + // TODO: UniqueCameraModel + // TODO: Add remaining non-essential tags + { + // Set orientation + uint16_t orientation = 1; // Normal + BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env, + TAG_ORIENTATION); + } + + { + // Set subfiletype + uint32_t subfileType = 0; // Main image + BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env, + TAG_NEWSUBFILETYPE); + } + + { + // Set bits per sample + uint16_t bits = static_cast<uint16_t>(bitsPerSample); + BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env, + TAG_BITSPERSAMPLE); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env, + TAG_COMPRESSION); + } + + { + // Set dimensions + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); + BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH); + uint32_t width = static_cast<uint32_t>(entry.data.i32[2]); + uint32_t height = static_cast<uint32_t>(entry.data.i32[3]); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env, + TAG_IMAGEWIDTH); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env, + TAG_IMAGELENGTH); + imageWidth = width; + imageHeight = height; + } + + { + // Set photometric interpretation + uint16_t interpretation = 32803; + BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation, + TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION); + } + + { + // Set blacklevel tags + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN); + BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL); + const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32); + BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env, + TAG_BLACKLEVEL); + + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env, + TAG_BLACKLEVELREPEATDIM); + } + + { + // Set samples per pixel + uint16_t samples = static_cast<uint16_t>(samplesPerPixel); + BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0), + env, TAG_SAMPLESPERPIXEL); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0), + env, TAG_PLANARCONFIGURATION); + } + + { + // Set CFA pattern dimensions + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0), + env, TAG_CFAREPEATPATTERNDIM); + } + + { + // Set CFA pattern + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); + BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN); + camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa = + static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>( + entry.data.u8[0]); + switch(cfa) { + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: { + uint8_t cfa[4] = {0, 1, 1, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: { + uint8_t cfa[4] = {1, 0, 2, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: { + uint8_t cfa[4] = {1, 2, 0, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG; + break; + } + case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: { + uint8_t cfa[4] = {2, 1, 1, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN); + opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR; + break; + } + default: { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid metadata for tag %d", TAG_CFAPATTERN); + return; + } + } + } + + { + // Set CFA plane color + uint8_t cfaPlaneColor[3] = {0, 1, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0), + env, TAG_CFAPLANECOLOR); + } + + { + // Set CFA layout + uint16_t cfaLayout = 1; + BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0), + env, TAG_CFALAYOUT); + } + + { + // Set DNG version information + uint8_t version[4] = {1, 4, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0), + env, TAG_DNGVERSION); + + uint8_t backwardVersion[4] = {1, 1, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0), + env, TAG_DNGBACKWARDVERSION); + } + + { + // Set whitelevel + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL); + BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL); + uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]); + BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env, + TAG_WHITELEVEL); + } + + { + // Set default scale + uint32_t defaultScale[4] = {1, 1, 1, 1}; + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0), + env, TAG_DEFAULTSCALE); + } + + bool singleIlluminant = false; + { + // Set calibration illuminants + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1); + BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1); + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2); + if (entry2.count == 0) { + singleIlluminant = true; + } + uint16_t ref1 = entry1.data.u8[0]; + + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1); + + if (!singleIlluminant) { + uint16_t ref2 = entry2.data.u8[0]; + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2); + } + } + + { + // Set color transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1); + + int32_t colorTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + colorTransform1[ctr++] = entry1.data.r[i].numerator; + colorTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0), + env, TAG_COLORMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2); + int32_t colorTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + colorTransform2[ctr++] = entry2.data.r[i].numerator; + colorTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0), + env, TAG_COLORMATRIX2); + } + } + + { + // Set calibration transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1); + + int32_t calibrationTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + calibrationTransform1[ctr++] = entry1.data.r[i].numerator; + calibrationTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2); + int32_t calibrationTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + calibrationTransform2[ctr++] = entry2.data.r[i].numerator; + calibrationTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION2); + } + } + + { + // Set forward transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1); + BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1); + + int32_t forwardTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + forwardTransform1[ctr++] = entry1.data.r[i].numerator; + forwardTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1, + TIFF_IFD_0), env, TAG_FORWARDMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2); + BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2); + int32_t forwardTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + forwardTransform2[ctr++] = entry2.data.r[i].numerator; + forwardTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2, + TIFF_IFD_0), env, TAG_FORWARDMATRIX2); + } + } + + { + // Set camera neutral + camera_metadata_entry entry = + results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT); + BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL); + uint32_t cameraNeutral[entry.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry.count; ++i) { + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].numerator); + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].denominator); + } + + BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral, + TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL); + } + + { + // Setup data strips + // TODO: Switch to tiled implementation. + uint32_t offset = 0; + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env, + TAG_STRIPOFFSETS); + + BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env, + TAG_ROWSPERSTRIP); + + uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel / + bitsPerByte; + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env, + TAG_STRIPBYTECOUNTS); + } + + { + // Setup default crop + crop origin tags + uint32_t margin = 8; // Default margin recommended by Adobe for interpolation. + uint32_t dimensionLimit = 128; // Smallest image dimension crop margin from. + if (imageWidth >= dimensionLimit && imageHeight >= dimensionLimit) { + uint32_t defaultCropOrigin[] = {margin, margin}; + uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin}; + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin, + TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN); + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize, + TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE); + } + } + + { + // Setup unique camera model tag + char model[PROPERTY_VALUE_MAX]; + property_get("ro.product.model", model, ""); + + char manufacturer[PROPERTY_VALUE_MAX]; + property_get("ro.product.manufacturer", manufacturer, ""); + + char brand[PROPERTY_VALUE_MAX]; + property_get("ro.product.brand", brand, ""); + + String8 cameraModel(model); + cameraModel += "-"; + cameraModel += manufacturer; + cameraModel += "-"; + cameraModel += brand; + + BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1, + reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env, + TAG_UNIQUECAMERAMODEL); + } + + { + // Setup opcode List 2 + camera_metadata_entry entry1 = + characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE); + BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2); + uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]); + uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]); + + camera_metadata_entry entry2 = + results.find(ANDROID_STATISTICS_LENS_SHADING_MAP); + BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2); + if (entry2.count == lsmWidth * lsmHeight * 4) { + + OpcodeListBuilder builder; + status_t err = builder.addGainMapsForMetadata(lsmWidth, + lsmHeight, + 0, + 0, + imageHeight, + imageWidth, + opcodeCfaLayout, + entry2.data.f); + if (err == OK) { + size_t listSize = builder.getSize(); + uint8_t opcodeListBuf[listSize]; + err = builder.buildOpList(opcodeListBuf); + if (err == OK) { + BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf, + TIFF_IFD_0), env, TAG_OPCODELIST2); + } else { + ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to construct lens shading map opcode."); + } + } else { + ALOGE("%s: Could not add Lens shading map.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to add lens shading map."); + } + } else { + ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__); + } + } + + DngCreator_setCreator(env, thiz, writer); +} + +static void DngCreator_destroy(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + DngCreator_setCreator(env, thiz, NULL); +} + +static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetOrientation is not implemented"); +} + +static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented"); +} + +static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height, + jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride, + jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented"); +} + +static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width, + jint height, jobject inBuffer, jint rowStride, jint pixStride) { + ALOGV("%s:", __FUNCTION__); + + sp<JniOutputStream> out = new JniOutputStream(env, outStream); + if(env->ExceptionCheck()) { + ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + return; + } + + uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer)); + if (pixelBytes == NULL) { + ALOGE("%s: Could not get native byte buffer", __FUNCTION__); + jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer"); + return; + } + + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "Write called with uninitialized DngCreator"); + return; + } + // TODO: handle lens shading map, etc. conversions for other raw buffer sizes. + uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>()); + uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>()); + if (metadataWidth != width) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ + "Metadata width %d doesn't match image width %d", metadataWidth, width); + return; + } + + if (metadataHeight != height) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ + "Metadata height %d doesn't match image height %d", metadataHeight, height); + return; + } + + uint32_t stripOffset = writer->getTotalSize(); + + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env, + TAG_STRIPOFFSETS); + + if (writer->write(out.get()) != OK) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write metadata"); + } + return; + } + + size_t fullSize = rowStride * height; + jlong capacity = env->GetDirectBufferCapacity(inBuffer); + if (capacity < 0 || fullSize > capacity) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid size %d for Image, size given in metadata is %d at current stride", + capacity, fullSize); + return; + } + + if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) { + if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } else if (pixStride == BYTES_PER_SAMPLE) { + for (size_t i = 0; i < height; ++i) { + if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK || + env->ExceptionCheck()) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } + } else { + for (size_t i = 0; i < height; ++i) { + for (size_t j = 0; j < width; ++j) { + if (out->write(pixelBytes, i * rowStride + j * pixStride, + BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) { + if (env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write pixel data"); + } + return; + } + } + } + } + +} + +static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream, + jobject rawBuffer, jlong offset) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented."); +} + +static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream, + jobject inStream, jlong offset) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented."); +} + +} /*extern "C" */ + +static JNINativeMethod gDngCreatorMethods[] = { + {"nativeClassInit", "()V", (void*) DngCreator_nativeClassInit}, + {"nativeInit", "(Landroid/hardware/camera2/impl/CameraMetadataNative;" + "Landroid/hardware/camera2/impl/CameraMetadataNative;)V", (void*) DngCreator_init}, + {"nativeDestroy", "()V", (void*) DngCreator_destroy}, + {"nativeSetOrientation", "(I)V", (void*) DngCreator_nativeSetOrientation}, + {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V", + (void*) DngCreator_nativeSetThumbnailBitmap}, + {"nativeSetThumbnailImage", + "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V", + (void*) DngCreator_nativeSetThumbnailImage}, + {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V", + (void*) DngCreator_nativeWriteImage}, + {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V", + (void*) DngCreator_nativeWriteByteBuffer}, + {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V", + (void*) DngCreator_nativeWriteInputStream}, +}; + +int register_android_hardware_camera2_DngCreator(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/hardware/camera2/DngCreator", gDngCreatorMethods, + NELEM(gDngCreatorMethods)); +} diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index fedb1b2..a2b1ed9 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -23,6 +23,11 @@ #define ENCODING_PCM_16BIT 2 #define ENCODING_PCM_8BIT 3 #define ENCODING_PCM_FLOAT 4 +#define ENCODING_INVALID 0 +#define ENCODING_DEFAULT 1 + +#define CHANNEL_INVALID 0 +#define CHANNEL_OUT_DEFAULT 1 static inline audio_format_t audioFormatToNative(int audioFormat) { @@ -33,9 +38,58 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_PCM_8_BIT; case ENCODING_PCM_FLOAT: return AUDIO_FORMAT_PCM_FLOAT; + case ENCODING_DEFAULT: + return AUDIO_FORMAT_DEFAULT; default: return AUDIO_FORMAT_INVALID; } } +static inline int audioFormatFromNative(audio_format_t nativeFormat) +{ + switch (nativeFormat) { + case AUDIO_FORMAT_PCM_16_BIT: + return ENCODING_PCM_16BIT; + case AUDIO_FORMAT_PCM_8_BIT: + return ENCODING_PCM_8BIT; + case AUDIO_FORMAT_PCM_FLOAT: + return ENCODING_PCM_FLOAT; + case AUDIO_FORMAT_DEFAULT: + return ENCODING_DEFAULT; + default: + return ENCODING_INVALID; + } +} + +static inline audio_channel_mask_t outChannelMaskToNative(int channelMask) +{ + switch (channelMask) { + case CHANNEL_OUT_DEFAULT: + case CHANNEL_INVALID: + return AUDIO_CHANNEL_NONE; + default: + return (audio_channel_mask_t)(channelMask>>2); + } +} + +static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask) +{ + switch (nativeMask) { + case AUDIO_CHANNEL_NONE: + return CHANNEL_OUT_DEFAULT; + default: + return (int)nativeMask<<2; + } +} + +static inline audio_channel_mask_t inChannelMaskToNative(int channelMask) +{ + return (audio_channel_mask_t)channelMask; +} + +static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask) +{ + return (int)nativeMask; +} + #endif // ANDROID_MEDIA_AUDIOFORMAT_H diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index a9a62f8..0f7e140 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -15,7 +15,9 @@ ** limitations under the License. */ -#define LOG_TAG "AudioSystem" +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioSystem-JNI" #include <utils/Log.h> #include <jni.h> @@ -26,6 +28,8 @@ #include <system/audio.h> #include <system/audio_policy.h> +#include "android_media_AudioFormat.h" +#include "android_media_AudioErrors.h" // ---------------------------------------------------------------------------- @@ -33,12 +37,160 @@ using namespace android; static const char* const kClassPathName = "android/media/AudioSystem"; +static jclass gArrayListClass; +static struct { + jmethodID add; +} gArrayListMethods; + +static jclass gAudioHandleClass; +static jmethodID gAudioHandleCstor; +static struct { + jfieldID mId; +} gAudioHandleFields; + +static jclass gAudioPortClass; +static jmethodID gAudioPortCstor; +static struct { + jfieldID mHandle; + jfieldID mRole; + jfieldID mGains; + jfieldID mActiveConfig; + // other fields unused by JNI +} gAudioPortFields; + +static jclass gAudioPortConfigClass; +static jmethodID gAudioPortConfigCstor; +static struct { + jfieldID mPort; + jfieldID mSamplingRate; + jfieldID mChannelMask; + jfieldID mFormat; + jfieldID mGain; + jfieldID mConfigMask; +} gAudioPortConfigFields; + +static jclass gAudioDevicePortClass; +static jmethodID gAudioDevicePortCstor; + +static jclass gAudioDevicePortConfigClass; +static jmethodID gAudioDevicePortConfigCstor; + +static jclass gAudioMixPortClass; +static jmethodID gAudioMixPortCstor; + +static jclass gAudioMixPortConfigClass; +static jmethodID gAudioMixPortConfigCstor; + +static jclass gAudioGainClass; +static jmethodID gAudioGainCstor; + +static jclass gAudioGainConfigClass; +static jmethodID gAudioGainConfigCstor; +static struct { + jfieldID mIndex; + jfieldID mMode; + jfieldID mChannelMask; + jfieldID mValues; + jfieldID mRampDurationMs; + // other fields unused by JNI +} gAudioGainConfigFields; + +static jclass gAudioPatchClass; +static jmethodID gAudioPatchCstor; +static struct { + jfieldID mHandle; + // other fields unused by JNI +} gAudioPatchFields; + +static const char* const kEventHandlerClassPathName = + "android/media/AudioPortEventHandler"; +static jmethodID gPostEventFromNative; + enum AudioError { kAudioStatusOk = 0, kAudioStatusError = 1, kAudioStatusMediaServerDied = 100 }; +enum { + AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1, + AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2, + AUDIOPORT_EVENT_SERVICE_DIED = 3, +}; + +#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5 + +// ---------------------------------------------------------------------------- +// ref-counted object for callbacks +class JNIAudioPortCallback: public AudioSystem::AudioPortCallback +{ +public: + JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz); + ~JNIAudioPortCallback(); + + virtual void onAudioPortListUpdate(); + virtual void onAudioPatchListUpdate(); + virtual void onServiceDied(); + +private: + void sendEvent(int event); + + jclass mClass; // Reference to AudioPortEventHandlerDelegate class + jobject mObject; // Weak ref to AudioPortEventHandlerDelegate Java object to call on +}; + +JNIAudioPortCallback::JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz) +{ + + // Hold onto the SoundTriggerModule class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + ALOGE("Can't find class %s", kEventHandlerClassPathName); + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the SoundTriggerModule object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); +} + +JNIAudioPortCallback::~JNIAudioPortCallback() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIAudioPortCallback::sendEvent(int event) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject, + event, 0, 0, NULL); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + +void JNIAudioPortCallback::onAudioPortListUpdate() +{ + sendEvent(AUDIOPORT_EVENT_PORT_LIST_UPDATED); +} + +void JNIAudioPortCallback::onAudioPatchListUpdate() +{ + sendEvent(AUDIOPORT_EVENT_PATCH_LIST_UPDATED); +} + +void JNIAudioPortCallback::onServiceDied() +{ + sendEvent(AUDIOPORT_EVENT_SERVICE_DIED); +} + static int check_AudioSystem_Command(status_t status) { switch (status) { @@ -281,6 +433,854 @@ android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz) return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger()); } + +static bool useInChannelMask(audio_port_type_t type, audio_port_role_t role) +{ + return ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) || + ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK)); +} + +static void convertAudioGainConfigToNative(JNIEnv *env, + struct audio_gain_config *nAudioGainConfig, + const jobject jAudioGainConfig, + bool useInMask) +{ + nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex); + nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode); + ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index); + jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask); + audio_channel_mask_t nMask; + if (useInMask) { + nMask = inChannelMaskToNative(jMask); + ALOGV("convertAudioGainConfigToNative IN mask java %x native %x", jMask, nMask); + } else { + nMask = outChannelMaskToNative(jMask); + ALOGV("convertAudioGainConfigToNative OUT mask java %x native %x", jMask, nMask); + } + nAudioGainConfig->channel_mask = nMask; + nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig, + gAudioGainConfigFields.mRampDurationMs); + jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig, + gAudioGainConfigFields.mValues); + int *nValues = env->GetIntArrayElements(jValues, NULL); + size_t size = env->GetArrayLength(jValues); + memcpy(nAudioGainConfig->values, nValues, size * sizeof(int)); + env->DeleteLocalRef(jValues); +} + + +static jint convertAudioPortConfigToNative(JNIEnv *env, + struct audio_port_config *nAudioPortConfig, + const jobject jAudioPortConfig) +{ + jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort); + jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle); + nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId); + nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort, + gAudioPortFields.mRole); + if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) { + nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE; + } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) { + nAudioPortConfig->type = AUDIO_PORT_TYPE_MIX; + } else { + env->DeleteLocalRef(jAudioPort); + env->DeleteLocalRef(jHandle); + return (jint)AUDIO_JAVA_ERROR; + } + ALOGV("convertAudioPortConfigToNative handle %d role %d type %d", + nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type); + + nAudioPortConfig->sample_rate = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mSamplingRate); + + bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role); + audio_channel_mask_t nMask; + jint jMask = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mChannelMask); + if (useInMask) { + nMask = inChannelMaskToNative(jMask); + ALOGV("convertAudioPortConfigToNative IN mask java %x native %x", jMask, nMask); + } else { + nMask = outChannelMaskToNative(jMask); + ALOGV("convertAudioPortConfigToNative OUT mask java %x native %x", jMask, nMask); + } + nAudioPortConfig->channel_mask = nMask; + + jint jFormat = env->GetIntField(jAudioPortConfig, gAudioPortConfigFields.mFormat); + audio_format_t nFormat = audioFormatToNative(jFormat); + ALOGV("convertAudioPortConfigToNative format %d native %d", jFormat, nFormat); + nAudioPortConfig->format = nFormat; + jobject jGain = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mGain); + if (jGain != NULL) { + convertAudioGainConfigToNative(env, &nAudioPortConfig->gain, jGain, useInMask); + env->DeleteLocalRef(jGain); + } else { + ALOGV("convertAudioPortConfigToNative no gain"); + nAudioPortConfig->gain.index = -1; + } + nAudioPortConfig->config_mask = env->GetIntField(jAudioPortConfig, + gAudioPortConfigFields.mConfigMask); + + env->DeleteLocalRef(jAudioPort); + env->DeleteLocalRef(jHandle); + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint convertAudioPortConfigFromNative(JNIEnv *env, + jobject jAudioPort, + jobject *jAudioPortConfig, + const struct audio_port_config *nAudioPortConfig) +{ + jint jStatus = AUDIO_JAVA_SUCCESS; + jobject jAudioGainConfig = NULL; + jobject jAudioGain = NULL; + jintArray jGainValues; + bool audioportCreated = false; + + ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort); + + if (jAudioPort == NULL) { + jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nAudioPortConfig->id); + + ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id, + nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix"); + + if (jHandle == NULL) { + return (jint)AUDIO_JAVA_ERROR; + } + // create dummy port and port config objects with just the correct handle + // and configuration data. The actual AudioPortConfig objects will be + // constructed by java code with correct class type (device, mix etc...) + // and reference to AudioPort instance in this client + jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor, + jHandle, + 0, + NULL, + NULL, + NULL, + NULL); + env->DeleteLocalRef(jHandle); + if (jAudioPort == NULL) { + return (jint)AUDIO_JAVA_ERROR; + } + ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d", + nAudioPortConfig->id); + + audioportCreated = true; + } + + bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role); + + audio_channel_mask_t nMask; + jint jMask; + + int gainIndex = nAudioPortConfig->gain.index; + if (gainIndex >= 0) { + ALOGV("convertAudioPortConfigFromNative gain found with index %d mode %x", + gainIndex, nAudioPortConfig->gain.mode); + if (audioportCreated) { + ALOGV("convertAudioPortConfigFromNative creating gain"); + jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor, + gainIndex, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0); + if (jAudioGain == NULL) { + ALOGV("convertAudioPortConfigFromNative creating gain FAILED"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } else { + ALOGV("convertAudioPortConfigFromNative reading gain from port"); + jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort, + gAudioPortFields.mGains); + if (jGains == NULL) { + ALOGV("convertAudioPortConfigFromNative could not get gains from port"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + jAudioGain = env->GetObjectArrayElement(jGains, gainIndex); + env->DeleteLocalRef(jGains); + if (jAudioGain == NULL) { + ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + //TODO: replace popcount by audio utils function mask to count + int numValues = popcount(nAudioPortConfig->gain.channel_mask); + jGainValues = env->NewIntArray(numValues); + if (jGainValues == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetIntArrayRegion(jGainValues, 0, numValues, + nAudioPortConfig->gain.values); + + nMask = nAudioPortConfig->gain.channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + jAudioGainConfig = env->NewObject(gAudioGainConfigClass, + gAudioGainConfigCstor, + gainIndex, + jAudioGain, + nAudioPortConfig->gain.mode, + jMask, + jGainValues, + nAudioPortConfig->gain.ramp_duration_ms); + env->DeleteLocalRef(jGainValues); + if (jAudioGainConfig == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create gain config"); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + jclass clazz; + jmethodID methodID; + if (audioportCreated) { + clazz = gAudioPortConfigClass; + methodID = gAudioPortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a generic port config"); + } else { + if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) { + clazz = gAudioDevicePortConfigClass; + methodID = gAudioDevicePortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a device config"); + } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) { + clazz = gAudioMixPortConfigClass; + methodID = gAudioMixPortConfigCstor; + ALOGV("convertAudioPortConfigFromNative building a mix config"); + } else { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + } + nMask = nAudioPortConfig->channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + *jAudioPortConfig = env->NewObject(clazz, methodID, + jAudioPort, + nAudioPortConfig->sample_rate, + jMask, + audioFormatFromNative(nAudioPortConfig->format), + jAudioGainConfig); + if (*jAudioPortConfig == NULL) { + ALOGV("convertAudioPortConfigFromNative could not create new port config"); + jStatus = (jint)AUDIO_JAVA_ERROR; + } else { + ALOGV("convertAudioPortConfigFromNative OK"); + } + +exit: + if (audioportCreated) { + env->DeleteLocalRef(jAudioPort); + if (jAudioGain != NULL) { + env->DeleteLocalRef(jAudioGain); + } + } + if (jAudioGainConfig != NULL) { + env->DeleteLocalRef(jAudioGainConfig); + } + return jStatus; +} + +static jint convertAudioPortFromNative(JNIEnv *env, + jobject *jAudioPort, const struct audio_port *nAudioPort) +{ + jint jStatus = (jint)AUDIO_JAVA_SUCCESS; + jintArray jSamplingRates = NULL; + jintArray jChannelMasks = NULL; + jintArray jFormats = NULL; + jobjectArray jGains = NULL; + jobject jHandle = NULL; + bool useInMask; + + ALOGV("convertAudioPortFromNative id %d role %d type %d", + nAudioPort->id, nAudioPort->role, nAudioPort->type); + + jSamplingRates = env->NewIntArray(nAudioPort->num_sample_rates); + if (jSamplingRates == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + if (nAudioPort->num_sample_rates) { + env->SetIntArrayRegion(jSamplingRates, 0, nAudioPort->num_sample_rates, + (jint *)nAudioPort->sample_rates); + } + + jChannelMasks = env->NewIntArray(nAudioPort->num_channel_masks); + if (jChannelMasks == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + useInMask = useInChannelMask(nAudioPort->type, nAudioPort->role); + + jint jMask; + for (size_t j = 0; j < nAudioPort->num_channel_masks; j++) { + if (useInMask) { + jMask = inChannelMaskFromNative(nAudioPort->channel_masks[j]); + } else { + jMask = outChannelMaskFromNative(nAudioPort->channel_masks[j]); + } + env->SetIntArrayRegion(jChannelMasks, j, 1, &jMask); + } + + jFormats = env->NewIntArray(nAudioPort->num_formats); + if (jFormats == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + for (size_t j = 0; j < nAudioPort->num_formats; j++) { + jint jFormat = audioFormatFromNative(nAudioPort->formats[j]); + env->SetIntArrayRegion(jFormats, j, 1, &jFormat); + } + + jGains = env->NewObjectArray(nAudioPort->num_gains, + gAudioGainClass, NULL); + if (jGains == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + for (size_t j = 0; j < nAudioPort->num_gains; j++) { + audio_channel_mask_t nMask = nAudioPort->gains[j].channel_mask; + if (useInMask) { + jMask = inChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask); + } else { + jMask = outChannelMaskFromNative(nMask); + ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask); + } + + jobject jGain = env->NewObject(gAudioGainClass, gAudioGainCstor, + j, + nAudioPort->gains[j].mode, + jMask, + nAudioPort->gains[j].min_value, + nAudioPort->gains[j].max_value, + nAudioPort->gains[j].default_value, + nAudioPort->gains[j].step_value, + nAudioPort->gains[j].min_ramp_ms, + nAudioPort->gains[j].max_ramp_ms); + if (jGain == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetObjectArrayElement(jGains, j, jGain); + env->DeleteLocalRef(jGain); + } + + jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nAudioPort->id); + if (jHandle == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) { + ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type); + jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address); + *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor, + jHandle, jSamplingRates, jChannelMasks, jFormats, jGains, + nAudioPort->ext.device.type, jAddress); + env->DeleteLocalRef(jAddress); + } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { + ALOGV("convertAudioPortFromNative is a mix"); + *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, + jHandle, nAudioPort->role, jSamplingRates, jChannelMasks, + jFormats, jGains); + } else { + ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type); + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + if (*jAudioPort == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + + jobject jAudioPortConfig; + jStatus = convertAudioPortConfigFromNative(env, + *jAudioPort, + &jAudioPortConfig, + &nAudioPort->active_config); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig); + +exit: + if (jSamplingRates != NULL) { + env->DeleteLocalRef(jSamplingRates); + } + if (jChannelMasks != NULL) { + env->DeleteLocalRef(jChannelMasks); + } + if (jFormats != NULL) { + env->DeleteLocalRef(jFormats); + } + if (jGains != NULL) { + env->DeleteLocalRef(jGains); + } + if (jHandle != NULL) { + env->DeleteLocalRef(jHandle); + } + + return jStatus; +} + + +static jint +android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz, + jobject jPorts, jintArray jGeneration) +{ + ALOGV("listAudioPorts"); + + if (jPorts == NULL) { + ALOGE("listAudioPorts NULL AudioPort ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jPorts, gArrayListClass)) { + ALOGE("listAudioPorts not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + status_t status; + unsigned int generation1; + unsigned int generation; + unsigned int numPorts; + jint *nGeneration; + struct audio_port *nPorts = NULL; + int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS; + + // get the port count and all the ports until they both return the same generation + do { + if (attempts-- < 0) { + status = TIMED_OUT; + break; + } + + numPorts = 0; + status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, + AUDIO_PORT_TYPE_NONE, + &numPorts, + NULL, + &generation1); + if (status != NO_ERROR || numPorts == 0) { + ALOGE_IF(status != NO_ERROR, "AudioSystem::listAudioPorts error %d", status); + break; + } + nPorts = (struct audio_port *)realloc(nPorts, numPorts * sizeof(struct audio_port)); + + status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE, + AUDIO_PORT_TYPE_NONE, + &numPorts, + nPorts, + &generation); + ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d", + numPorts, generation, generation1); + } while (generation1 != generation && status == NO_ERROR); + + jint jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + nGeneration = env->GetIntArrayElements(jGeneration, NULL); + if (nGeneration == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + nGeneration[0] = generation1; + env->ReleaseIntArrayElements(jGeneration, nGeneration, 0); + + for (size_t i = 0; i < numPorts; i++) { + jobject jAudioPort; + jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort); + } + +exit: + free(nPorts); + return jStatus; +} + +static int +android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz, + jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks) +{ + status_t status; + jint jStatus; + + ALOGV("createAudioPatch"); + if (jPatches == NULL || jSources == NULL || jSinks == NULL) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (env->GetArrayLength(jPatches) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jint numSources = env->GetArrayLength(jSources); + if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + jint numSinks = env->GetArrayLength(jSinks); + if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + audio_patch_handle_t handle = (audio_patch_handle_t)0; + jobject jPatch = env->GetObjectArrayElement(jPatches, 0); + jobject jPatchHandle = NULL; + if (jPatch != NULL) { + if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle); + handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId); + } + + struct audio_patch nPatch; + + nPatch.id = handle; + nPatch.num_sources = 0; + nPatch.num_sinks = 0; + jobject jSource = NULL; + jobject jSink = NULL; + + for (jint i = 0; i < numSources; i++) { + jSource = env->GetObjectArrayElement(jSources, i); + if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) { + jStatus = (jint)AUDIO_JAVA_BAD_VALUE; + goto exit; + } + jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource); + env->DeleteLocalRef(jSource); + jSource = NULL; + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + nPatch.num_sources++; + } + + for (jint i = 0; i < numSinks; i++) { + jSink = env->GetObjectArrayElement(jSinks, i); + if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) { + jStatus = (jint)AUDIO_JAVA_BAD_VALUE; + goto exit; + } + jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink); + env->DeleteLocalRef(jSink); + jSink = NULL; + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + nPatch.num_sinks++; + } + + ALOGV("AudioSystem::createAudioPatch"); + status = AudioSystem::createAudioPatch(&nPatch, &handle); + ALOGV("AudioSystem::createAudioPatch() returned %d hande %d", status, handle); + + jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + if (jPatchHandle == NULL) { + jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + handle); + if (jPatchHandle == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks); + if (jPatch == NULL) { + jStatus = (jint)AUDIO_JAVA_ERROR; + goto exit; + } + env->SetObjectArrayElement(jPatches, 0, jPatch); + } else { + env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle); + } + +exit: + if (jPatchHandle != NULL) { + env->DeleteLocalRef(jPatchHandle); + } + if (jPatch != NULL) { + env->DeleteLocalRef(jPatch); + } + if (jSource != NULL) { + env->DeleteLocalRef(jSource); + } + if (jSink != NULL) { + env->DeleteLocalRef(jSink); + } + return jStatus; +} + +static int +android_media_AudioSystem_releaseAudioPatch(JNIEnv *env, jobject clazz, + jobject jPatch) +{ + ALOGV("releaseAudioPatch"); + if (jPatch == NULL) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + audio_patch_handle_t handle = (audio_patch_handle_t)0; + jobject jPatchHandle = NULL; + if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle); + handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId); + env->DeleteLocalRef(jPatchHandle); + + ALOGV("AudioSystem::releaseAudioPatch"); + status_t status = AudioSystem::releaseAudioPatch(handle); + ALOGV("AudioSystem::releaseAudioPatch() returned %d", status); + jint jStatus = nativeToJavaStatus(status); + return status; +} + +static jint +android_media_AudioSystem_listAudioPatches(JNIEnv *env, jobject clazz, + jobject jPatches, jintArray jGeneration) +{ + ALOGV("listAudioPatches"); + if (jPatches == NULL) { + ALOGE("listAudioPatches NULL AudioPatch ArrayList"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jPatches, gArrayListClass)) { + ALOGE("listAudioPatches not an arraylist"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + status_t status; + unsigned int generation1; + unsigned int generation; + unsigned int numPatches; + jint *nGeneration; + struct audio_patch *nPatches = NULL; + jobjectArray jSources = NULL; + jobject jSource = NULL; + jobjectArray jSinks = NULL; + jobject jSink = NULL; + jobject jPatch = NULL; + int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS; + + // get the patch count and all the patches until they both return the same generation + do { + if (attempts-- < 0) { + status = TIMED_OUT; + break; + } + + numPatches = 0; + status = AudioSystem::listAudioPatches(&numPatches, + NULL, + &generation1); + if (status != NO_ERROR || numPatches == 0) { + ALOGE_IF(status != NO_ERROR, "listAudioPatches AudioSystem::listAudioPatches error %d", + status); + break; + } + nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch)); + + status = AudioSystem::listAudioPatches(&numPatches, + nPatches, + &generation); + ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d", + numPatches, generation, generation1); + + } while (generation1 != generation && status == NO_ERROR); + + jint jStatus = nativeToJavaStatus(status); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + + nGeneration = env->GetIntArrayElements(jGeneration, NULL); + if (nGeneration == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + nGeneration[0] = generation1; + env->ReleaseIntArrayElements(jGeneration, nGeneration, 0); + + for (size_t i = 0; i < numPatches; i++) { + jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor, + nPatches[i].id); + if (patchHandle == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + ALOGV("listAudioPatches patch %d num_sources %d num_sinks %d", + i, nPatches[i].num_sources, nPatches[i].num_sinks); + + env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id); + + // load sources + jSources = env->NewObjectArray(nPatches[i].num_sources, + gAudioPortConfigClass, NULL); + if (jSources == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + + for (size_t j = 0; j < nPatches[i].num_sources; j++) { + jStatus = convertAudioPortConfigFromNative(env, + NULL, + &jSource, + &nPatches[i].sources[j]); + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->SetObjectArrayElement(jSources, j, jSource); + env->DeleteLocalRef(jSource); + jSource = NULL; + ALOGV("listAudioPatches patch %d source %d is a %s handle %d", + i, j, + nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix", + nPatches[i].sources[j].id); + } + // load sinks + jSinks = env->NewObjectArray(nPatches[i].num_sinks, + gAudioPortConfigClass, NULL); + if (jSinks == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + + for (size_t j = 0; j < nPatches[i].num_sinks; j++) { + jStatus = convertAudioPortConfigFromNative(env, + NULL, + &jSink, + &nPatches[i].sinks[j]); + + if (jStatus != AUDIO_JAVA_SUCCESS) { + goto exit; + } + env->SetObjectArrayElement(jSinks, j, jSink); + env->DeleteLocalRef(jSink); + jSink = NULL; + ALOGV("listAudioPatches patch %d sink %d is a %s handle %d", + i, j, + nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix", + nPatches[i].sinks[j].id); + } + + jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, + patchHandle, jSources, jSinks); + env->DeleteLocalRef(jSources); + jSources = NULL; + env->DeleteLocalRef(jSinks); + jSinks = NULL; + if (jPatch == NULL) { + jStatus = AUDIO_JAVA_ERROR; + goto exit; + } + env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch); + env->DeleteLocalRef(jPatch); + jPatch = NULL; + } + +exit: + if (jSources != NULL) { + env->DeleteLocalRef(jSources); + } + if (jSource != NULL) { + env->DeleteLocalRef(jSource); + } + if (jSinks != NULL) { + env->DeleteLocalRef(jSinks); + } + if (jSink != NULL) { + env->DeleteLocalRef(jSink); + } + if (jPatch != NULL) { + env->DeleteLocalRef(jPatch); + } + free(nPatches); + return jStatus; +} + +static jint +android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz, + jobject jAudioPortConfig) +{ + ALOGV("setAudioPortConfig"); + if (jAudioPortConfig == NULL) { + return AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) { + return AUDIO_JAVA_BAD_VALUE; + } + struct audio_port_config nAudioPortConfig; + jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig); + ALOGV("AudioSystem::setAudioPortConfig() returned %d", status); + jStatus = nativeToJavaStatus(status); + return jStatus; +} + +static void +android_media_AudioSystem_eventHandlerSetup(JNIEnv *env, jobject thiz, jobject weak_this) +{ + ALOGV("eventHandlerSetup"); + + sp<JNIAudioPortCallback> callback = new JNIAudioPortCallback(env, thiz, weak_this); + + AudioSystem::setAudioPortCallback(callback); +} + +static void +android_media_AudioSystem_eventHandlerFinalize(JNIEnv *env, jobject thiz) +{ + ALOGV("eventHandlerFinalize"); + + sp<JNIAudioPortCallback> callback; + + AudioSystem::setAudioPortCallback(callback); +} + // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -309,12 +1309,123 @@ static JNINativeMethod gMethods[] = { {"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency}, {"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice}, {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger}, + {"listAudioPorts", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPorts}, + {"createAudioPatch", "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I", + (void *)android_media_AudioSystem_createAudioPatch}, + {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I", + (void *)android_media_AudioSystem_releaseAudioPatch}, + {"listAudioPatches", "(Ljava/util/ArrayList;[I)I", + (void *)android_media_AudioSystem_listAudioPatches}, + {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I", + (void *)android_media_AudioSystem_setAudioPortConfig}, +}; + + +static JNINativeMethod gEventHandlerMethods[] = { + {"native_setup", + "(Ljava/lang/Object;)V", + (void *)android_media_AudioSystem_eventHandlerSetup}, + {"native_finalize", + "()V", + (void *)android_media_AudioSystem_eventHandlerFinalize}, }; int register_android_media_AudioSystem(JNIEnv *env) { + + jclass arrayListClass = env->FindClass("java/util/ArrayList"); + gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass); + gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); + + jclass audioHandleClass = env->FindClass("android/media/AudioHandle"); + gAudioHandleClass = (jclass) env->NewGlobalRef(audioHandleClass); + gAudioHandleCstor = env->GetMethodID(audioHandleClass, "<init>", "(I)V"); + gAudioHandleFields.mId = env->GetFieldID(audioHandleClass, "mId", "I"); + + jclass audioPortClass = env->FindClass("android/media/AudioPort"); + gAudioPortClass = (jclass) env->NewGlobalRef(audioPortClass); + gAudioPortCstor = env->GetMethodID(audioPortClass, "<init>", + "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V"); + gAudioPortFields.mHandle = env->GetFieldID(audioPortClass, "mHandle", + "Landroid/media/AudioHandle;"); + gAudioPortFields.mRole = env->GetFieldID(audioPortClass, "mRole", "I"); + gAudioPortFields.mGains = env->GetFieldID(audioPortClass, "mGains", + "[Landroid/media/AudioGain;"); + gAudioPortFields.mActiveConfig = env->GetFieldID(audioPortClass, "mActiveConfig", + "Landroid/media/AudioPortConfig;"); + + jclass audioPortConfigClass = env->FindClass("android/media/AudioPortConfig"); + gAudioPortConfigClass = (jclass) env->NewGlobalRef(audioPortConfigClass); + gAudioPortConfigCstor = env->GetMethodID(audioPortConfigClass, "<init>", + "(Landroid/media/AudioPort;IIILandroid/media/AudioGainConfig;)V"); + gAudioPortConfigFields.mPort = env->GetFieldID(audioPortConfigClass, "mPort", + "Landroid/media/AudioPort;"); + gAudioPortConfigFields.mSamplingRate = env->GetFieldID(audioPortConfigClass, + "mSamplingRate", "I"); + gAudioPortConfigFields.mChannelMask = env->GetFieldID(audioPortConfigClass, + "mChannelMask", "I"); + gAudioPortConfigFields.mFormat = env->GetFieldID(audioPortConfigClass, "mFormat", "I"); + gAudioPortConfigFields.mGain = env->GetFieldID(audioPortConfigClass, "mGain", + "Landroid/media/AudioGainConfig;"); + gAudioPortConfigFields.mConfigMask = env->GetFieldID(audioPortConfigClass, "mConfigMask", "I"); + + jclass audioDevicePortConfigClass = env->FindClass("android/media/AudioDevicePortConfig"); + gAudioDevicePortConfigClass = (jclass) env->NewGlobalRef(audioDevicePortConfigClass); + gAudioDevicePortConfigCstor = env->GetMethodID(audioDevicePortConfigClass, "<init>", + "(Landroid/media/AudioDevicePort;IIILandroid/media/AudioGainConfig;)V"); + + jclass audioMixPortConfigClass = env->FindClass("android/media/AudioMixPortConfig"); + gAudioMixPortConfigClass = (jclass) env->NewGlobalRef(audioMixPortConfigClass); + gAudioMixPortConfigCstor = env->GetMethodID(audioMixPortConfigClass, "<init>", + "(Landroid/media/AudioMixPort;IIILandroid/media/AudioGainConfig;)V"); + + jclass audioDevicePortClass = env->FindClass("android/media/AudioDevicePort"); + gAudioDevicePortClass = (jclass) env->NewGlobalRef(audioDevicePortClass); + gAudioDevicePortCstor = env->GetMethodID(audioDevicePortClass, "<init>", + "(Landroid/media/AudioHandle;[I[I[I[Landroid/media/AudioGain;ILjava/lang/String;)V"); + + jclass audioMixPortClass = env->FindClass("android/media/AudioMixPort"); + gAudioMixPortClass = (jclass) env->NewGlobalRef(audioMixPortClass); + gAudioMixPortCstor = env->GetMethodID(audioMixPortClass, "<init>", + "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V"); + + jclass audioGainClass = env->FindClass("android/media/AudioGain"); + gAudioGainClass = (jclass) env->NewGlobalRef(audioGainClass); + gAudioGainCstor = env->GetMethodID(audioGainClass, "<init>", "(IIIIIIIII)V"); + + jclass audioGainConfigClass = env->FindClass("android/media/AudioGainConfig"); + gAudioGainConfigClass = (jclass) env->NewGlobalRef(audioGainConfigClass); + gAudioGainConfigCstor = env->GetMethodID(audioGainConfigClass, "<init>", + "(ILandroid/media/AudioGain;II[II)V"); + gAudioGainConfigFields.mIndex = env->GetFieldID(gAudioGainConfigClass, "mIndex", "I"); + gAudioGainConfigFields.mMode = env->GetFieldID(audioGainConfigClass, "mMode", "I"); + gAudioGainConfigFields.mChannelMask = env->GetFieldID(audioGainConfigClass, "mChannelMask", + "I"); + gAudioGainConfigFields.mValues = env->GetFieldID(audioGainConfigClass, "mValues", "[I"); + gAudioGainConfigFields.mRampDurationMs = env->GetFieldID(audioGainConfigClass, + "mRampDurationMs", "I"); + + jclass audioPatchClass = env->FindClass("android/media/AudioPatch"); + gAudioPatchClass = (jclass) env->NewGlobalRef(audioPatchClass); + gAudioPatchCstor = env->GetMethodID(audioPatchClass, "<init>", +"(Landroid/media/AudioHandle;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)V"); + gAudioPatchFields.mHandle = env->GetFieldID(audioPatchClass, "mHandle", + "Landroid/media/AudioHandle;"); + + jclass eventHandlerClass = env->FindClass(kEventHandlerClassPathName); + gPostEventFromNative = env->GetStaticMethodID(eventHandlerClass, "postEventFromNative", + "(Ljava/lang/Object;IIILjava/lang/Object;)V"); + + AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback); - return AndroidRuntime::registerNativeMethods(env, + int status = AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); + + if (status == 0) { + status = AndroidRuntime::registerNativeMethods(env, + kEventHandlerClassPathName, gEventHandlerMethods, NELEM(gEventHandlerMethods)); + } + return status; } diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index c5dd06f..ecdfeb2 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -91,53 +91,9 @@ static struct { } gRectClassInfo; // ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_flushCaches(JNIEnv* env, jobject clazz, - jint mode) { - if (Caches::hasInstance()) { - Caches::getInstance().flush(static_cast<Caches::FlushMode>(mode)); - } -} - -static jboolean android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - return Caches::getInstance().init() ? JNI_TRUE : JNI_FALSE; - } - return JNI_FALSE; -} - -static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) { - if (Caches::hasInstance()) { - Caches::getInstance().terminate(); - } -} - -// ---------------------------------------------------------------------------- -// Caching -// ---------------------------------------------------------------------------- - -static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz, - jobject graphicBuffer, jlongArray atlasMapArray, jint count) { - - sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); - jlong* jAtlasMap = env->GetLongArrayElements(atlasMapArray, NULL); - Caches::getInstance().assetAtlas.init(buffer, jAtlasMap, count); - env->ReleaseLongArrayElements(atlasMapArray, jAtlasMap, 0); -} - -// ---------------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------------- -static jlong android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) { - RENDERER_LOGD("Create OpenGLRenderer"); - OpenGLRenderer* renderer = new OpenGLRenderer(); - renderer->initProperties(); - return reinterpret_cast<jlong>(renderer); -} - static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject clazz, jlong rendererPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); @@ -174,10 +130,6 @@ static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject clazz, renderer->finish(); } -static jint android_view_GLES20Canvas_getStencilSize(JNIEnv* env, jobject clazz) { - return Stencil::getStencilSize(); -} - static void android_view_GLES20Canvas_setProperty(JNIEnv* env, jobject clazz, jstring name, jstring value) { if (!Caches::hasInstance()) { @@ -373,7 +325,7 @@ static void android_view_GLES20Canvas_setMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->setMatrix(matrix); + renderer->setMatrix(matrix ? *matrix : SkMatrix::I()); } static void android_view_GLES20Canvas_getMatrix(JNIEnv* env, jobject clazz, @@ -387,7 +339,7 @@ static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong matrixPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); - renderer->concatMatrix(matrix); + renderer->concatMatrix(*matrix); } // ---------------------------------------------------------------------------- @@ -430,7 +382,7 @@ static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject claz OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr); SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawBitmap(bitmap, matrix, paint); + renderer->drawBitmap(bitmap, *matrix, paint); } static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, @@ -577,15 +529,6 @@ static void android_view_GLES20Canvas_drawRegionAsRects(JNIEnv* env, jobject cla } } -static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject clazz, - jlong rendererPtr, jfloatArray rects, jint count, jlong paintPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - jfloat* storage = env->GetFloatArrayElements(rects, NULL); - SkPaint* paint = reinterpret_cast<SkPaint*>(paintPtr); - renderer->drawRects(storage, count, paint); - env->ReleaseFloatArrayElements(rects, storage, 0); -} - static void android_view_GLES20Canvas_drawPoints(JNIEnv* env, jobject clazz, jlong rendererPtr, jfloatArray points, jint offset, jint count, jlong paintPtr) { OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); @@ -924,39 +867,6 @@ static void android_view_GLES20Canvas_drawLayer(JNIEnv* env, jobject clazz, renderer->drawLayer(layer, x, y); } -static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr, jlong bitmapPtr) { - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr); - return LayerRenderer::copyLayer(layer, bitmap); -} - -static void android_view_GLES20Canvas_pushLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->pushLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_cancelLayerUpdate(JNIEnv* env, jobject clazz, - jlong rendererPtr, jlong layerPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - renderer->cancelLayerUpdate(layer); -} - -static void android_view_GLES20Canvas_clearLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->clearLayerUpdates(); -} - -static void android_view_GLES20Canvas_flushLayerUpdates(JNIEnv* env, jobject clazz, - jlong rendererPtr) { - OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr); - renderer->flushLayerUpdates(); -} - #endif // USE_OPENGL_RENDERER // ---------------------------------------------------------------------------- @@ -1001,14 +911,7 @@ static JNINativeMethod gMethods[] = { { "nIsAvailable", "()Z", (void*) android_view_GLES20Canvas_isAvailable }, #ifdef USE_OPENGL_RENDERER - { "nFlushCaches", "(I)V", (void*) android_view_GLES20Canvas_flushCaches }, - { "nInitCaches", "()Z", (void*) android_view_GLES20Canvas_initCaches }, - { "nTerminateCaches", "()V", (void*) android_view_GLES20Canvas_terminateCaches }, - { "nInitAtlas", "(Landroid/view/GraphicBuffer;[JI)V", - (void*) android_view_GLES20Canvas_initAtlas }, - - { "nCreateRenderer", "()J", (void*) android_view_GLES20Canvas_createRenderer }, { "nDestroyRenderer", "(J)V", (void*) android_view_GLES20Canvas_destroyRenderer }, { "nSetViewport", "(JII)V", (void*) android_view_GLES20Canvas_setViewport }, { "nPrepare", "(JZ)I", (void*) android_view_GLES20Canvas_prepare }, @@ -1017,9 +920,6 @@ static JNINativeMethod gMethods[] = { { "nSetProperty", "(Ljava/lang/String;Ljava/lang/String;)V", (void*) android_view_GLES20Canvas_setProperty }, - - { "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize }, - { "nCallDrawGLFunction", "(JJ)I", (void*) android_view_GLES20Canvas_callDrawGLFunction }, { "nSave", "(JI)I", (void*) android_view_GLES20Canvas_save }, @@ -1059,7 +959,6 @@ static JNINativeMethod gMethods[] = { { "nDrawColor", "(JII)V", (void*) android_view_GLES20Canvas_drawColor }, { "nDrawRect", "(JFFFFJ)V", (void*) android_view_GLES20Canvas_drawRect }, { "nDrawRects", "(JJJ)V", (void*) android_view_GLES20Canvas_drawRegionAsRects }, - { "nDrawRects", "(J[FIJ)V", (void*) android_view_GLES20Canvas_drawRects }, { "nDrawRoundRect", "(JFFFFFFJ)V", (void*) android_view_GLES20Canvas_drawRoundRect }, { "nDrawCircle", "(JFFFJ)V", (void*) android_view_GLES20Canvas_drawCircle }, { "nDrawCircle", "(JJJJJ)V", (void*) android_view_GLES20Canvas_drawCircleProps }, @@ -1099,11 +998,6 @@ static JNINativeMethod gMethods[] = { { "nCreateDisplayListRenderer", "()J", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, { "nDrawLayer", "(JJFF)V", (void*) android_view_GLES20Canvas_drawLayer }, - { "nCopyLayer", "(JJ)Z", (void*) android_view_GLES20Canvas_copyLayer }, - { "nClearLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_clearLayerUpdates }, - { "nFlushLayerUpdates", "(J)V", (void*) android_view_GLES20Canvas_flushLayerUpdates }, - { "nPushLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_pushLayerUpdate }, - { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_GLES20Canvas_cancelLayerUpdate }, { "nGetMaximumTextureWidth", "()I", (void*) android_view_GLES20Canvas_getMaxTextureWidth }, { "nGetMaximumTextureHeight", "()I", (void*) android_view_GLES20Canvas_getMaxTextureHeight }, diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp deleted file mode 100644 index d0269a3..0000000 --- a/core/jni/android_view_GLRenderer.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#define LOG_TAG "GLRenderer" - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <android_runtime/AndroidRuntime.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <EGL/egl_cache.h> - -#include <utils/Timers.h> - -#include <private/hwui/DrawGlInfo.h> - -#include <Caches.h> -#include <Extensions.h> -#include <LayerRenderer.h> -#include <RenderNode.h> - -#ifdef USE_OPENGL_RENDERER - EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); -#endif - -namespace android { - -/** - * Note: OpenGLRenderer JNI layer is generated and compiled only on supported - * devices. This means all the logic must be compiled only when the - * preprocessor variable USE_OPENGL_RENDERER is defined. - */ -#ifdef USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Defines -// ---------------------------------------------------------------------------- - -// Debug -#define DEBUG_RENDERER 0 - -// Debug -#if DEBUG_RENDERER - #define RENDERER_LOGD(...) ALOGD(__VA_ARGS__) -#else - #define RENDERER_LOGD(...) -#endif - -// ---------------------------------------------------------------------------- -// Surface and display management -// ---------------------------------------------------------------------------- - -static jboolean android_view_GLRenderer_preserveBackBuffer(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - eglGetError(); - eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not enable buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS; -} - -static jboolean android_view_GLRenderer_isBackBufferPreserved(JNIEnv* env, jobject clazz) { - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - EGLint value; - - eglGetError(); - eglQuerySurface(display, surface, EGL_SWAP_BEHAVIOR, &value); - - EGLint error = eglGetError(); - if (error != EGL_SUCCESS) { - RENDERER_LOGD("Could not query buffer preserved swap behavior (%x)", error); - } - - return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED; -} - -// ---------------------------------------------------------------------------- -// Tracing and debugging -// ---------------------------------------------------------------------------- - -static bool android_view_GLRenderer_loadProperties(JNIEnv* env, jobject clazz) { - if (uirenderer::Caches::hasInstance()) { - return uirenderer::Caches::getInstance().initProperties(); - } - return false; -} - -static void android_view_GLRenderer_beginFrame(JNIEnv* env, jobject clazz, - jintArray size) { - - EGLDisplay display = eglGetCurrentDisplay(); - EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); - - if (size) { - EGLint value; - jint* storage = env->GetIntArrayElements(size, NULL); - - eglQuerySurface(display, surface, EGL_WIDTH, &value); - storage[0] = value; - - eglQuerySurface(display, surface, EGL_HEIGHT, &value); - storage[1] = value; - - env->ReleaseIntArrayElements(size, storage, 0); - } - - eglBeginFrame(display, surface); -} - -static jlong android_view_GLRenderer_getSystemTime(JNIEnv* env, jobject clazz) { - if (uirenderer::Extensions::getInstance().hasNvSystemTime()) { - return eglGetSystemTimeNV(); - } - return systemTime(SYSTEM_TIME_MONOTONIC); -} - -static void android_view_GLRenderer_destroyLayer(JNIEnv* env, jobject clazz, - jlong layerPtr) { - using namespace android::uirenderer; - Layer* layer = reinterpret_cast<Layer*>(layerPtr); - LayerRenderer::destroyLayer(layer); -} - -static void android_view_GLRenderer_prepareTree(JNIEnv* env, jobject clazz, - jlong renderNodePtr) { - using namespace android::uirenderer; - RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); - TreeInfo ignoredInfo; - renderNode->prepareTree(ignoredInfo); -} - -static void android_view_GLRenderer_invokeFunctor(JNIEnv* env, jobject clazz, - jlong functorPtr, jboolean hasContext) { - using namespace android::uirenderer; - Functor* functor = reinterpret_cast<Functor*>(functorPtr); - DrawGlInfo::Mode mode = hasContext ? DrawGlInfo::kModeProcess : DrawGlInfo::kModeProcessNoContext; - (*functor)(mode, NULL); -} - -#endif // USE_OPENGL_RENDERER - -// ---------------------------------------------------------------------------- -// Shaders -// ---------------------------------------------------------------------------- - -static void android_view_GLRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, - jstring diskCachePath) { - - const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); - egl_cache_t::get()->setCacheFilename(cacheArray); - env->ReleaseStringUTFChars(diskCachePath, cacheArray); -} - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -const char* const kClassPathName = "android/view/GLRenderer"; - -static JNINativeMethod gMethods[] = { -#ifdef USE_OPENGL_RENDERER - { "isBackBufferPreserved", "()Z", (void*) android_view_GLRenderer_isBackBufferPreserved }, - { "preserveBackBuffer", "()Z", (void*) android_view_GLRenderer_preserveBackBuffer }, - { "loadProperties", "()Z", (void*) android_view_GLRenderer_loadProperties }, - - { "beginFrame", "([I)V", (void*) android_view_GLRenderer_beginFrame }, - - { "getSystemTime", "()J", (void*) android_view_GLRenderer_getSystemTime }, - { "nDestroyLayer", "(J)V", (void*) android_view_GLRenderer_destroyLayer }, - { "nPrepareTree", "(J)V", (void*) android_view_GLRenderer_prepareTree }, - { "nInvokeFunctor", "(JZ)V", (void*) android_view_GLRenderer_invokeFunctor }, -#endif - - { "setupShadersDiskCache", "(Ljava/lang/String;)V", - (void*) android_view_GLRenderer_setupShadersDiskCache }, -}; - -int register_android_view_GLRenderer(JNIEnv* env) { - return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -}; diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index 8a426ac..0210bd9 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -21,6 +21,7 @@ #include "android_os_Parcel.h" #include "android_view_GraphicBuffer.h" +#include "android/graphics/GraphicsJNI.h" #include <android_runtime/AndroidRuntime.h> @@ -75,7 +76,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; #define GET_INT(object, field) \ @@ -197,12 +198,11 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat()); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -218,8 +218,7 @@ static jboolean android_view_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobj GraphicBufferWrapper* wrapper = reinterpret_cast<GraphicBufferWrapper*>(wrapperHandle); - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (wrapper) { status_t status = wrapper->buffer->unlock(); @@ -319,7 +318,7 @@ int register_android_view_GraphicBuffer(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp index b2f17de..879836d 100644 --- a/core/jni/android_view_HardwareLayer.cpp +++ b/core/jni/android_view_HardwareLayer.cpp @@ -43,41 +43,12 @@ using namespace uirenderer; #ifdef USE_OPENGL_RENDERER -static jlong android_view_HardwareLayer_createTextureLayer(JNIEnv* env, jobject clazz) { - Layer* layer = LayerRenderer::createTextureLayer(); - if (!layer) return 0; - - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) ); -} - -static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject clazz, - jint width, jint height) { - Layer* layer = LayerRenderer::createRenderLayer(width, height); - if (!layer) return 0; - - OpenGLRenderer* renderer = new LayerRenderer(layer); - renderer->initProperties(); - return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer, renderer) ); -} - static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); layer->backingLayer()->clearTexture(); } -static jlong android_view_HardwareLayer_detachBackingLayer(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - return reinterpret_cast<jlong>( layer->detachBackingLayer() ); -} - -static void android_view_HardwareLayer_destroyLayerUpdater(JNIEnv* env, jobject clazz, - jlong layerUpdaterPtr) { - DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); - delete layer; -} - static jboolean android_view_HardwareLayer_prepare(JNIEnv* env, jobject clazz, jlong layerUpdaterPtr, jint width, jint height, jboolean isOpaque) { DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr); @@ -154,11 +125,7 @@ const char* const kClassPathName = "android/view/HardwareLayer"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nCreateTextureLayer", "()J", (void*) android_view_HardwareLayer_createTextureLayer }, - { "nCreateRenderLayer", "(II)J", (void*) android_view_HardwareLayer_createRenderLayer }, { "nOnTextureDestroyed", "(J)V", (void*) android_view_HardwareLayer_onTextureDestroyed }, - { "nDetachBackingLayer", "(J)J", (void*) android_view_HardwareLayer_detachBackingLayer }, - { "nDestroyLayerUpdater", "(J)V", (void*) android_view_HardwareLayer_destroyLayerUpdater }, { "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare }, { "nSetLayerPaint", "(JJ)V", (void*) android_view_HardwareLayer_setLayerPaint }, diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 6ae02e0..a590dbf 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -211,8 +211,9 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj, outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, env->GetFloatField(pointerCoordsObj, gPointerCoordsClassInfo.orientation)); - uint64_t bits = env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits); - if (bits) { + BitSet64 bits = + BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits)); + if (!bits.isEmpty()) { jfloatArray valuesArray = jfloatArray(env->GetObjectField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisValues)); if (valuesArray) { @@ -221,11 +222,9 @@ static void pointerCoordsToNative(JNIEnv* env, jobject pointerCoordsObj, uint32_t index = 0; do { - uint32_t axis = __builtin_ctzll(bits); - uint64_t axisBit = 1LL << axis; - bits &= ~axisBit; + uint32_t axis = bits.clearFirstMarkedBit(); outRawPointerCoords->setAxisValue(axis, values[index++]); - } while (bits); + } while (!bits.isEmpty()); env->ReleasePrimitiveArrayCritical(valuesArray, values, JNI_ABORT); env->DeleteLocalRef(valuesArray); @@ -275,21 +274,19 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer env->SetFloatField(outPointerCoordsObj, gPointerCoordsClassInfo.orientation, rawPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); - const uint64_t unpackedAxisBits = 0 - | (1LL << AMOTION_EVENT_AXIS_X) - | (1LL << AMOTION_EVENT_AXIS_Y) - | (1LL << AMOTION_EVENT_AXIS_PRESSURE) - | (1LL << AMOTION_EVENT_AXIS_SIZE) - | (1LL << AMOTION_EVENT_AXIS_TOUCH_MAJOR) - | (1LL << AMOTION_EVENT_AXIS_TOUCH_MINOR) - | (1LL << AMOTION_EVENT_AXIS_TOOL_MAJOR) - | (1LL << AMOTION_EVENT_AXIS_TOOL_MINOR) - | (1LL << AMOTION_EVENT_AXIS_ORIENTATION); - uint64_t outBits = 0; - uint64_t remainingBits = rawPointerCoords->bits & ~unpackedAxisBits; - if (remainingBits) { - uint32_t packedAxesCount = __builtin_popcountll(remainingBits); + BitSet64 bits = BitSet64(rawPointerCoords->bits); + bits.clearBit(AMOTION_EVENT_AXIS_X); + bits.clearBit(AMOTION_EVENT_AXIS_Y); + bits.clearBit(AMOTION_EVENT_AXIS_PRESSURE); + bits.clearBit(AMOTION_EVENT_AXIS_SIZE); + bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MAJOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MINOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR); + bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR); + bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION); + if (!bits.isEmpty()) { + uint32_t packedAxesCount = bits.count(); jfloatArray outValuesArray = obtainPackedAxisValuesArray(env, packedAxesCount, outPointerCoordsObj); if (!outValuesArray) { @@ -302,12 +299,10 @@ static void pointerCoordsFromNative(JNIEnv* env, const PointerCoords* rawPointer const float* values = rawPointerCoords->values; uint32_t index = 0; do { - uint32_t axis = __builtin_ctzll(remainingBits); - uint64_t axisBit = 1LL << axis; - remainingBits &= ~axisBit; - outBits |= axisBit; + uint32_t axis = bits.clearFirstMarkedBit(); + outBits |= BitSet64::valueForBit(axis); outValues[index++] = rawPointerCoords->getAxisValue(axis); - } while (remainingBits); + } while (!bits.isEmpty()); env->ReleasePrimitiveArrayCritical(outValuesArray, outValues, 0); env->DeleteLocalRef(outValuesArray); diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 867c1b1..26022e0 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -48,6 +48,12 @@ static void android_view_RenderNode_output(JNIEnv* env, renderNode->output(); } +static jint android_view_RenderNode_getDebugSize(JNIEnv* env, + jobject clazz, jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); + return renderNode->getDebugSize(); +} + static jlong android_view_RenderNode_create(JNIEnv* env, jobject clazz, jstring name) { RenderNode* renderNode = new RenderNode(); renderNode->incStrong(0); @@ -505,6 +511,7 @@ static JNINativeMethod gMethods[] = { { "nDestroyRenderNode", "(J)V", (void*) android_view_RenderNode_destroyRenderNode }, { "nSetDisplayListData", "(JJ)V", (void*) android_view_RenderNode_setDisplayListData }, { "nOutput", "(J)V", (void*) android_view_RenderNode_output }, + { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize }, { "nSetCaching", "(JZ)V", (void*) android_view_RenderNode_setCaching }, { "nSetStaticMatrix", "(JJ)V", (void*) android_view_RenderNode_setStaticMatrix }, diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp index e19ce36..d689864 100644 --- a/core/jni/android_view_RenderNodeAnimator.cpp +++ b/core/jni/android_view_RenderNodeAnimator.cpp @@ -116,6 +116,11 @@ static jlong createCanvasPropertyPaintAnimator(JNIEnv* env, jobject clazz, return reinterpret_cast<jlong>( animator ); } +static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) { + BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); + animator->setStartValue(startValue); +} + static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong duration) { LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative"); BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr); @@ -157,6 +162,7 @@ static JNINativeMethod gMethods[] = { { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator }, { "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator }, { "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator }, + { "nSetStartValue", "(JF)V", (void*) setStartValue }, { "nSetDuration", "(JJ)V", (void*) setDuration }, { "nGetDuration", "(J)J", (void*) getDuration }, { "nSetStartDelay", "(JJ)V", (void*) setStartDelay }, diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 6c9d060..11f87cc 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -70,7 +70,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; // ---------------------------------------------------------------------------- @@ -232,10 +232,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, bitmap.setPixels(NULL); } - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, + reinterpret_cast<jlong>(&bitmap)); if (dirtyRectPtr) { + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj); nativeCanvas->clipRect( SkRect::Make(reinterpret_cast<const SkIRect&>(dirtyRect)) ); } @@ -262,8 +263,7 @@ static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz, } // detach the canvas from the surface - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - env->CallVoidMethod(canvasObj, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + env->CallVoidMethod(canvasObj, gCanvasClassInfo.setNativeBitmap, (jlong)0); // unlock surface status_t err = surface->unlockAndPost(); @@ -375,7 +375,7 @@ int register_android_view_Surface(JNIEnv* env) clazz = env->FindClass("android/graphics/Canvas"); gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I"); - gCanvasClassInfo.safeCanvasSwap = env->GetMethodID(clazz, "safeCanvasSwap", "(JZ)V"); + gCanvasClassInfo.setNativeBitmap = env->GetMethodID(clazz, "setNativeBitmap", "(J)V"); clazz = env->FindClass("android/graphics/Rect"); gRectClassInfo.left = env->GetFieldID(clazz, "left", "I"); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5a935a9..4594cc3 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -61,6 +61,13 @@ static struct { jfieldID secure; } gPhysicalDisplayInfoClassInfo; +static struct { + jfieldID bottom; + jfieldID left; + jfieldID right; + jfieldID top; +} gRectClassInfo; + // Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref. void DeleteScreenshot(void* addr, void* context) { SkASSERT(addr == ((ScreenshotClient*) context)->getPixels()); @@ -104,25 +111,32 @@ static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) { ctrl->decStrong((void *)nativeCreate); } -static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj, - jint width, jint height, jint minLayer, jint maxLayer, bool allLayers, - bool useIdentityTransform) { +static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, + jobject displayTokenObj, jobject sourceCropObj, jint width, jint height, + jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); if (displayToken == NULL) { return NULL; } + int left = env->GetIntField(sourceCropObj, gRectClassInfo.left); + int top = env->GetIntField(sourceCropObj, gRectClassInfo.top); + int right = env->GetIntField(sourceCropObj, gRectClassInfo.right); + int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom); + Rect sourceCrop(left, top, right, bottom); + ScreenshotClient* screenshot = new ScreenshotClient(); status_t res; if (width > 0 && height > 0) { if (allLayers) { - res = screenshot->update(displayToken, width, height, useIdentityTransform); - } else { - res = screenshot->update(displayToken, width, height, minLayer, maxLayer, + res = screenshot->update(displayToken, sourceCrop, width, height, useIdentityTransform); + } else { + res = screenshot->update(displayToken, sourceCrop, width, height, + minLayer, maxLayer, useIdentityTransform); } } else { - res = screenshot->update(displayToken, useIdentityTransform); + res = screenshot->update(displayToken, sourceCrop, useIdentityTransform); } if (res != NO_ERROR) { delete screenshot; @@ -174,20 +188,25 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); } -static void nativeScreenshot(JNIEnv* env, jclass clazz, - jobject displayTokenObj, jobject surfaceObj, - jint width, jint height, jint minLayer, jint maxLayer, bool allLayers, - bool useIdentityTransform) { +static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj, + jobject surfaceObj, jobject sourceCropObj, jint width, jint height, + jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); if (displayToken != NULL) { sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj); if (consumer != NULL) { + int left = env->GetIntField(sourceCropObj, gRectClassInfo.left); + int top = env->GetIntField(sourceCropObj, gRectClassInfo.top); + int right = env->GetIntField(sourceCropObj, gRectClassInfo.right); + int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom); + Rect sourceCrop(left, top, right, bottom); + if (allLayers) { minLayer = 0; maxLayer = -1; } - ScreenshotClient::capture( - displayToken, consumer->getIGraphicBufferProducer(), + ScreenshotClient::capture(displayToken, + consumer->getIGraphicBufferProducer(), sourceCrop, width, height, uint32_t(minLayer), uint32_t(maxLayer), useIdentityTransform); } @@ -563,9 +582,9 @@ static JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeRelease }, {"nativeDestroy", "(J)V", (void*)nativeDestroy }, - {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZZ)Landroid/graphics/Bitmap;", + {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZ)Landroid/graphics/Bitmap;", (void*)nativeScreenshotBitmap }, - {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZZ)V", + {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V", (void*)nativeScreenshot }, {"nativeOpenTransaction", "()V", (void*)nativeOpenTransaction }, @@ -640,6 +659,12 @@ int register_android_view_SurfaceControl(JNIEnv* env) gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F"); gPhysicalDisplayInfoClassInfo.secure = env->GetFieldID(clazz, "secure", "Z"); + jclass rectClazz = env->FindClass("android/graphics/Rect"); + gRectClassInfo.bottom = env->GetFieldID(rectClazz, "bottom", "I"); + gRectClassInfo.left = env->GetFieldID(rectClazz, "left", "I"); + gRectClassInfo.right = env->GetFieldID(rectClazz, "right", "I"); + gRectClassInfo.top = env->GetFieldID(rectClazz, "top", "I"); + jclass frameStatsClazz = env->FindClass("android/view/FrameStats"); jfieldID undefined_time_nano_field = env->GetStaticFieldID(frameStatsClazz, "UNDEFINED_TIME_NANO", "J"); nsecs_t undefined_time_nano = env->GetStaticLongField(frameStatsClazz, undefined_time_nano_field); diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 9258543..c1ab515 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -29,6 +29,8 @@ #include <SkCanvas.h> #include <SkImage.h> +#include "android/graphics/GraphicsJNI.h" + namespace android { // ---------------------------------------------------------------------------- @@ -45,7 +47,7 @@ static struct { static struct { jfieldID mSurfaceFormat; - jmethodID safeCanvasSwap; + jmethodID setNativeBitmap; } gCanvasClassInfo; static struct { @@ -159,12 +161,11 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, } SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer.format); - - SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(&bitmap)); SkRect clipRect; clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + SkCanvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvas); nativeCanvas->clipRect(clipRect); if (dirtyRect) { @@ -178,8 +179,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, static void android_view_TextureView_unlockCanvasAndPost(JNIEnv* env, jobject, jlong nativeWindow, jobject canvas) { - SkCanvas* nativeCanvas = SkNEW(SkCanvas); - INVOKEV(canvas, gCanvasClassInfo.safeCanvasSwap, (jlong)nativeCanvas, false); + INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); if (nativeWindow) { sp<ANativeWindow> window((ANativeWindow*) nativeWindow); @@ -228,7 +228,7 @@ int register_android_view_TextureView(JNIEnv* env) { FIND_CLASS(clazz, "android/graphics/Canvas"); GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); - GET_METHOD_ID(gCanvasClassInfo.safeCanvasSwap, clazz, "safeCanvasSwap", "(JZ)V"); + GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); FIND_CLASS(clazz, "android/view/TextureView"); GET_FIELD_ID(gTextureViewClassInfo.nativeWindow, clazz, "mNativeWindow", "J"); diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 48fb729..a550649 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -22,6 +22,10 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <EGL/egl_cache.h> + #include <utils/StrongPointer.h> #include <android_runtime/android_view_Surface.h> #include <system/window.h> @@ -238,10 +242,11 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, } static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, - jlong proxyPtr, jlong frameTimeNanos, jint dirtyLeft, jint dirtyTop, - jint dirtyRight, jint dirtyBottom) { + jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density, + jint dirtyLeft, jint dirtyTop, jint dirtyRight, jint dirtyBottom) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - return proxy->syncAndDrawFrame(frameTimeNanos, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); + return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density, + dirtyLeft, dirtyTop, dirtyRight, dirtyBottom); } static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz, @@ -286,11 +291,24 @@ static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject return proxy->copyLayerInto(layer, bitmap); } -static void android_view_ThreadedRenderer_destroyLayer(JNIEnv* env, jobject clazz, +static void android_view_ThreadedRenderer_pushLayerUpdate(JNIEnv* env, jobject clazz, + jlong proxyPtr, jlong layerPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); + proxy->pushLayerUpdate(layer); +} + +static void android_view_ThreadedRenderer_cancelLayerUpdate(JNIEnv* env, jobject clazz, jlong proxyPtr, jlong layerPtr) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); - proxy->destroyLayer(layer); + proxy->cancelLayerUpdate(layer); +} + +static void android_view_ThreadedRenderer_flushCaches(JNIEnv* env, jobject clazz, + jlong proxyPtr, jint flushMode) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->flushCaches(static_cast<Caches::FlushMode>(flushMode)); } static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz, @@ -299,9 +317,34 @@ static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz, proxy->fence(); } +static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobject clazz, + jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->notifyFramePending(); +} + +static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz, + jlong proxyPtr, jobject javaFileDescriptor) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); + proxy->dumpProfileInfo(fd); +} + #endif // ---------------------------------------------------------------------------- +// Shaders +// ---------------------------------------------------------------------------- + +static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz, + jstring diskCachePath) { + + const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL); + egl_cache_t::get()->setCacheFilename(cacheArray); + env->ReleaseStringUTFChars(diskCachePath, cacheArray); +} + +// ---------------------------------------------------------------------------- // JNI Glue // ---------------------------------------------------------------------------- @@ -320,16 +363,22 @@ static JNINativeMethod gMethods[] = { { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface }, { "nSetup", "(JIIFFFF)V", (void*) android_view_ThreadedRenderer_setup }, { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, - { "nSyncAndDrawFrame", "(JJIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, + { "nSyncAndDrawFrame", "(JJJFIIII)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface }, { "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor }, { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext }, { "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer }, { "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer }, { "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto }, - { "nDestroyLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_destroyLayer }, + { "nPushLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_pushLayerUpdate }, + { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_cancelLayerUpdate }, + { "nFlushCaches", "(JI)V", (void*) android_view_ThreadedRenderer_flushCaches }, { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence }, + { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, + { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, #endif + { "setupShadersDiskCache", "(Ljava/lang/String;)V", + (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, }; int register_android_view_ThreadedRenderer(JNIEnv* env) { diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 230658f..e55e4ea 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -46,6 +46,9 @@ #define LIB_SUFFIX ".so" #define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1) +#define RS_BITCODE_SUFFIX ".bc" +#define RS_BITCODE_SUFFIX_LEN (sizeof(RS_BITCODE_SUFFIX) -1) + #define GDBSERVER "gdbserver" #define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) @@ -486,6 +489,42 @@ com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, j return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); } +enum bitcode_scan_result_t { + APK_SCAN_ERROR = -1, + NO_BITCODE_PRESENT = 0, + BITCODE_PRESENT = 1, +}; + +static jint +com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz, + jlong apkHandle) { + ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); + void* cookie = NULL; + if (!zipFile->startIteration(&cookie)) { + return APK_SCAN_ERROR; + } + + char fileName[PATH_MAX]; + ZipEntryRO next = NULL; + while ((next = zipFile->nextEntry(cookie)) != NULL) { + if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) { + continue; + } + + const size_t fileNameLen = strlen(fileName); + const char* lastSlash = strrchr(fileName, '/'); + const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1; + if (!strncmp(fileName + fileNameLen - RS_BITCODE_SUFFIX_LEN, RS_BITCODE_SUFFIX, + RS_BITCODE_SUFFIX_LEN) && isFilenameSafe(baseName)) { + zipFile->endIteration(cookie); + return BITCODE_PRESENT; + } + } + + zipFile->endIteration(cookie); + return NO_BITCODE_PRESENT; +} + static jlong com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath) { @@ -517,6 +556,8 @@ static JNINativeMethod gMethods[] = { {"nativeFindSupportedAbi", "(J[Ljava/lang/String;)I", (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, + {"hasRenderscriptBitcode", "(J)I", + (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, }; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1874fd8..fe703b2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -258,6 +258,12 @@ <protected-broadcast android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast + android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" /> + <!-- Defined in RestrictionsManager --> + <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> @@ -1014,6 +1020,13 @@ android:description="@string/permdesc_sim_communication" android:protectionLevel="dangerous" /> + <!-- Allows TvInputService to access underlying TV input hardware such as + built-in tuners and HDMI-in's. + @hide This should only be used by OEM's TvInputService's. + --> + <permission android:name="android.permission.TV_INPUT_HARDWARE" + android:protectionLevel="signatureOrSystem" /> + <!-- =========================================== --> <!-- Permissions associated with audio capture --> <!-- =========================================== --> @@ -2063,7 +2076,7 @@ android:description="@string/permdesc_bindRemoteDisplay" android:protectionLevel="signature" /> - <!-- Must be required by a {@link android.tv.TvInputService} + <!-- Must be required by a {@link android.media.tv.TvInputService} to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_TV_INPUT" android:label="@string/permlab_bindTvInput" diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml index e87352f..117774a 100644 --- a/core/res/res/anim/input_method_exit.xml +++ b/core/res/res/anim/input_method_exit.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/res/anim/fade_out.xml -** -** Copyright 2007, The Android Open Source Project +/* Copyright 2007, 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. diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml index cb47b3c..7e212be 100644 --- a/core/res/res/anim/lock_screen_behind_enter.xml +++ b/core/res/res/anim/lock_screen_behind_enter.xml @@ -18,11 +18,17 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:background="#ff000000" android:shareInterpolator="false"> + android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@interpolator/decelerate_quint" - android:startOffset="@android:integer/config_shortAnimTime" - android:duration="@android:integer/config_shortAnimTime"/> + android:interpolator="@interpolator/linear_out_slow_in" + android:duration="@integer/config_shortAnimTime"/> + <scale + android:fromXScale="0.95" android:toXScale="1.0" + android:fromYScale="0.95" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" android:fillBefore="true" + android:interpolator="@interpolator/linear_out_slow_in" + android:duration="@integer/config_shortAnimTime" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/slide_in_micro.xml b/core/res/res/anim/slide_in_micro.xml new file mode 100644 index 0000000..6320e80 --- /dev/null +++ b/core/res/res/anim/slide_in_micro.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/slide_in_micro.xml +** +** Copyright 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"> + <translate android:fromXDelta="100%p" android:toXDelta="0" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> +</set> diff --git a/core/res/res/anim/slide_out_micro.xml b/core/res/res/anim/slide_out_micro.xml new file mode 100644 index 0000000..4cb6df0 --- /dev/null +++ b/core/res/res/anim/slide_out_micro.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/res/anim/slide_out_micro.xml +** +** Copyright 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"> + <translate android:fromXDelta="0" android:toXDelta="100%p" + android:duration="@android:integer/config_mediumAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_mediumAnimTime" /> +</set> diff --git a/core/res/res/anim/voice_activity_close_enter.xml b/core/res/res/anim/voice_activity_close_enter.xml new file mode 100644 index 0000000..4f3d3d5 --- /dev/null +++ b/core/res/res/anim/voice_activity_close_enter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/voice_activity_close_exit.xml b/core/res/res/anim/voice_activity_close_exit.xml new file mode 100644 index 0000000..023b012 --- /dev/null +++ b/core/res/res/anim/voice_activity_close_exit.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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" + android:shareInterpolator="false"> + <translate android:fromYDelta="0" android:toYDelta="-20%" + android:interpolator="@interpolator/accelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/voice_activity_open_enter.xml index c29fd1a..ce7a4f9 100644 --- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml +++ b/core/res/res/anim/voice_activity_open_enter.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* +** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:detachWallpaper="true" android:shareInterpolator="false"> - <alpha - android:fromAlpha="0.0" android:toAlpha="1.0" - android:fillEnabled="true" android:fillBefore="true" - android:interpolator="@interpolator/decelerate_quad" - android:startOffset="@android:integer/config_shortAnimTime" - android:duration="@android:integer/config_shortAnimTime"/> + android:shareInterpolator="false"> + <translate android:fromYDelta="-20%" android:toYDelta="0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.5" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_cubic" + android:duration="@android:integer/config_shortAnimTime" /> </set> diff --git a/core/res/res/anim/voice_activity_open_exit.xml b/core/res/res/anim/voice_activity_open_exit.xml new file mode 100644 index 0000000..4f3d3d5 --- /dev/null +++ b/core/res/res/anim/voice_activity_open_exit.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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" + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="1.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/anim/voice_layer_enter.xml b/core/res/res/anim/voice_layer_enter.xml new file mode 100644 index 0000000..ce7a4f9 --- /dev/null +++ b/core/res/res/anim/voice_layer_enter.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2007, 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" + android:shareInterpolator="false"> + <translate android:fromYDelta="-20%" android:toYDelta="0" + android:interpolator="@interpolator/decelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="0.5" android:toAlpha="1.0" + android:interpolator="@interpolator/decelerate_cubic" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/voice_layer_exit.xml b/core/res/res/anim/voice_layer_exit.xml new file mode 100644 index 0000000..023b012 --- /dev/null +++ b/core/res/res/anim/voice_layer_exit.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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" + android:shareInterpolator="false"> + <translate android:fromYDelta="0" android:toYDelta="-20%" + android:interpolator="@interpolator/accelerate_quint" + android:duration="@android:integer/config_shortAnimTime"/> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/accelerate_cubic" + android:duration="@android:integer/config_shortAnimTime"/> +</set> diff --git a/core/res/res/color/btn_default_quantum_dark.xml b/core/res/res/color/btn_default_quantum_dark.xml index f2e772d..ec0f140 100644 --- a/core/res/res/color/btn_default_quantum_dark.xml +++ b/core/res/res/color/btn_default_quantum_dark.xml @@ -15,6 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_700"/> - <item android:color="@color/quantum_grey_700"/> + <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_dark"/> + <item android:color="@color/button_quantum_dark"/> </selector> diff --git a/core/res/res/color/btn_default_quantum_light.xml b/core/res/res/color/btn_default_quantum_light.xml index de1bd2c..9536d24 100644 --- a/core/res/res/color/btn_default_quantum_light.xml +++ b/core/res/res/color/btn_default_quantum_light.xml @@ -15,6 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_300"/> - <item android:color="@color/quantum_grey_300"/> + <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_light"/> + <item android:color="@color/button_quantum_light"/> </selector> diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00000_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 1880a15..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00001_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index aecb4d2..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00002_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index 8401f91..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00003_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index 5832865..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00004_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index 6d14962..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00005_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index aee057c..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00006_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index fb5801e..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00007_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index fdb5271..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00008_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index b8c7397..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00009_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index d0395a8..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00010_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index 59bb437..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00011_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index c053b90..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00012_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index eb30a79..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00013_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 1af0bff..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00014_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index 3b36e7d..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_anim_00015_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index c12d20a..0000000 --- a/core/res/res/drawable-hdpi/btn_check_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_off_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 5bc1d90..0000000 --- a/core/res/res/drawable-hdpi/btn_check_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_on_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_check_on_qntm_alpha.png Binary files differdeleted file mode 100644 index e5de2c1..0000000 --- a/core/res/res/drawable-hdpi/btn_check_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_000.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..3cb4073 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_001.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..8fd1480 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_002.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..d35b579 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_003.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..543c6bc --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_004.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..4fc3c40 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_005.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..c184535 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_006.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..9f9dd43 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_007.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..8c629ce --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_008.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..81134b5 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_009.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..baa5860 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_010.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..d7e28366 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_011.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..6f24795 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_012.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..22f997d --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_013.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..85f4471 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_014.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..ad483c9 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_off_qntm_015.png b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..f24c2fb --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_off_qntm_015.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_000.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..7a9e9bd --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_001.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..af04902 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_002.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..32a6e94 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_003.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..c1b4b37 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_004.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..34d3ade --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_005.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..3d5db53 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_006.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..ea35437 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_007.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..48744f8 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_008.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..f654517 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_009.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..16f959a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_010.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..98c754b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_011.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..5827dc2 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_012.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..9850d74 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_013.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..03ab06b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_014.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..11cdd88 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_check_to_on_qntm_015.png b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..874edbf --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_check_to_on_qntm_015.png diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00000_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 882365b..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00001_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index f6c7094..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00002_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index 0e326c9..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00003_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index 8bf1170..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00004_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index cedb66e..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00005_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 257d7ba..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00006_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index e07b36e..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00007_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index ef94200..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00008_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index ad67004..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00009_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index 50796e2..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00010_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index ba7be9e..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00011_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index bdbfe78..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00012_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index fe89951..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00013_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 840c88f..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00014_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index 621d1d2..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_anim_00015_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index fd8be89..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_off_pressed_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_off_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index 7bef530..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_off_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_off_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_off_qntm_alpha.png Binary files differdeleted file mode 100644 index ae50dd5..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_on_pressed_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_on_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index 0678dbb..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_on_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_on_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_radio_on_qntm_alpha.png Binary files differdeleted file mode 100644 index f332925..0000000 --- a/core/res/res/drawable-hdpi/btn_radio_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_000.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..da88e98 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_001.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..907d92d --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_002.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..9d24dc1 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_003.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..8aa2605 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_004.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..b4cdf02 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_005.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..0724ed7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_006.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..c9bd4e3 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_007.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..5630ec3 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_008.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..4bf666c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_009.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..dffaa07 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_010.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..5f86e18 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_011.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..9b50aef --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_012.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..1cf5e7f --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_013.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..2bb641a --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_014.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..08e7485 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_015.png b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..519b5a3 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_off_qntm_015.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_000.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..0d3e1e7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_001.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..88c4a9e --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_002.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..8fa2e88 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_003.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..53dd9d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_004.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..e235195 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_005.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..1721284 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_006.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..31819fa --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_007.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..5de44b9 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_008.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..aa20f65 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_009.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..c379ba7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_010.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..e23b410 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_011.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..a9543dc --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_012.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..2473b78 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_013.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..b4acc9c --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_014.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..c9cf344 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_015.png b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..a8c390e --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_radio_to_on_qntm_015.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_000.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_001.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..e062f61 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_002.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..7737646 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_003.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..65ff45e --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_004.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..11aaec0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_005.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..9e1b60f --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_006.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..1e45687 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_007.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..1e45687 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_008.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..6c48456 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_009.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..a4d084b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_010.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..1e1a1b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_011.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..1e1a1b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_012.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..1e1a1b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_013.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..1e1a1b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_014.png b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..1e1a1b0 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_off_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_000.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..cf09f97 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_000.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_001.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..3218e66 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_001.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_002.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..0acff03 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_002.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_003.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..c93adf4 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_003.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_004.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..5d8ddc96 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_004.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_005.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..47206a4 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_005.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_006.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..7d6a91f --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_006.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_007.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..e062f61 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_007.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_008.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..b0f0dde --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_008.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_009.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_009.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_010.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_010.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_011.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_011.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_012.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_012.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_013.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_013.png diff --git a/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_014.png b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..c54f8d7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_switch_to_on_qntm_014.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_indicator_qntm_alpha.9.png b/core/res/res/drawable-hdpi/btn_toggle_indicator_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..68e17ad --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_indicator_qntm_alpha.9.png diff --git a/core/res/res/drawable-hdpi/btn_toggle_qntm_alpha.9.png b/core/res/res/drawable-hdpi/btn_toggle_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..879d9c2 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_toggle_qntm_alpha.9.png diff --git a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index ba5bd01..0000000 --- a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_000.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..281923e --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_000.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_001.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..e91d4fb --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_001.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_002.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..15baded --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_002.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_003.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..3d5899f --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_003.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_004.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..e2277bd --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_004.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_005.png b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..b502e22 --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_from_pressed_qntm_005.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_000.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..a70be2c --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_000.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_001.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..9442316 --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_001.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_002.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..33db4a88 --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_002.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_003.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..4e1cd16 --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_003.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_004.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..d5254f4 --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_004.png diff --git a/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_005.png b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..7aa0a3e --- /dev/null +++ b/core/res/res/drawable-hdpi/scrubber_control_to_pressed_qntm_005.png diff --git a/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png Binary files differdeleted file mode 100644 index 73e8f1c..0000000 --- a/core/res/res/drawable-hdpi/switch_off_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png Binary files differdeleted file mode 100644 index ff6affe..0000000 --- a/core/res/res/drawable-hdpi/switch_on_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/switch_track_qntm_alpha.9.png b/core/res/res/drawable-hdpi/switch_track_qntm_alpha.9.png Binary files differindex b11de9e..ac1fc23 100644 --- a/core/res/res/drawable-hdpi/switch_track_qntm_alpha.9.png +++ b/core/res/res/drawable-hdpi/switch_track_qntm_alpha.9.png diff --git a/core/res/res/drawable-hdpi/text_select_handle_left_qntm_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_left_qntm_alpha.png Binary files differindex 598b98c..9cdc25b 100644 --- a/core/res/res/drawable-hdpi/text_select_handle_left_qntm_alpha.png +++ b/core/res/res/drawable-hdpi/text_select_handle_left_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/text_select_handle_right_qntm_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_right_qntm_alpha.png Binary files differindex 79fe7c5..276d480 100644 --- a/core/res/res/drawable-hdpi/text_select_handle_right_qntm_alpha.png +++ b/core/res/res/drawable-hdpi/text_select_handle_right_qntm_alpha.png diff --git a/core/res/res/drawable-hdpi/work_icon.png b/core/res/res/drawable-hdpi/work_icon.png Binary files differindex e90866b..be8a36e 100644 --- a/core/res/res/drawable-hdpi/work_icon.png +++ b/core/res/res/drawable-hdpi/work_icon.png diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00000_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 0f44ff9..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00001_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index 9d5dda0..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00002_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index e4ce802..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00003_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index d1806ac..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00004_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index ab9315b..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00005_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 46e90e6..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00006_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index e8c56ff..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00007_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index 59dcb7e..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00008_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index e9bd4a2..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00009_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index 1d05037..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00010_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index 91b40de..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00011_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index c531cab..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00012_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index 11bb387..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00013_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 8843210..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00014_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index 6ff2f3d..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_anim_00015_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index a03c1e2..0000000 --- a/core/res/res/drawable-mdpi/btn_check_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_off_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 2ab6b7f..0000000 --- a/core/res/res/drawable-mdpi/btn_check_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_on_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_check_on_qntm_alpha.png Binary files differdeleted file mode 100644 index 2211d83..0000000 --- a/core/res/res/drawable-mdpi/btn_check_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_000.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..9759818 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_001.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..4eb2c4f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_002.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..e6d6b42 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_003.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..03cb23a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_004.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..bfe3c3d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_005.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..65bdf42 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_006.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..44f9614b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_007.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..cf8ec38 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_008.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..4d624b3 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_009.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..7c4eb7f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_010.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..e90dd31 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_011.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..831c0e8 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_012.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..7355dfd --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_013.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..be71a69 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_014.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..a4a185b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_off_qntm_015.png b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..8d0386f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_off_qntm_015.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_000.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..70793c4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_001.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..632082b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_002.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..e7fc5fb --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_003.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..91a0a33 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_004.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..3bd90d6 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_005.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..5ac39ec --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_006.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..4181983 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_007.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..c8b04df --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_008.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..b7b3a9f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_009.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..62bc4ed --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_010.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..ac463ad --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_011.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..12b605d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_012.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..63a3c6a --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_013.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..17660c4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_014.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..7d9de3d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_check_to_on_qntm_015.png b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..8aa1be2 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_check_to_on_qntm_015.png diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00000_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 0a22e1a..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00001_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index 2e2469c..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00002_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index c1054d9..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00003_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index cf8d80a..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00004_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index 9d9e870..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00005_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 1bad701..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00006_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index a84a54f..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00007_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index 4d8050b..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00008_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index 374172c..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00009_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index 233036e..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00010_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index 61d9b58..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00011_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index 274e983..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00012_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index acf16e5..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00013_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index ee48241..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00014_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index dbbb736..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_anim_00015_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index bcabd0d..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_off_pressed_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_off_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index 713fee8..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_off_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_off_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_radio_off_qntm_alpha.png Binary files differdeleted file mode 100644 index dcb90d0..0000000 --- a/core/res/res/drawable-mdpi/btn_radio_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_000.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..a2b7fce --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_001.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..fe0d3b1 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_002.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..d66d00d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_003.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..2f2f5cd --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_004.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..72c9495 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_005.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..7d9090f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_006.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..c5442e8 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_007.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..ca80cdb --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_008.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..d41a10b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_009.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..262c838 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_010.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..7f6ea8b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_011.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..8d50a81 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_012.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..0725a68 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_013.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..6191a4b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_014.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..1904d74 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_015.png b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..bec8dda --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_off_qntm_015.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_000.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..54ef480 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_001.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..55c5163 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_002.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..0fe2a89 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_003.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..86efab7 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_004.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..c0a5ca5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_005.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..ec55175 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_006.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..3e4a690 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_007.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..da49734 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_008.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..471cda1 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_009.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..d560262 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_010.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..f6096b4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_011.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..9e2500b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_012.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..efbac99 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_013.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..676f0ca --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_014.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..4803157 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_015.png b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..4f8a162 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_radio_to_on_qntm_015.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_000.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_001.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..3617168 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_002.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..e4366f4 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_003.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..ea4533b --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_004.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..94aedbb --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_005.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..ef84578 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_006.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..4de2321 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_007.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..4de2321 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_008.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..d62fbd5 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_009.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..3d87c4e --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_010.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..536ed46 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_011.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..536ed46 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_012.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..536ed46 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_013.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..536ed46 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_014.png b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..536ed46 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_off_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_000.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..f5b660d --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_000.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_001.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..9e4db6c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_001.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_002.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..7de2128 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_002.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_003.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..1980c2c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_003.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_004.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..6e73ef0 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_004.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_005.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..f897392 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_005.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_006.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..74a6ebd --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_006.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_007.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..3617168 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_007.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_008.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..884eb66 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_008.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_009.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_009.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_010.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_010.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_011.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_011.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_012.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_012.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_013.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_013.png diff --git a/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_014.png b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..8c3f26c --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_switch_to_on_qntm_014.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_indicator_qntm_alpha.9.png b/core/res/res/drawable-mdpi/btn_toggle_indicator_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..e5bface --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_indicator_qntm_alpha.9.png diff --git a/core/res/res/drawable-mdpi/btn_toggle_qntm_alpha.9.png b/core/res/res/drawable-mdpi/btn_toggle_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..dca86ea --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_toggle_qntm_alpha.9.png diff --git a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index 4e2612d..0000000 --- a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_000.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..377a6b4 --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_000.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_001.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..0859f62 --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_001.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_002.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..bf5cdcd --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_002.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_003.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..4cd177d --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_003.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_004.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..dfe39ca --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_004.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_005.png b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..5d3ab99 --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_from_pressed_qntm_005.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_000.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..80922eb --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_000.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_001.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..aa77044 --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_001.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_002.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..7b099db --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_002.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_003.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..088c86a --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_003.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_004.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..3c6b9bc --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_004.png diff --git a/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_005.png b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..adf5100 --- /dev/null +++ b/core/res/res/drawable-mdpi/scrubber_control_to_pressed_qntm_005.png diff --git a/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png Binary files differdeleted file mode 100644 index 8949b52..0000000 --- a/core/res/res/drawable-mdpi/switch_off_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png Binary files differdeleted file mode 100644 index d727683..0000000 --- a/core/res/res/drawable-mdpi/switch_on_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/switch_track_qntm_alpha.9.png b/core/res/res/drawable-mdpi/switch_track_qntm_alpha.9.png Binary files differindex 8991421..b6538e4 100644 --- a/core/res/res/drawable-mdpi/switch_track_qntm_alpha.9.png +++ b/core/res/res/drawable-mdpi/switch_track_qntm_alpha.9.png diff --git a/core/res/res/drawable-mdpi/text_select_handle_left_qntm_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_left_qntm_alpha.png Binary files differindex 506a186..95c0168 100644 --- a/core/res/res/drawable-mdpi/text_select_handle_left_qntm_alpha.png +++ b/core/res/res/drawable-mdpi/text_select_handle_left_qntm_alpha.png diff --git a/core/res/res/drawable-mdpi/text_select_handle_right_qntm_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_right_qntm_alpha.png Binary files differindex fb0e926..569332a 100644 --- a/core/res/res/drawable-mdpi/text_select_handle_right_qntm_alpha.png +++ b/core/res/res/drawable-mdpi/text_select_handle_right_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00000_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 25500e8..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00001_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index b136e25..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00002_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index 6a94e30..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00003_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index d386421..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00004_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index c811385..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00005_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 58b3267..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00006_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index 0659e72..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00007_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index b4227d1..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00008_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index 714ef00..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00009_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index 139595b..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00010_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index 4491107..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00011_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index 20eb752..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00012_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index 532c9f2..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00013_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 0d78a32..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00014_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index af29678..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_anim_00015_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index 23eb9e3..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_off_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 5d820ae..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_on_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_check_on_qntm_alpha.png Binary files differdeleted file mode 100644 index 019c92e..0000000 --- a/core/res/res/drawable-xhdpi/btn_check_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_000.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..2347643 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_001.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..70aaa01 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_002.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..01e498a --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_003.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..71d1cf7 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_004.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..d1e7b1d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_005.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..7db7d06 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_006.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..dadb62e --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_007.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..f87f744 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_008.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..be99d87 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_009.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..f83bc05 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_010.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..870071d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_011.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..3a18414 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_012.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..f3d1187 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_013.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..4078cca --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_014.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..d4849b5 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_015.png b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..6e2af72 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_off_qntm_015.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_000.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..9244174 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_001.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..8c7fe95 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_002.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..71eb1d0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_003.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..613f38a --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_004.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..2d20ccc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_005.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..407f78d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_006.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..1bf24b0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_007.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..a450bd0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_008.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..63ba593 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_009.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..6d05e5a --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_010.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..1c8cd8f --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_011.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..b8bc564 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_012.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..3d80128 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_013.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..c21dfba --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_014.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..2dfe90d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_015.png b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..5f40d73 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_check_to_on_qntm_015.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00000_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index cd11e14..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00001_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index b10db83..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00002_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index efeb6fb..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00003_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index 83080af..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00004_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index b9cc322..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00005_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 3b5f9c4..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00006_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index 58c93db..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00007_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index 0f1d010..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00008_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index 05a7a0f..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00009_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index 9345035..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00010_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index 5f149b7..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00011_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index 191f369..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00012_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index 44e08e6..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00013_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 5a9dfa0..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00014_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index ee921c6..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_anim_00015_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index 567bb0c..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_off_pressed_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_off_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index 2fd964e..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_off_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_off_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_radio_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 8873cd6..0000000 --- a/core/res/res/drawable-xhdpi/btn_radio_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_000.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..b54c6ff --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_001.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..fff7056 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_002.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..026462d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_003.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..26cc8de --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_004.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..c055fff --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_005.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..a22e780 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_006.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..357374c --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_007.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..71d4667 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_008.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..2ed175e --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_009.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..e0f7d8e --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_010.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..62b0578 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_011.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..4d6ef4a --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_012.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..37cee2d --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_013.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..a8bc25f --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_014.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..cf68d93 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_015.png b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..96834bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_off_qntm_015.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_000.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..d068dbe --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_001.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..4aabb1e --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_002.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..bbac8e4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_003.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..2fc7459 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_004.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..83c6d0e --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_005.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..45c08d7 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_006.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..05b7dfb --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_007.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..baf9964 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_008.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..d6e0369 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_009.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..3f35270 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_010.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..a5b34dc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_011.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..361967b --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_012.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..c478bb7 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_013.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..075fa0c --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_014.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..d9e364b --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_015.png b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..9924496 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_radio_to_on_qntm_015.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_000.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_001.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..7bd99fe --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_002.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..2ef623b --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_003.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..19db3e0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_004.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..984c3c5 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_005.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..6454190 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_006.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..cee9393 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_007.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..cee9393 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_008.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..437ffdd --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_009.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..d2e14ad --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_010.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..4e2f5bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_011.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..4e2f5bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_012.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..4e2f5bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_013.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..4e2f5bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_014.png b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..4e2f5bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_off_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_000.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..f1bcfa3 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_001.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..ede2fec --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_002.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..94ce017 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_003.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..647cfe3 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_004.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..b3bf923 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_005.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..ae95b2b --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_006.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..b8e4bd6 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_006.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_007.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..ec6d6d7 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_007.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_008.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..c0e493f --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_008.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_009.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_009.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_010.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_010.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_011.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_011.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_012.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_012.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_013.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_013.png diff --git a/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_014.png b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..2494fd4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_switch_to_on_qntm_014.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_indicator_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/btn_toggle_indicator_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..dff391f --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_toggle_indicator_qntm_alpha.9.png diff --git a/core/res/res/drawable-xhdpi/btn_toggle_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/btn_toggle_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..b135338 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_toggle_qntm_alpha.9.png diff --git a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index e6ca1ea..0000000 --- a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_000.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..ea09a31 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_001.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..f9a4391 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_002.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..d9606e1 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_003.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..df2d9d0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_004.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..625a322 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_005.png b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..79e8dde --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_from_pressed_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_000.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..e99c266 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_000.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_001.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..f0329a4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_001.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_002.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..42c40b7 --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_002.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_003.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..807491f --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_003.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_004.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..dfec9cc --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_004.png diff --git a/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_005.png b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..0ed59ea --- /dev/null +++ b/core/res/res/drawable-xhdpi/scrubber_control_to_pressed_qntm_005.png diff --git a/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png Binary files differdeleted file mode 100644 index a7a972c..0000000 --- a/core/res/res/drawable-xhdpi/switch_off_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png Binary files differdeleted file mode 100644 index dd8910b..0000000 --- a/core/res/res/drawable-xhdpi/switch_on_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/switch_track_qntm_alpha.9.png b/core/res/res/drawable-xhdpi/switch_track_qntm_alpha.9.png Binary files differindex 4970f56..d6a0ab2 100644 --- a/core/res/res/drawable-xhdpi/switch_track_qntm_alpha.9.png +++ b/core/res/res/drawable-xhdpi/switch_track_qntm_alpha.9.png diff --git a/core/res/res/drawable-xhdpi/text_select_handle_left_qntm_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_left_qntm_alpha.png Binary files differindex 38b8e8b..a01ac10 100644 --- a/core/res/res/drawable-xhdpi/text_select_handle_left_qntm_alpha.png +++ b/core/res/res/drawable-xhdpi/text_select_handle_left_qntm_alpha.png diff --git a/core/res/res/drawable-xhdpi/text_select_handle_right_qntm_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_right_qntm_alpha.png Binary files differindex d6002a7..d3602d9 100644 --- a/core/res/res/drawable-xhdpi/text_select_handle_right_qntm_alpha.png +++ b/core/res/res/drawable-xhdpi/text_select_handle_right_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00000_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00000_qntm_alpha.png Binary files differdeleted file mode 100644 index 1881f54..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00000_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00001_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00001_qntm_alpha.png Binary files differdeleted file mode 100644 index 6f8ec2d..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00001_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00002_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00002_qntm_alpha.png Binary files differdeleted file mode 100644 index c954ed9..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00002_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00003_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00003_qntm_alpha.png Binary files differdeleted file mode 100644 index 9d1a47e..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00003_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00004_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00004_qntm_alpha.png Binary files differdeleted file mode 100644 index ce63631..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00004_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00005_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00005_qntm_alpha.png Binary files differdeleted file mode 100644 index 430c134..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00005_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00006_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00006_qntm_alpha.png Binary files differdeleted file mode 100644 index cdebf83..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00006_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00007_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00007_qntm_alpha.png Binary files differdeleted file mode 100644 index 40ceadb..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00007_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00008_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00008_qntm_alpha.png Binary files differdeleted file mode 100644 index fb13eb2..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00008_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00009_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00009_qntm_alpha.png Binary files differdeleted file mode 100644 index d716fba..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00009_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00010_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00010_qntm_alpha.png Binary files differdeleted file mode 100644 index b8be041..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00010_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00011_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00011_qntm_alpha.png Binary files differdeleted file mode 100644 index bad0c3c..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00011_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00012_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00012_qntm_alpha.png Binary files differdeleted file mode 100644 index a6368fb..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00012_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00013_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00013_qntm_alpha.png Binary files differdeleted file mode 100644 index 234e5d1..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00013_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00014_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00014_qntm_alpha.png Binary files differdeleted file mode 100644 index 3e7796d..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00014_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_anim_00015_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_anim_00015_qntm_alpha.png Binary files differdeleted file mode 100644 index 0673999..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_anim_00015_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_off_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 2a17861..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_on_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_check_on_qntm_alpha.png Binary files differdeleted file mode 100644 index 61067ac..0000000 --- a/core/res/res/drawable-xxhdpi/btn_check_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..b754381 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..517d7a7 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..2c1d5b6 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..0c6ff7e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..0796601 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..9b4e0f8 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..25767eb --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..cd0951f --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..9ae8165 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..efd9bc6 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..fccbc9d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..dddafca --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..7e37433 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..9bc22de --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..507ed10 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_015.png b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..6a21c7f --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_off_qntm_015.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..0d544d9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..39da0ac --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..d5ada12 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..d4e096c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..468a9b4 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..ea3cd2e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..0652cb0 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..768d2b0 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..1d06a90 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..8a70a80 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..bf9ec7f --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..cff07b9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..40f997e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..6ba84ec --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..766610e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_015.png b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..810a029 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_check_to_on_qntm_015.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_off_pressed_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_radio_off_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index fdbbbce..0000000 --- a/core/res/res/drawable-xxhdpi/btn_radio_off_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_radio_off_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_radio_off_qntm_alpha.png Binary files differdeleted file mode 100644 index 0ec2ee6..0000000 --- a/core/res/res/drawable-xxhdpi/btn_radio_off_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_radio_on_pressed_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_radio_on_pressed_qntm_alpha.png Binary files differdeleted file mode 100644 index b46ee1c..0000000 --- a/core/res/res/drawable-xxhdpi/btn_radio_on_pressed_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_radio_on_qntm_alpha.png b/core/res/res/drawable-xxhdpi/btn_radio_on_qntm_alpha.png Binary files differdeleted file mode 100644 index 8737156..0000000 --- a/core/res/res/drawable-xxhdpi/btn_radio_on_qntm_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..cbc3833 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..4243895 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..b522d37 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..647b965 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..a317497 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..0e4b25f --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..6e279d9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..f0840cc --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..140e9e8 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..5cf8ec5 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..f9624d8 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..899df8c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..6543e1c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..cd758dd --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..72d950c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_015.png b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..07bdbc9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_off_qntm_015.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..c9af24b --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..01de3f5 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..f428bc5 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..ab5c008 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..5b157cf --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..1210be2 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..e6b4140 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..b678e08 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..6ca2a69 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..7de608e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..b2bbcce --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..6950db3 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..c790756 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..ed5d888 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..81a4a63 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_015.png b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..db1d93a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_radio_to_on_qntm_015.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..eff3dd0 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..000a23a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..394d661 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..4e7311d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..d9dcf91 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..674142e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..674142e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..9d4026a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..bb4b426 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..a37076d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..a37076d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..a37076d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..a37076d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..a37076d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_000.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..22e9951 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_001.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..14e6b39 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_002.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..86b2c01 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_003.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..1c565df --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_004.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..b825449 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_005.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..170c234 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_006.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..5477007 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_007.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..eff3dd0 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_008.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..e3fd96a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_009.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_010.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_011.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_012.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_013.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_014.png b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..198ac07 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_switch_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxhdpi/btn_toggle_indicator_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/btn_toggle_indicator_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..0d6a39a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_toggle_indicator_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxhdpi/btn_toggle_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/btn_toggle_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..f235aed --- /dev/null +++ b/core/res/res/drawable-xxhdpi/btn_toggle_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png Binary files differdeleted file mode 100644 index d6018dd..0000000 --- a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_000.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..46aa533 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_001.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..a749469 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_002.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..ef43f00 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_003.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..eebddc3 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_004.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..44b654d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_005.png b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..6e768c1 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_from_pressed_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_000.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..2ac6dae --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_000.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_001.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..91c49ce --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_001.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_002.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..4b4bd1f --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_002.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_003.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..637e596 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_003.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_004.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..42d4d2a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_004.png diff --git a/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_005.png b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..995d1b2 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/scrubber_control_to_pressed_qntm_005.png diff --git a/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png Binary files differdeleted file mode 100644 index 8d79a13..0000000 --- a/core/res/res/drawable-xxhdpi/switch_off_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png Binary files differdeleted file mode 100644 index e0e4ef9..0000000 --- a/core/res/res/drawable-xxhdpi/switch_on_qntm_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/switch_track_qntm_alpha.9.png b/core/res/res/drawable-xxhdpi/switch_track_qntm_alpha.9.png Binary files differindex 74a259b..a8067cb 100644 --- a/core/res/res/drawable-xxhdpi/switch_track_qntm_alpha.9.png +++ b/core/res/res/drawable-xxhdpi/switch_track_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_left_qntm_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_left_qntm_alpha.png Binary files differindex 93469a2..75085ce 100644 --- a/core/res/res/drawable-xxhdpi/text_select_handle_left_qntm_alpha.png +++ b/core/res/res/drawable-xxhdpi/text_select_handle_left_qntm_alpha.png diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_right_qntm_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_right_qntm_alpha.png Binary files differindex b3493e7..e2eb5be 100644 --- a/core/res/res/drawable-xxhdpi/text_select_handle_right_qntm_alpha.png +++ b/core/res/res/drawable-xxhdpi/text_select_handle_right_qntm_alpha.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..f0ff1a7 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..b382df3 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..8cb4ce2 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..4db2b01 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..8c4709b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..1ad960a --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..e47cc20 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..c4d0d51 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..915d56a --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..85795cb --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..157fd91 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..9d446de --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..dfac1f0 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..aed6c08 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..1b8bd6b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_015.png b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..5dd0e5b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_off_qntm_015.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..5dd0e5b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..1a31ad9 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..63c7f12 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..847dd08 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..b93f3cc --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..1e3dea7 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..5a85238 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..35960ca --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..6db5555 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..a9c5851 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..38465bd --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..15942dc --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..67d0d64 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..69b5c1b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..0e5d331 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_015.png b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..f0ff1a7 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_check_to_on_qntm_015.png diff --git a/core/res/res/drawable-xxxhdpi/btn_qntm_alpha.9.png b/core/res/res/drawable-xxxhdpi/btn_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..01eeefe --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..44028af --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..ec13a86 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..43754eb --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..39d1d64 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..f36f883 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..7a4cc5c --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..80a21ec --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..2141104 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..203bd51 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..5df6fc5 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..6d0fced --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..8c0c372 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..4fa6f53 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..d3dbf7d --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..4ccf8de --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_015.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_015.png Binary files differnew file mode 100644 index 0000000..adef871 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_off_qntm_015.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..adef871 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..9fc3556 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..7f00609 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..e4aa58d --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..fe4e4b7 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..86666ca --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..608faaf --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..ec95422 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..76e2754 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..3853eac --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..621aff1 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..d24be2a --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..df33892 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..ff4b818 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..d9793ae --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_015.png b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_015.png Binary files differnew file mode 100644 index 0000000..44028af --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_radio_to_on_qntm_015.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_000.png Binary files differnew file mode 100644 index 0000000..8b202c6 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_001.png Binary files differnew file mode 100644 index 0000000..3b497f3 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_002.png Binary files differnew file mode 100644 index 0000000..532b6de --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_003.png Binary files differnew file mode 100644 index 0000000..403b2fe --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_004.png Binary files differnew file mode 100644 index 0000000..8c5086c --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_005.png Binary files differnew file mode 100644 index 0000000..d4870f8 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_006.png Binary files differnew file mode 100644 index 0000000..c05adf5 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_007.png Binary files differnew file mode 100644 index 0000000..99b2056 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_008.png Binary files differnew file mode 100644 index 0000000..d839358 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_009.png Binary files differnew file mode 100644 index 0000000..913f94d --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_010.png Binary files differnew file mode 100644 index 0000000..7f325b3 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_011.png Binary files differnew file mode 100644 index 0000000..149a9aa --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_012.png Binary files differnew file mode 100644 index 0000000..95c219e --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_013.png Binary files differnew file mode 100644 index 0000000..462a128 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_014.png Binary files differnew file mode 100644 index 0000000..5911d16 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_off_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_000.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_000.png Binary files differnew file mode 100644 index 0000000..e0c6d85 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_001.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_001.png Binary files differnew file mode 100644 index 0000000..5679943 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_002.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_002.png Binary files differnew file mode 100644 index 0000000..54b636d --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_003.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_003.png Binary files differnew file mode 100644 index 0000000..bf9fac0 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_004.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_004.png Binary files differnew file mode 100644 index 0000000..25d5319 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_005.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_005.png Binary files differnew file mode 100644 index 0000000..d2df595 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_006.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_006.png Binary files differnew file mode 100644 index 0000000..7700bde --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_006.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_007.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_007.png Binary files differnew file mode 100644 index 0000000..883f98b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_007.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_008.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_008.png Binary files differnew file mode 100644 index 0000000..b3b2108 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_008.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_009.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_009.png Binary files differnew file mode 100644 index 0000000..3aad596 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_009.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_010.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_010.png Binary files differnew file mode 100644 index 0000000..2017e17 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_010.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_011.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_011.png Binary files differnew file mode 100644 index 0000000..1fc2700 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_011.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_012.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_012.png Binary files differnew file mode 100644 index 0000000..bb8b0f2 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_012.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_013.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_013.png Binary files differnew file mode 100644 index 0000000..66ab8f6 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_013.png diff --git a/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_014.png b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_014.png Binary files differnew file mode 100644 index 0000000..e3424db --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_switch_to_on_qntm_014.png diff --git a/core/res/res/drawable-xxxhdpi/btn_toggle_indicator_qntm_alpha.9.png b/core/res/res/drawable-xxxhdpi/btn_toggle_indicator_qntm_alpha.9.png Binary files differnew file mode 100755 index 0000000..c06740b --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_toggle_indicator_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxxhdpi/btn_toggle_qntm_alpha.9.png b/core/res/res/drawable-xxxhdpi/btn_toggle_qntm_alpha.9.png Binary files differnew file mode 100755 index 0000000..7556167 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/btn_toggle_qntm_alpha.9.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_000.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..0c8f746 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_001.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..5db9deb --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_002.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..3aca6d3 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_003.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..746c74f --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_004.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..454a5b2 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_005.png b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..80ad8cc --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_from_pressed_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_000.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_000.png Binary files differnew file mode 100644 index 0000000..cfd0db4 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_000.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_001.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_001.png Binary files differnew file mode 100644 index 0000000..845092f --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_001.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_002.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_002.png Binary files differnew file mode 100644 index 0000000..0042db4 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_002.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_003.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_003.png Binary files differnew file mode 100644 index 0000000..77b2901 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_003.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_004.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_004.png Binary files differnew file mode 100644 index 0000000..fb3c238 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_004.png diff --git a/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_005.png b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_005.png Binary files differnew file mode 100644 index 0000000..0d28c45 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/scrubber_control_to_pressed_qntm_005.png diff --git a/core/res/res/drawable-xxxhdpi/switch_track_qntm_alpha.9.png b/core/res/res/drawable-xxxhdpi/switch_track_qntm_alpha.9.png Binary files differnew file mode 100644 index 0000000..fb07f2a --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/switch_track_qntm_alpha.9.png diff --git a/core/res/res/drawable/btn_borderless_quantum.xml b/core/res/res/drawable/btn_borderless_quantum.xml index a8def44..2cd7ed6 100644 --- a/core/res/res/drawable/btn_borderless_quantum.xml +++ b/core/res/res/drawable/btn_borderless_quantum.xml @@ -16,21 +16,6 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorControlHighlight"> - <item android:id="@id/mask"> - <inset - android:insetLeft="4dp" - android:insetTop="4dp" - android:insetBottom="4dp" - android:insetRight="4dp"> - <shape android:shape="rectangle"> - <solid android:color="@color/white" /> - <corners android:radius="2dp" /> - <padding - android:left="4dp" - android:top="4dp" - android:bottom="4dp" - android:right="4dp" /> - </shape> - </inset> - </item> + <item android:id="@id/mask" + android:drawable="@drawable/btn_qntm_alpha" /> </ripple> diff --git a/core/res/res/drawable/btn_check_quantum.xml b/core/res/res/drawable/btn_check_quantum.xml deleted file mode 100644 index 6ceba60..0000000 --- a/core/res/res/drawable/btn_check_quantum.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_checked="true" android:state_pressed="true"> - <bitmap android:src="@drawable/btn_check_on_qntm_alpha" - android:tint="?attr/colorControlActivated" /> - </item> - <item android:state_checked="true"> - <bitmap android:src="@drawable/btn_check_on_qntm_alpha" - android:tint="?attr/colorControlActivated" /> - </item> - <item android:state_pressed="true"> - <bitmap android:src="@drawable/btn_check_off_qntm_alpha" - android:tint="?attr/colorControlActivated" /> - </item> - <item> - <bitmap android:src="@drawable/btn_check_off_qntm_alpha" - android:tint="?attr/colorControlNormal" /> - </item> -</selector> diff --git a/core/res/res/drawable/btn_check_quantum_anim.xml b/core/res/res/drawable/btn_check_quantum_anim.xml index 96715a4..b16875e 100644 --- a/core/res/res/drawable/btn_check_quantum_anim.xml +++ b/core/res/res/drawable/btn_check_quantum_anim.xml @@ -16,88 +16,118 @@ <animated-selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false" android:state_checked="true"> - <bitmap android:src="@drawable/btn_check_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" - android:alpha="?attr/disabledAlpha" /> + <bitmap android:src="@drawable/btn_check_to_on_qntm_015" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" /> </item> <item android:state_enabled="false"> - <bitmap android:src="@drawable/btn_check_anim_00000_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> + <bitmap android:src="@drawable/btn_check_to_on_qntm_000" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> </item> <item android:state_checked="true" android:id="@+id/on"> - <bitmap android:src="@drawable/btn_check_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <bitmap android:src="@drawable/btn_check_to_on_qntm_015" android:tint="?attr/colorControlActivated" /> </item> <item android:id="@+id/off"> - <bitmap android:src="@drawable/btn_check_anim_00000_qntm_alpha" - android:tint="?attr/colorControlNormal" /> + <bitmap android:src="@drawable/btn_check_to_on_qntm_000" android:tint="?attr/colorControlNormal" /> </item> - <transition android:fromId="@+id/off" android:toId="@+id/on" android:reversible="true"> + <transition android:fromId="@+id/off" android:toId="@+id/on"> <animation-list> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00000_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_000" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00001_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_001" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00002_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_002" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00003_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_003" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00004_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_004" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00005_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_005" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00006_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_006" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00007_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_007" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00008_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_008" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00009_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_009" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00010_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_010" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00011_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_011" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00012_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_012" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00013_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_013" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00014_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_014" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_check_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_on_qntm_015" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> + <transition android:fromId="@+id/on" android:toId="@+id/off"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_000" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_001" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_002" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_003" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_004" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_005" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_006" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_007" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_008" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_009" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_010" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_011" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_012" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_013" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_014" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_check_to_off_qntm_015" android:tint="?attr/colorControlActivated" /> </item> </animation-list> </transition> diff --git a/core/res/res/drawable/btn_default_quantum.xml b/core/res/res/drawable/btn_default_quantum.xml index 63473a4..61193fe 100644 --- a/core/res/res/drawable/btn_default_quantum.xml +++ b/core/res/res/drawable/btn_default_quantum.xml @@ -17,20 +17,7 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:tint="?attr/colorControlHighlight"> <item> - <inset - android:insetLeft="4dp" - android:insetTop="4dp" - android:insetBottom="4dp" - android:insetRight="4dp"> - <shape android:shape="rectangle"> - <solid android:color="?attr/colorButtonNormal" /> - <corners android:radius="2dp" /> - <padding - android:left="4dp" - android:top="4dp" - android:bottom="4dp" - android:right="4dp" /> - </shape> - </inset> + <nine-patch android:src="@drawable/btn_qntm_alpha" + android:tint="?attr/colorButtonNormal" /> </item> </ripple> diff --git a/core/res/res/drawable/btn_radio_quantum.xml b/core/res/res/drawable/btn_radio_quantum.xml deleted file mode 100644 index 0f9ebce..0000000 --- a/core/res/res/drawable/btn_radio_quantum.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:state_checked="true"> - <bitmap android:src="@drawable/btn_radio_on_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> - </item> - <item android:state_enabled="false"> - <bitmap android:src="@drawable/btn_radio_off_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> - </item> - <item android:state_checked="true"> - <bitmap android:src="@drawable/btn_radio_on_qntm_alpha" - android:tint="?attr/colorControlActivated" /> - </item> - <item> - <bitmap android:src="@drawable/btn_radio_off_qntm_alpha" - android:tint="?attr/colorControlNormal" /> - </item> -</selector> diff --git a/core/res/res/drawable/btn_radio_quantum_anim.xml b/core/res/res/drawable/btn_radio_quantum_anim.xml index 5068b7a..cd9b518 100644 --- a/core/res/res/drawable/btn_radio_quantum_anim.xml +++ b/core/res/res/drawable/btn_radio_quantum_anim.xml @@ -16,88 +16,118 @@ <animated-selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false" android:state_checked="true"> - <bitmap android:src="@drawable/btn_radio_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" - android:alpha="?attr/disabledAlpha" /> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_015" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" /> </item> <item android:state_enabled="false"> - <bitmap android:src="@drawable/btn_radio_anim_00000_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_000" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> </item> <item android:state_checked="true" android:id="@+id/on"> - <bitmap android:src="@drawable/btn_radio_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_015" android:tint="?attr/colorControlActivated" /> </item> <item android:id="@+id/off"> - <bitmap android:src="@drawable/btn_radio_anim_00000_qntm_alpha" - android:tint="?attr/colorControlNormal" /> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_000" android:tint="?attr/colorControlNormal" /> </item> - <transition android:fromId="@+id/off" android:toId="@+id/on" android:reversible="true"> + <transition android:fromId="@+id/off" android:toId="@+id/on"> <animation-list> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00000_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_000" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00001_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_001" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00002_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_002" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00003_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_003" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00004_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_004" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00005_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_005" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00006_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_006" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00007_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_007" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00008_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_008" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00009_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_009" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00010_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_010" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00011_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_011" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00012_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_012" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00013_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_013" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00014_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_014" android:tint="?attr/colorControlActivated" /> </item> - <item android:duration="33"> - <bitmap android:src="@drawable/btn_radio_anim_00015_qntm_alpha" - android:tint="?attr/colorControlActivated" /> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_on_qntm_015" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> + <transition android:fromId="@+id/on" android:toId="@+id/off"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_000" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_001" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_002" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_003" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_004" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_005" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_006" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_007" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_008" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_009" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_010" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_011" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_012" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_013" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_014" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_radio_to_off_qntm_015" android:tint="?attr/colorControlActivated" /> </item> </animation-list> </transition> diff --git a/core/res/res/drawable/btn_toggle_quantum.xml b/core/res/res/drawable/btn_toggle_quantum.xml new file mode 100644 index 0000000..e235598 --- /dev/null +++ b/core/res/res/drawable/btn_toggle_quantum.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="4dp" + android:insetTop="4dp" + android:insetBottom="4dp" + android:insetRight="4dp"> + <layer-list android:paddingMode="stack"> + <item> + <ripple android:tint="?attr/colorControlHighlight"> + <item> + <nine-patch android:src="@drawable/btn_toggle_qntm_alpha" + android:tint="?attr/colorButtonNormal" /> + </item> + </ripple> + </item> + <item> + <selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="false"> + <nine-patch android:src="@drawable/btn_toggle_indicator_qntm_alpha" + android:tint="?attr/colorControlNormal" /> + </item> + <item android:state_checked="true"> + <nine-patch android:src="@drawable/btn_toggle_indicator_qntm_alpha" + android:tint="?attr/colorControlActivated" /> + </item> + </selector> + </item> + </layer-list> +</inset> diff --git a/core/res/res/drawable/ic_audio_ring_notif.xml b/core/res/res/drawable/ic_audio_ring_notif.xml index 247d1b4..b52db5c 100644 --- a/core/res/res/drawable/ic_audio_ring_notif.xml +++ b/core/res/res/drawable/ic_audio_ring_notif.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +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" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> +</vector> diff --git a/core/res/res/drawable/ic_audio_ring_notif_mute.xml b/core/res/res/drawable/ic_audio_ring_notif_mute.xml index 72aaa9d..8d7d6cb 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_mute.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_mute.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +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" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_mute_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" /> +</vector> diff --git a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml index 9e31aba..2f1d940 100644 --- a/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml +++ b/core/res/res/drawable/ic_audio_ring_notif_vibrate.xml @@ -1,23 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- -/* - * Copyright 2013, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +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" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_audio_ring_notif_vibrate_am_alpha" - android:autoMirrored="true" - android:tint="?attr/colorControlNormal" /> + <path + android:fill="#8A000000" + android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/> +</vector> diff --git a/core/res/res/drawable/ic_lock_bugreport.xml b/core/res/res/drawable/ic_lock_bugreport.xml index a3f82ce..b93a09a 100644 --- a/core/res/res/drawable/ic_lock_bugreport.xml +++ b/core/res/res/drawable/ic_lock_bugreport.xml @@ -1,19 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- +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 + 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 + 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. + 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" > + <size + android:width="32dp" + android:height="32dp"/> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_lock_bugreport_alpha" - android:tint="?attr/colorControlNormal" /> + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="?attr/colorControlNormal" + android:pathData="M20.0,8.0l-2.8,0.0c-0.5,-0.8 -1.1,-1.5 -1.8,-2.0L17.0,4.4L15.6,3.0l-2.2,2.2C13.0,5.1 12.5,5.0 12.0,5.0s-1.0,0.1 -1.4,0.2L8.4,3.0L7.0,4.4L8.6,6.0C7.9,6.5 7.3,7.2 6.8,8.0L4.0,8.0l0.0,2.0l2.1,0.0C6.0,10.3 6.0,10.7 6.0,11.0l0.0,1.0L4.0,12.0l0.0,2.0l2.0,0.0l0.0,1.0c0.0,0.3 0.0,0.7 0.1,1.0L4.0,16.0l0.0,2.0l2.8,0.0c1.0,1.8 3.0,3.0 5.2,3.0s4.2,-1.2 5.2,-3.0L20.0,18.0l0.0,-2.0l-2.1,0.0c0.1,-0.3 0.1,-0.7 0.1,-1.0l0.0,-1.0l2.0,0.0l0.0,-2.0l-2.0,0.0l0.0,-1.0c0.0,-0.3 0.0,-0.7 -0.1,-1.0L20.0,10.0L20.0,8.0zM14.0,16.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,16.0zM14.0,12.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,12.0z"/> +</vector> diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/item_background_borderless_quantum.xml index 6cd59e5..c2a1c127 100644 --- a/core/res/res/drawable/list_selector_quantum.xml +++ b/core/res/res/drawable/item_background_borderless_quantum.xml @@ -15,8 +15,5 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:tint="?attr/colorControlHighlight"> - <item android:id="@id/mask"> - <color android:color="@color/white" /> - </item> -</ripple> + android:tint="?attr/colorControlHighlight" + android:pinned="true" /> diff --git a/core/res/res/drawable/item_background_quantum.xml b/core/res/res/drawable/item_background_quantum.xml index c2a1c127..039ca51 100644 --- a/core/res/res/drawable/item_background_quantum.xml +++ b/core/res/res/drawable/item_background_quantum.xml @@ -15,5 +15,8 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:tint="?attr/colorControlHighlight" - android:pinned="true" /> + android:tint="?attr/colorControlHighlight"> + <item android:id="@id/mask"> + <color android:color="@color/white" /> + </item> +</ripple>
\ No newline at end of file diff --git a/core/res/res/drawable/scrubber_control_quantum_anim.xml b/core/res/res/drawable/scrubber_control_quantum_anim.xml new file mode 100644 index 0000000..87d3ae9 --- /dev/null +++ b/core/res/res/drawable/scrubber_control_quantum_anim.xml @@ -0,0 +1,74 @@ +<?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-selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true"> + <item android:state_enabled="false" android:state_pressed="true"> + <bitmap android:src="@drawable/scrubber_control_off_qntm_alpha" android:gravity="center" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" /> + </item> + <item android:state_enabled="false"> + <bitmap android:src="@drawable/scrubber_control_off_qntm_alpha" android:gravity="center" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> + </item> + <item android:state_pressed="true" android:id="@+id/pressed"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_005" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:id="@+id/not_pressed"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_000" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <transition android:fromId="@+id/not_pressed" android:toId="@+id/pressed"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_000" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_001" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_002" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_003" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_004" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_to_pressed_qntm_005" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> + <transition android:fromId="@+id/pressed" android:toId="@+id/not_pressed"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_000" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_001" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_002" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_003" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_004" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/scrubber_control_from_pressed_qntm_005" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> +</animated-selector> diff --git a/core/res/res/drawable/switch_inner_quantum.xml b/core/res/res/drawable/switch_inner_quantum.xml deleted file mode 100644 index 856895e..0000000 --- a/core/res/res/drawable/switch_inner_quantum.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:state_checked="true"> - <nine-patch android:src="@drawable/switch_on_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> - </item> - <item android:state_enabled="false"> - <nine-patch android:src="@drawable/switch_off_qntm_alpha" - android:tint="?attr/colorControlNormal" - android:alpha="?attr/disabledAlpha" /> - </item> - <item android:state_checked="true"> - <nine-patch android:src="@drawable/switch_on_qntm_alpha" - android:tint="?attr/colorControlActivated" /> - </item> - <item> - <nine-patch android:src="@drawable/switch_off_qntm_alpha" - android:tint="?attr/colorControlNormal" /> - </item> -</selector> diff --git a/core/res/res/drawable/switch_thumb_quantum_anim.xml b/core/res/res/drawable/switch_thumb_quantum_anim.xml new file mode 100644 index 0000000..1984d47 --- /dev/null +++ b/core/res/res/drawable/switch_thumb_quantum_anim.xml @@ -0,0 +1,128 @@ +<?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-selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true"> + <item android:state_enabled="false" android:state_checked="true"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_014" android:gravity="center" android:tint="?attr/colorControlActivated" android:alpha="?attr/disabledAlpha" /> + </item> + <item android:state_enabled="false"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_000" android:gravity="center" android:tint="?attr/colorControlNormal" android:alpha="?attr/disabledAlpha" /> + </item> + <item android:state_checked="true" android:id="@+id/on"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_014" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:id="@+id/off"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_000" android:gravity="center" android:tint="?attr/colorControlNormal" /> + </item> + <transition android:fromId="@+id/off" android:toId="@+id/on"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_000" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_001" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_002" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_003" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_004" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_005" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_006" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_007" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_008" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_009" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_010" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_011" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_012" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_013" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_on_qntm_014" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> + <transition android:fromId="@+id/on" android:toId="@+id/off"> + <animation-list> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_000" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_001" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_002" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_003" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_004" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_005" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_006" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_007" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_008" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_009" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_010" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_011" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_012" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_013" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + <item android:duration="15"> + <bitmap android:src="@drawable/btn_switch_to_off_qntm_014" android:gravity="center" android:tint="?attr/colorControlActivated" /> + </item> + </animation-list> + </transition> +</animated-selector> diff --git a/core/res/res/drawable/switch_track_quantum.xml b/core/res/res/drawable/switch_track_quantum.xml index 8c4e6b71..3651a0a 100644 --- a/core/res/res/drawable/switch_track_quantum.xml +++ b/core/res/res/drawable/switch_track_quantum.xml @@ -15,6 +15,16 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" android:state_checked="true"> + <nine-patch android:src="@drawable/switch_track_qntm_alpha" + android:tint="?attr/colorControlActivated" + android:alpha="?attr/disabledAlpha" /> + </item> + <item android:state_enabled="false"> + <nine-patch android:src="@drawable/switch_track_qntm_alpha" + android:tint="?attr/colorControlNormal" + android:alpha="?attr/disabledAlpha" /> + </item> <item android:state_checked="true"> <nine-patch android:src="@drawable/switch_track_qntm_alpha" android:tint="?attr/colorControlActivated" /> diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml index e109425..7fd22ad 100644 --- a/core/res/res/layout/alert_dialog_quantum.xml +++ b/core/res/res/layout/alert_dialog_quantum.xml @@ -23,7 +23,10 @@ android:orientation="vertical" android:background="@drawable/dialog_background_quantum" android:translationZ="@dimen/floating_window_z" - android:layout_margin="@dimen/floating_window_margin"> + android:layout_marginLeft="@dimen/floating_window_margin_left" + android:layout_marginTop="@dimen/floating_window_margin_top" + android:layout_marginRight="@dimen/floating_window_margin_right" + android:layout_marginBottom="@dimen/floating_window_margin_bottom"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" @@ -44,7 +47,7 @@ android:scaleType="fitCenter" android:src="@null" /> <TextView android:id="@+id/alertTitle" - style="?android:attr/windowTitleStyle" + style="?attr/windowTitleStyle" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" @@ -65,7 +68,7 @@ android:layout_height="wrap_content" android:clipToPadding="false"> <TextView android:id="@+id/message" - style="?android:attr/textAppearanceMedium" + style="?attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingStart="16dip" @@ -92,26 +95,24 @@ android:gravity="end" android:padding="16dip"> <LinearLayout - style="?android:attr/buttonBarStyle" + style="?attr/buttonBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layoutDirection="locale"> <Button android:id="@+id/button3" - style="?android:attr/buttonBarButtonStyle" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="8dip" android:maxLines="2" android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button2" - style="?android:attr/buttonBarButtonStyle" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" android:minHeight="@dimen/alert_dialog_button_bar_height" /> <Button android:id="@+id/button1" - style="?android:attr/buttonBarButtonStyle" - android:layout_marginLeft="8dip" + style="?attr/buttonBarButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="2" diff --git a/core/res/res/layout/global_actions_silent_mode.xml b/core/res/res/layout/global_actions_silent_mode.xml index 79401af..a358623 100644 --- a/core/res/res/layout/global_actions_silent_mode.xml +++ b/core/res/res/layout/global_actions_silent_mode.xml @@ -37,7 +37,7 @@ android:layout_marginEnd="8dp" android:layout_marginTop="6dp" android:layout_marginBottom="6dp" - android:src="@drawable/ic_audio_vol_mute" + android:src="@drawable/ic_audio_ring_notif_mute" android:scaleType="center" android:duplicateParentState="true" android:background="@drawable/silent_mode_indicator" @@ -94,7 +94,7 @@ android:layout_marginEnd="8dp" android:layout_marginTop="6dp" android:layout_marginBottom="6dp" - android:src="@drawable/ic_audio_vol" + android:src="@drawable/ic_audio_ring_notif" android:scaleType="center" android:duplicateParentState="true" android:background="@drawable/silent_mode_indicator" diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml new file mode 100644 index 0000000..290c7da --- /dev/null +++ b/core/res/res/layout/screen_toolbar.xml @@ -0,0 +1,51 @@ +<?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. +--> + +<!-- +This is an optimized layout for a screen with a toolbar enabled. +--> + +<com.android.internal.widget.ActionBarOverlayLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/decor_content_parent" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:splitMotionEvents="false" + android:theme="?attr/actionBarTheme"> + <FrameLayout android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <com.android.internal.widget.ActionBarContainer + android:id="@+id/action_bar_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + style="?attr/actionBarStyle" + android:viewName="android:action_bar" + android:gravity="top"> + <Toolbar + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/toolbarStyle" /> + <com.android.internal.widget.ActionBarContextView + android:id="@+id/action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + style="?attr/actionModeStyle" /> + </com.android.internal.widget.ActionBarContainer> +</com.android.internal.widget.ActionBarOverlayLayout> diff --git a/core/res/res/layout/volume_adjust.xml b/core/res/res/layout/volume_adjust.xml deleted file mode 100644 index 3ad1f23..0000000 --- a/core/res/res/layout/volume_adjust.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2007 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/visible_panel" - android:orientation="horizontal" - android:layout_width="300dp" - android:layout_height="wrap_content"> - - <LinearLayout - android:id="@+id/slider_group" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> - <!-- Sliders go here --> - </LinearLayout> - - <ImageView - android:id="@+id/expand_button_divider" - android:src="?attr/dividerVertical" - android:layout_width="wrap_content" - android:layout_height="32dip" - android:scaleType="fitXY" - android:layout_gravity="top" - android:layout_marginTop="16dip" - android:layout_marginBottom="16dip" /> - - <ImageView - android:id="@+id/expand_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top" - android:padding="16dip" - android:background="?attr/selectableItemBackground" - android:src="@drawable/ic_sysbar_quicksettings" /> - -</LinearLayout> diff --git a/core/res/res/layout/volume_adjust_item.xml b/core/res/res/layout/volume_adjust_item.xml deleted file mode 100644 index 57cecf4..0000000 --- a/core/res/res/layout/volume_adjust_item.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="80dip" - android:orientation="horizontal" - android:layout_marginTop="8dip" - android:layout_marginBottom="8dip" - android:gravity="start|center_vertical"> - - <ImageView - android:id="@+id/stream_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="16dip" - android:background="?attr/selectableItemBackground" - android:contentDescription="@null" /> - - <SeekBar - style="?android:attr/seekBarStyle" - android:id="@+id/seekbar" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:padding="16dip" - android:layout_marginEnd="16dip" /> - -</LinearLayout> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index df59abe..b17b8c6 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -267,7 +267,7 @@ <string name="permdesc_statusBarService" msgid="716113660795976060">"Tillader, at appen er statusbjælken."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"udvid/skjul statuslinje"</string> <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"Tillader, at appen kan udvide og skjule statusbjælken."</string> - <string name="permlab_install_shortcut" msgid="4279070216371564234">"installer genveje"</string> + <string name="permlab_install_shortcut" msgid="4279070216371564234">"installere genveje"</string> <string name="permdesc_install_shortcut" msgid="8341295916286736996">"Tillader, at en applikation føjer genveje til startskærmen uden brugerindgriben."</string> <string name="permlab_uninstall_shortcut" msgid="4729634524044003699">"afinstaller genveje"</string> <string name="permdesc_uninstall_shortcut" msgid="6745743474265057975">"Tillader, at applikationen fjerner genveje på startskærmen uden brugerindgriben."</string> @@ -281,7 +281,7 @@ <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"Tillader, at appen kan modtage og behandle nødtransmissioner. Denne tilladelse er kun tilgængelig for systemapps."</string> <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"læse Cell Broadcast-beskeder"</string> <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Tillader, at appen læser Cell Broadcast-beskeder, der modtages af din enhed. I nogle områder sendes der Cell Broadcast-beskeder for at advare om nødsituationer. Ondsindede apps kan forstyrre ydelsen eller driften af din enhed, når der modtages en Cell Broadcast-besked om en nødsituation."</string> - <string name="permlab_sendSms" msgid="5600830612147671529">"send sms-beskeder"</string> + <string name="permlab_sendSms" msgid="5600830612147671529">"sende sms-beskeder"</string> <string name="permdesc_sendSms" msgid="7094729298204937667">"Tillader, at appen kan sende sms-beskeder. Dette kan resultere i uventede opkrævninger. Skadelige apps kan koste dig penge ved at sende beskeder uden din bekræftelse."</string> <string name="permlab_sendRespondViaMessageRequest" msgid="8713889105305943200">"send hændelser, hvor der skal svares pr. besked"</string> <string name="permdesc_sendRespondViaMessageRequest" msgid="7107648548468778734">"Tillader, at appen kan sende anmodninger til andre apps til beskeder for at håndtere hændelser, hvor der skal svares pr. besked."</string> @@ -295,7 +295,7 @@ <string name="permdesc_receiveWapPush" msgid="748232190220583385">"Tillader, at appen kan modtage og behandle WAP-beskeder. Denne tilladelse omfatter muligheden for at overvåge eller slette de beskeder, der sendes til dig, uden at vise dem til dig."</string> <string name="permlab_getTasks" msgid="6466095396623933906">"hente kørende apps"</string> <string name="permdesc_getTasks" msgid="7454215995847658102">"Tillader, at appen kan hente oplysninger om nuværende og seneste opgaver. Med denne tilladelse kan appen finde oplysninger om, hvilke applikationer der bruges på enheden."</string> - <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"kommuniker på tværs af brugere"</string> + <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"kommunikere på tværs af brugere"</string> <string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"Tillader, at appen udfører handlinger på tværs af forskellige brugere på enheden. Ondsindede apps kan bruge dette til at krænke beskyttelsen mellem brugere."</string> <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"fuld licens til at kommunikere på tværs af brugere"</string> <string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Tillader alle mulige former for kommunikation på tværs af brugere."</string> @@ -325,7 +325,7 @@ <string name="permdesc_forceStopPackages" msgid="5253157296183940812">"Tillader, at appen kan tvinge andre apps til at stoppe."</string> <string name="permlab_forceBack" msgid="652935204072584616">"tvinge appen til at lukke"</string> <string name="permdesc_forceBack" msgid="3892295830419513623">"Tillader, at appen kan tvinge enhver aktivitet i forgrunden til at lukke og gå tilbage. Bør aldrig være nødvendigt til almindelige apps."</string> - <string name="permlab_dump" msgid="1681799862438954752">"hent intern systemtilstand"</string> + <string name="permlab_dump" msgid="1681799862438954752">"hente intern systemtilstand"</string> <string name="permdesc_dump" msgid="1778299088692290329">"Tillader, at appen kan hente systemets interne tilstand. Ondsindede apps kan hente en lang række fortrolige og beskyttede oplysninger, som de normalt aldrig ville have brug for."</string> <string name="permlab_retrieve_window_content" msgid="8022588608994589938">"hente skærmindhold"</string> <string name="permdesc_retrieve_window_content" msgid="3193269069469700265">"Tillader, at appen kan hente indholdet i det aktive vindue. Ondsindede apps kan hente al indholdet i vinduet og undersøge al dens tekst med undtagelse af adgangskoder."</string> @@ -359,7 +359,7 @@ <string name="permdesc_batteryStats" msgid="5897346582882915114">"Tillader, at en applikation læser de aktuelle data for batteriforbruget. Kan tillade, at applikationen henter detaljerede oplysninger om, hvilke apps du bruger."</string> <string name="permlab_updateBatteryStats" msgid="3719689764536379557">"rediger batteristatistikker"</string> <string name="permdesc_updateBatteryStats" msgid="6862817857178025002">"Tillader, at appen kan ændre indsamlede batteristatistikker. Anvendes ikke af normale apps."</string> - <string name="permlab_getAppOpsStats" msgid="1508779687436585744">"hent statistikker for handlinger i appen"</string> + <string name="permlab_getAppOpsStats" msgid="1508779687436585744">"hente statistikker om handlinger i appen"</string> <string name="permdesc_getAppOpsStats" msgid="6243887041577912877">"Tillader, at appen indhenter statistikker for handlinger i applikationen. Denne handling bruges ikke i almindelige apps."</string> <string name="permlab_updateAppOpsStats" msgid="8829097373851521505">"lav ændringer i statistik for handlinger i appen"</string> <string name="permdesc_updateAppOpsStats" msgid="50784596594403483">"Tillader, at appen kan ændre indsamlede statistikker for handlinger i applikationen. Dette kan ikke bruges af almindelige apps."</string> @@ -474,7 +474,7 @@ <string name="permlab_writeContacts" msgid="5107492086416793544">"ændre dine kontaktpersoner"</string> <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Tillader, at appen kan ændre data om de kontaktpersoner, der er gemt på din tablet, f.eks. hvor ofte du har ringet til dem, sendt dem en e-mail eller på anden måde kommunikeret med bestemte kontaktpersoner. Med denne tilladelse kan apps slette kontaktoplysninger."</string> <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Tillader, at appen kan ændre data om de kontaktpersoner, der er gemt på din telefon, f.eks. hvor ofte du har ringet til dem, sendt en e-mail til dem eller på anden måde kommunikeret med bestemte kontaktpersoner. Med denne tilladelse kan apps slette kontaktoplysninger."</string> - <string name="permlab_readCallLog" msgid="3478133184624102739">"læs opkaldsliste"</string> + <string name="permlab_readCallLog" msgid="3478133184624102739">"læse opkaldsliste"</string> <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"Tillader, at appen kan læse din tablets opkaldsliste, f.eks. data om indgående og udgående opkald. Med denne tilladelse kan apps gemme dine opkaldslistedata, og skadelige apps kan dele opkaldslistedata uden din viden."</string> <string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"Tillader, at appen kan læse telefonens opkaldsliste, f.eks. data om indgående og udgående opkald. Med denne tilladelse kan apps gemme dine opkaldslistedata, og skadelige apps kan dele disse opkaldslistedata uden din viden."</string> <string name="permlab_writeCallLog" msgid="8552045664743499354">"skriv opkaldsliste"</string> @@ -486,9 +486,9 @@ <string name="permdesc_writeProfile" product="default" msgid="5552084294598465899">"Tillader, at appen kan ændre eller tilføje oplysninger i din personlige profil, der er gemt på din enhed, f.eks. dit navn eller dine kontaktoplysninger. Dette betyder, at andre apps kan identificere dig og sende profiloplysninger til andre."</string> <string name="permlab_bodySensors" msgid="4871091374767171066">"kropssensorer (f.eks. pulsmålere)"</string> <string name="permdesc_bodySensors" product="default" msgid="2998865085124153531">"Tillader, at appen får adgang til data fra sensorer, du bruger til at måle, hvad der sker inde i din krop, f.eks. din puls."</string> - <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"læs din sociale strøm"</string> + <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"læse din sociale strøm"</string> <string name="permdesc_readSocialStream" product="default" msgid="4255706027172050872">"Tillader, at appen kan få adgang til og synkronisere sociale opdateringer fra dig og dine venner. Vær forsigtig, når du deler oplysninger – med denne tilladelse kan appen læse kommunikation mellem dig og dine venner på sociale netværk, uanset fortrolighed. Bemærk! Denne tilladelse håndhæves muligvis ikke på alle sociale netværk."</string> - <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"skriv i din sociale strøm"</string> + <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"skrive i din sociale strøm"</string> <string name="permdesc_writeSocialStream" product="default" msgid="3086557552204114849">"Tillader, at appen kan vise sociale opdateringer fra dine venner. Vær forsigtig, når du deler oplysninger – med denne tilladelse kan appen producere meddelelser, der kan synes at komme fra en ven. Bemærk! Denne tilladelse håndhæves muligvis ikke på alle sociale netværk."</string> <string name="permlab_readCalendar" msgid="5972727560257612398">"læse kalenderbegivenheder og fortrolige oplysninger"</string> <string name="permdesc_readCalendar" product="tablet" msgid="4216462049057658723">"Tillader, at appen kan læse alle de kalenderbegivenheder, der er gemt på din tablet, f.eks. venners eller kollegers. Med denne tilladelse kan appen dele eller gemme dine kalenderdata, uanset fortrolighed eller følsomhed."</string> @@ -526,7 +526,7 @@ <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Tillader, at appen opfanger og omdirigerer et sikkert videooutput."</string> <string name="permlab_mediaContentControl" msgid="8749790560720562511">"kontrollér medieafspilning og metadataadgang"</string> <string name="permdesc_mediaContentControl" msgid="1637478200272062">"Tillader, at appen styrer medieafspilning og får adgang til medieoplysninger (titel, forfatter...)."</string> - <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"skift dine lydindstillinger"</string> + <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"skifte dine lydindstillinger"</string> <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"optage lyd"</string> <string name="permdesc_recordAudio" msgid="4906839301087980680">"Tillader, at appen kan optage lyd med mikrofonen. Med denne tilladelse kan appen til enhver tid optage lyd uden din bekræftelse."</string> @@ -632,7 +632,7 @@ <string name="permdesc_createNetworkSockets" msgid="3403062187779724185">"Tillader, at appen kan oprette netværkssockets og bruge tilpassede netværksprotokoller. Browseren og andre applikationer indeholder midler til at sende data til internettet, så med denne tilladelse er der ingen forpligtelse til at sende data til internettet."</string> <string name="permlab_writeApnSettings" msgid="505660159675751896">"ændre/opfange netværksindstillinger og trafik"</string> <string name="permdesc_writeApnSettings" msgid="5333798886412714193">"Tillader, at appen kan ændre netværksindstillinger og opsnappe og inspicere al netværkstrafik, f.eks. for at ændre proxy og port for et adgangspunkt. Ondsindede apps kan overvåge, omdirigere eller ændre netværkspakker uden din viden."</string> - <string name="permlab_changeNetworkState" msgid="958884291454327309">"skift netværksforbindelse"</string> + <string name="permlab_changeNetworkState" msgid="958884291454327309">"skifte netværksforbindelse"</string> <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"Tillader, at appen kan ændre netværksforbindelsens tilstand."</string> <string name="permlab_changeTetherState" msgid="5952584964373017960">"skifte forbindelse til netdeling"</string> <string name="permdesc_changeTetherState" msgid="1524441344412319780">"Tillader, at appen kan ændre tilstand for en netværksforbindelse via netdeling."</string> @@ -672,9 +672,9 @@ <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"Tillader, at en app kan ændre synkroniseringsindstillingerne for en konto. Denne tilladelse kan f.eks. anvendes til at aktivere synkronisering af appen Personer med en konto."</string> <string name="permlab_readSyncStats" msgid="7396577451360202448">"læse synkroniseringsstatistikker"</string> <string name="permdesc_readSyncStats" msgid="1510143761757606156">"Tillader, at en app kan læse synkroniseringsstatistikkerne for en konto, f.eks. historikken for synkroniserede begivenheder og hvor meget data der synkroniseres."</string> - <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"læs abonnerede feeds"</string> + <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"læse feeds, jeg abonnerer på"</string> <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Tillader, at appen kan hente oplysninger om de feeds, der synkroniseres."</string> - <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"skriv abonnerede feeds"</string> + <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"skrive feeds, som jeg abonnerer på"</string> <string name="permdesc_subscribedFeedsWrite" msgid="6928930188826089413">"Tillader, at appen kan ændre dine synkroniserede feeds. Ondsindede apps kan ændre dine synkroniserede feeds."</string> <string name="permlab_readDictionary" msgid="4107101525746035718">"læse termer, som du har føjet til ordbogen"</string> <string name="permdesc_readDictionary" msgid="659614600338904243">"Tillader, at appen kan læse alle ord, navne og sætninger, som brugeren har gemt i brugerordbogen."</string> @@ -995,7 +995,7 @@ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Tillader, at appen kan ændre browserens historik eller de bogmærker, der er gemt på din telefon. Dette kan give appen tilladelse til at slette eller ændre browserdata. Bemærk! Denne tilladelse håndhæves muligvis ikke af tredjepartsbrowsere eller andre applikationer med websøgningsfunktioner."</string> <string name="permlab_setAlarm" msgid="1379294556362091814">"indstille en alarm"</string> <string name="permdesc_setAlarm" msgid="316392039157473848">"Tillader, at appen kan indstille en alarm i en installeret alarmapp. Nogle alarmapps har muligvis ikke denne funktion."</string> - <string name="permlab_addVoicemail" msgid="5525660026090959044">"tilføj telefonsvarer"</string> + <string name="permlab_addVoicemail" msgid="5525660026090959044">"tilføje telefonsvarer"</string> <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Tillader, at appen kan tilføje beskeder på din telefonsvarer."</string> <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"skifte tilladelser til geografisk placering i Browser"</string> <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Tillader, at appen kan ændre browserens tilladelser angående geografisk placering. Ondsindede apps kan benytte dette til at sende oplysninger om placering til vilkårlige websites."</string> @@ -1275,7 +1275,7 @@ <string name="date_picker_dialog_title" msgid="5879450659453782278">"Angiv dato"</string> <string name="date_time_set" msgid="5777075614321087758">"Angiv"</string> <string name="date_time_done" msgid="2507683751759308828">"Udført"</string> - <string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"NYHED! "</font></string> + <string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"NY: "</font></string> <string name="perms_description_app" msgid="5139836143293299417">"Leveret af <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="no_permissions" msgid="7283357728219338112">"Der kræves ingen tilladelser"</string> <string name="perm_costs_money" msgid="4902470324142151116">"dette kan koste dig penge"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 989eec6..d0561b9 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1471,8 +1471,8 @@ <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string> <string name="activitychooserview_choose_application" msgid="2125168057199941199">"Επιλέξτε κάποια εφαρμογή"</string> <string name="activitychooserview_choose_application_error" msgid="8624618365481126668">"Δεν ήταν δυνατή η εκκίνηση του <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string> - <string name="shareactionprovider_share_with" msgid="806688056141131819">"Κοινοποίηση με"</string> - <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Κοινοποίηση με <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string> + <string name="shareactionprovider_share_with" msgid="806688056141131819">"Κοινή χρήση με"</string> + <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"Κοινή χρήση με <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string> <string name="content_description_sliding_handle" msgid="415975056159262248">"Στοιχείο χειρισμού με δυνατότητα ολίσθησης. Αγγίξτε και πατήστε παρατεταμένα."</string> <string name="description_target_unlock_tablet" msgid="3833195335629795055">"Σύρετε για ξεκλείδωμα."</string> <string name="keyboard_headset_required_to_hear_password" msgid="7011927352267668657">"Συνδέστε ακουστικά για να ακούσετε τα πλήκτρα του κωδικού πρόσβασης να εκφωνούνται."</string> @@ -1516,7 +1516,7 @@ <string name="sha1_fingerprint" msgid="7930330235269404581">"Αποτύπωμα SHA-1"</string> <string name="activity_chooser_view_see_all" msgid="4292569383976636200">"Εμφάνιση όλων"</string> <string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"Επιλογή δραστηριότητας"</string> - <string name="share_action_provider_share_with" msgid="5247684435979149216">"Κοινοποίηση με"</string> + <string name="share_action_provider_share_with" msgid="5247684435979149216">"Κοινή χρήση με"</string> <string name="list_delimeter" msgid="3975117572185494152">", "</string> <string name="sending" msgid="3245653681008218030">"Γίνεται αποστολή…"</string> <string name="launchBrowserDefault" msgid="2057951947297614725">"Εκκίνηση προγράμματος περιήγησης;"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index c8eca25..3d043c4 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -294,7 +294,7 @@ <string name="permlab_receiveWapPush" msgid="5991398711936590410">"recevoir des messages texte (WAP)"</string> <string name="permdesc_receiveWapPush" msgid="748232190220583385">"Permet à l\'application de recevoir et de traiter les messages WAP. Cette autorisation lui donne la possibilité de surveiller ou supprimer les messages envoyés à votre appareil sans vous les montrer."</string> <string name="permlab_getTasks" msgid="6466095396623933906">"récupérer les applications en cours d\'exécution"</string> - <string name="permdesc_getTasks" msgid="7454215995847658102">"Permet à l\'application de récupérer des informations sur des tâches en cours d\'exécution et récemment exécutées. L\'application est ainsi susceptible de d\'obtenir des informations sur les applications utilisées sur l\'appareil."</string> + <string name="permdesc_getTasks" msgid="7454215995847658102">"Permet à l\'application de récupérer des informations sur des tâches en cours d\'exécution et récemment exécutées. L\'application est ainsi susceptible d\'obtenir des informations sur les applications utilisées sur l\'appareil."</string> <string name="permlab_interactAcrossUsers" msgid="7114255281944211682">"interagir entre les utilisateurs"</string> <string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"Permet à l\'application d\'effectuer des actions entre les différents utilisateurs de l\'appareil. Les applications malveillantes peuvent utiliser cette autorisation pour passer outre la protection entre les utilisateurs."</string> <string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"autorisation totale d\'interagir entre les utilisateurs"</string> @@ -503,7 +503,7 @@ <string name="permlab_installLocationProvider" msgid="6578101199825193873">"autoriser l\'installation d\'un fournisseur de services de localisation"</string> <string name="permdesc_installLocationProvider" msgid="9066146120470591509">"Permet de créer des sources de localisation fictives à des fins de tests ou pour installer un nouveau fournisseur de position. L\'application peut ainsi modifier la position et/ou l\'état renvoyé par d\'autres sources de localisation telles que le GPS ou les fournisseurs de position."</string> <string name="permlab_accessFineLocation" msgid="1191898061965273372">"position précise (GPS et réseau)"</string> - <string name="permdesc_accessFineLocation" msgid="5295047563564981250">"Permet à l\'application d\'obtenir votre position exacte à l\'aide du récepteur satellite GPS ou des sources de localisation de réseau tels que les points d\'accès Wi-Fi et les antennes-relais. Ces services de localisation doivent être activés et disponibles sur votre appareil pour que l\'application puissent déterminer où vous vous trouvez, le cas échéant. Cette autorisation peut entraîner une utilisation accrue de la batterie."</string> + <string name="permdesc_accessFineLocation" msgid="5295047563564981250">"Permet à l\'application d\'obtenir votre position exacte à l\'aide du récepteur satellite GPS ou des sources de localisation de réseau tels que les points d\'accès Wi-Fi et les antennes-relais. Ces services de localisation doivent être activés et disponibles sur votre appareil pour que l\'application puisse déterminer où vous vous trouvez, le cas échéant. Cette autorisation peut entraîner une utilisation accrue de la batterie."</string> <string name="permlab_accessCoarseLocation" msgid="4887895362354239628">"position approximative (réseau)"</string> <string name="permdesc_accessCoarseLocation" msgid="2538200184373302295">"Permet à l\'application d\'obtenir votre position approximative. Celle-ci est fournie par des services de localisation sur la base des sources de localisation de réseau tels que les points d\'accès Wi-Fi et les antennes-relais. Ces services de localisation doivent être activés et disponibles sur votre appareil pour que l\'application puisse déterminer où vous vous trouvez de façon approximative, le cas échéant."</string> <string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"Accès à SurfaceFlinger"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 000fb3a..2adaaf1 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -496,7 +496,7 @@ <string name="permlab_writeCalendar" msgid="8438874755193825647">"dodajte ili izmijenite kalendarske događaje i pošaljite e-poštu gostima bez znanja vlasnika"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Aplikaciji omogućuje dodavanje, uklanjanje i promjenu događaja koje možete izmijeniti na tabletnom računalu, uključujući one od vaših prijatelja ili suradnika. To aplikaciji može omogućiti slanje poruka koje izgledaju kao da dolaze od vlasnika kalendara ili izmjenu događaja bez znanja vlasnika."</string> <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Aplikaciji omogućuje dodavanje, uklanjanje i promjenu događaja koje možete izmijeniti na telefonu, uključujući one od vaših prijatelja ili suradnika. To aplikaciji može omogućiti slanje poruka koje izgledaju kao da dolaze od vlasnika kalendara ili izmjenu događaja bez znanja vlasnika."</string> - <string name="permlab_accessMockLocation" msgid="8688334974036823330">"omogući testiranje izvora lokacije"</string> + <string name="permlab_accessMockLocation" msgid="8688334974036823330">"omogućeno testiranje izvora lokacije"</string> <string name="permdesc_accessMockLocation" msgid="5808711039482051824">"Stvaranje lažnih izvora lokacije radi testiranja ili za instaliranje novog pružatelja usluga lokacije. To aplikaciji omogućuje zaobilaženje lokacije i/ili statusa koji vraćaju drugi izvori lokacije, primjerice GPS ili pružatelji usluga lokacije."</string> <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"pristup dodatnim naredbama davatelja lokacije"</string> <string name="permdesc_accessLocationExtraCommands" msgid="5945166642335800763">"Aplikaciji omogućuje pristup dodatnim naredbama za pružatelja usluga lokacije. To aplikaciji može omogućiti ometanje rada GPS-a ili drugih izvora lokacije."</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 4640f2a..d235eaa 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -187,7 +187,7 @@ <string name="global_action_settings" msgid="1756531602592545966">"การตั้งค่า"</string> <string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string> <string name="safeMode" msgid="2788228061547930246">"โหมดปลอดภัย"</string> - <string name="android_system_label" msgid="6577375335728551336">"ระบบ Android"</string> + <string name="android_system_label" msgid="6577375335728551336">"ระบบแอนดรอยด์"</string> <string name="user_owner_label" msgid="2804351898001038951">"ส่วนตัว"</string> <string name="managed_profile_label" msgid="6260850669674791528">"ที่ทำงาน"</string> <string name="permgrouplab_costMoney" msgid="5429808217861460401">"บริการที่ต้องเสียค่าใช้จ่าย"</string> @@ -424,7 +424,7 @@ <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"อนุญาตให้แอปพลิเคชันทำให้ส่วนหนึ่งของตัวเองคงอยู่ถาวรในหน่วยความจำ ซึ่งจะจำกัดพื้นที่หน่วยความจำที่ใช้งานได้ของแอปพลิเคชันอื่นๆ และทำให้แท็บเล็ตทำงานช้าลง"</string> <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"อนุญาตให้แอปพลิเคชันทำให้ส่วนหนึ่งของตัวเองคงอยู่ถาวรในหน่วยความจำ ซึ่งจะจำกัดพื้นที่หน่วยความจำที่ใช้งานได้ของแอปพลิเคชันอื่นๆ และทำให้โทรศัพท์ทำงานช้าลง"</string> <string name="permlab_deletePackages" msgid="184385129537705938">"ลบแอปพลิเคชัน"</string> - <string name="permdesc_deletePackages" msgid="7411480275167205081">"อนุญาตให้แอปพลิเคชันลบแพ็กเกจ Android แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ลบแอปพลิเคชันที่สำคัญ"</string> + <string name="permdesc_deletePackages" msgid="7411480275167205081">"อนุญาตให้แอปพลิเคชันลบแพ็กเกจแอนดรอยด์แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ลบแอปพลิเคชันที่สำคัญ"</string> <string name="permlab_clearAppUserData" msgid="274109191845842756">"ลบข้อมูลของแอปพลิเคชันอื่น"</string> <string name="permdesc_clearAppUserData" msgid="4625323684125459488">"อนุญาตให้แอปพลิเคชันล้างข้อมูลผู้ใช้"</string> <string name="permlab_deleteCacheFiles" msgid="3128665571837408675">"ลบแคชของแอปพลิเคชันอื่น"</string> @@ -432,7 +432,7 @@ <string name="permlab_getPackageSize" msgid="7472921768357981986">"วัดพื้นที่เก็บข้อมูลของแอปพลิเคชัน"</string> <string name="permdesc_getPackageSize" msgid="3921068154420738296">"อนุญาตให้แอปพลิเคชันเรียกดูรหัส ข้อมูล และขนาดแคชของตน"</string> <string name="permlab_installPackages" msgid="2199128482820306924">"ติดตั้งแอปพลิเคชันโดยตรง"</string> - <string name="permdesc_installPackages" msgid="5628530972548071284">"อนุญาตให้แอปพลิเคชันติดตั้งแพ็กเกจ Android ใหม่หรือที่อัปเดต แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ในการเพิ่มแอปพลิเคชันใหม่ๆ ด้วยสิทธิ์ที่สูงนี้ได้ตามต้องการ"</string> + <string name="permdesc_installPackages" msgid="5628530972548071284">"อนุญาตให้แอปพลิเคชันติดตั้งแพ็กเกจแอนดรอยด์ใหม่หรือที่อัปเดต แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ในการเพิ่มแอปพลิเคชันใหม่ๆ ด้วยสิทธิ์ที่สูงนี้ได้ตามต้องการ"</string> <string name="permlab_clearAppCache" msgid="7487279391723526815">"ลบข้อมูลแคชของแอปพลิเคชันทั้งหมด"</string> <string name="permdesc_clearAppCache" product="tablet" msgid="8974640871945434565">"อนุญาตให้แอปพลิเคชันสร้างพื้นที่ว่างในที่จัดเก็บข้อมูลของแท็บเล็ต โดยลบไฟล์ในไดเรกทอรีแคชของแอปพลิเคชันอื่นๆ ซึ่งอาจทำให้แอปพลิเคชันอื่นเริ่มทำงานช้ากว่าเดิมเนื่องจากต้องดึงข้อมูลของตนซ้ำ"</string> <string name="permdesc_clearAppCache" product="default" msgid="2459441021956436779">"อนุญาตให้แอปพลิเคชันสร้างพื้นที่ว่างในที่จัดเก็บข้อมูลของโทรศัพท์ โดยลบไฟล์ในไดเรกทอรีแคชของแอปพลิเคชันอื่นๆ ซึ่งอาจทำให้แอปพลิเคชันอื่นเริ่มทำงานช้ากว่าเดิมเนื่องจากต้องดึงข้อมูลของตนซ้ำ"</string> @@ -1190,7 +1190,7 @@ <string name="screen_compat_mode_hint" msgid="1064524084543304459">"เปิดใช้งานอีกครั้งในการตั้งค่าระบบ > แอปพลิเคชัน > ดาวน์โหลด"</string> <string name="smv_application" msgid="3307209192155442829">"แอปพลิเคชัน <xliff:g id="APPLICATION">%1$s</xliff:g> (กระบวนการ <xliff:g id="PROCESS">%2$s</xliff:g>) ละเมิดนโยบาย StrictMode ที่บังคับใช้ด้วยตัวเอง"</string> <string name="smv_process" msgid="5120397012047462446">"กระบวนการ <xliff:g id="PROCESS">%1$s</xliff:g> ละเมิดนโยบาย StrictMode ที่บังคับใช้ด้วยตัวเอง"</string> - <string name="android_upgrading_title" msgid="1584192285441405746">"กำลังอัปเกรด Android..."</string> + <string name="android_upgrading_title" msgid="1584192285441405746">"กำลังอัปเกรดแอนดรอยด์..."</string> <string name="android_upgrading_apk" msgid="7904042682111526169">"กำลังเพิ่มประสิทธิภาพแอปพลิเคชัน <xliff:g id="NUMBER_0">%1$d</xliff:g> จาก <xliff:g id="NUMBER_1">%2$d</xliff:g> รายการ"</string> <string name="android_upgrading_starting_apps" msgid="451464516346926713">"กำลังเริ่มต้นแอปพลิเคชัน"</string> <string name="android_upgrading_complete" msgid="1405954754112999229">"เสร็จสิ้นการบูต"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c05dfca..b8086be 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -493,6 +493,9 @@ {@link android.view.Window#setAllowExitTransitionOverlap(boolean)}. --> <attr name="windowAllowExitTransitionOverlap" format="boolean"/> + <!-- Internal layout used internally for window decor --> + <attr name="windowActionBarFullscreenDecorLayout" format="reference" /> + <!-- ============ --> <!-- Alert Dialog styles --> <!-- ============ --> @@ -685,6 +688,7 @@ <!-- Default ActivityChooserView style. --> <attr name="activityChooserViewStyle" format="reference" /> + <!-- Default Toolbar style. --> <attr name="toolbarStyle" format="reference" /> <!-- Fast scroller styles --> @@ -886,9 +890,12 @@ with the appearance of a singel button broken into segments. --> <attr name="segmentedButtonStyle" format="reference" /> - <!-- Background drawable for standalone items that need focus/pressed states. --> + <!-- Background drawable for bordered standalone items that need focus/pressed states. --> <attr name="selectableItemBackground" format="reference" /> + <!-- Background drawable for borderless standalone items that need focus/pressed states. --> + <attr name="selectableItemBackgroundBorderless" format="reference" /> + <!-- Style for buttons without an explicit border, often used in groups. --> <attr name="borderlessButtonStyle" format="reference" /> @@ -1713,6 +1720,7 @@ <attr name="windowSwipeToDismiss" /> <attr name="windowContentTransitions" /> <attr name="windowContentTransitionManager" /> + <attr name="windowActionBarFullscreenDecorLayout" /> <!-- The minimum width the window is allowed to be, along the major axis of the screen. That is, when in landscape. Can be either @@ -4057,6 +4065,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_rowSpan" format="integer" min="1" /> + <!-- The relative proportion of horizontal space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_rowWeight" format="float" /> <!-- The column boundary delimiting the left of the group of cells occupied by this view. --> <attr name="layout_column" /> @@ -4065,6 +4076,9 @@ The default is one. See {@link android.widget.GridLayout.Spec}. --> <attr name="layout_columnSpan" format="integer" min="1" /> + <!-- The relative proportion of vertical space that should be allocated to this view + during excess space distribution. --> + <attr name="layout_columnWeight" format="float" /> <!-- Gravity specifies how a component should be placed in its group of cells. The default is LEFT | BASELINE. See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. --> @@ -4653,11 +4667,11 @@ <!-- Drawable used to show animated touch feedback. --> <declare-styleable name="RippleDrawable"> - <!-- The tint to use for feedback ripples. This attribute is required. --> + <!-- The tint to use for ripple effects. This attribute is required. --> <attr name="tint" /> - <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. --> + <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default value is src_atop, which draws over the opaque parts of the drawable. --> <attr name="tintMode" /> - <!-- Whether to pin feedback ripples to the center of the drawable. Default value is false. --> + <!-- Whether to pin ripple effects to the center of the drawable. Default value is false. --> <attr name="pinned" format="boolean" /> </declare-styleable> @@ -4774,6 +4788,28 @@ <attr name="height" /> </declare-styleable> + <!-- Defines the group used in Vector Drawables. --> + <declare-styleable name="VectorDrawableGroup"> + <!-- The Name of this group --> + <attr name="name" /> + <!-- The amount to rotate the group --> + <attr name="rotation" /> + <!-- The X coordinate of the center of rotation of a group --> + <attr name="pivotX" /> + <!-- The Y coordinate of the center of rotation of a group --> + <attr name="pivotY" /> + <!-- The amount to translate the group on X coordinate --> + <attr name="translateX" format="float"/> + <!-- The amount to translate the group on Y coordinate --> + <attr name="translateY" format="float"/> + <!-- The amount to scale the group on X coordinate --> + <attr name="scaleX" /> + <!-- The amount to scale the group on X coordinate --> + <attr name="scaleY" /> + <!-- The alpha of the group (0 is transparent and 1 is opaque) --> + <attr name="alpha" /> + </declare-styleable> + <!-- Defines the path used in Vector Drawables. --> <declare-styleable name="VectorDrawablePath"> <!-- The Name of this path --> @@ -4782,12 +4818,6 @@ <attr name="strokeWidth" format="float" /> <!-- The opacity of a path stroke --> <attr name="strokeOpacity" format="float" /> - <!-- The amount to rotate the path stroke --> - <attr name="rotation" /> - <!-- The X coordinate of the center of rotation of a path --> - <attr name="pivotX" /> - <!-- The Y coordinate of the center of rotation of a path --> - <attr name="pivotY" /> <!-- The color to stroke the path if not defined implies no stroke--> <attr name="stroke" format="color" /> <!-- The color to fill the path if not defined implies no fill--> @@ -6119,6 +6149,11 @@ <!-- Component name of an activity that allows the user to modify the settings for this trust agent. --> <attr name="settingsActivity" /> + <!-- Title for a preference that allows that user to launch the + activity to modify trust agent settings. --> + <attr name="title" /> + <!-- Summary for the same preference as the title. --> + <attr name="summary" /> </declare-styleable> <!-- =============================== --> @@ -6398,11 +6433,17 @@ <attr name="indeterminateProgressStyle" format="reference" /> <!-- Specifies the horizontal padding on either end for an embedded progress bar. --> <attr name="progressBarPadding" format="dimension" /> + <!-- Up navigation glyph --> + <attr name="homeAsUpIndicator" /> <!-- Specifies padding that should be applied to the left and right sides of system-provided items in the bar. --> <attr name="itemPadding" format="dimension" /> <!-- Set true to hide the action bar on a vertical nested scroll of content. --> <attr name="hideOnContentScroll" format="boolean" /> + <attr name="contentInsetStart" format="dimension" /> + <attr name="contentInsetEnd" format="dimension" /> + <attr name="contentInsetLeft" format="dimension" /> + <attr name="contentInsetRight" format="dimension" /> </declare-styleable> <declare-styleable name="ActionMode"> @@ -6653,10 +6694,19 @@ <attr name="titleMarginEnd" format="dimension" /> <attr name="titleMarginTop" format="dimension" /> <attr name="titleMarginBottom" format="dimension" /> - <attr name="contentInsetStart" format="dimension" /> - <attr name="contentInsetEnd" format="dimension" /> - <attr name="contentInsetLeft" format="dimension" /> - <attr name="contentInsetRight" format="dimension" /> + <attr name="contentInsetStart" /> + <attr name="contentInsetEnd" /> + <attr name="contentInsetLeft" /> + <attr name="contentInsetRight" /> + <attr name="maxButtonHeight" format="dimension" /> + <attr name="navigationButtonStyle" format="reference" /> + <attr name="buttonGravity"> + <!-- Push object to the top of its container, not changing its size. --> + <flag name="top" value="0x30" /> + <!-- Push object to the bottom of its container, not changing its size. --> + <flag name="bottom" value="0x50" /> + </attr> + <attr name="collapseIcon" format="reference" /> </declare-styleable> <declare-styleable name="Toolbar_LayoutParams"> @@ -6671,4 +6721,17 @@ <declare-styleable name="EdgeEffect"> <attr name="colorPrimaryLight" /> </declare-styleable> + + <!-- Use <code>tv-input</code> as the root tag of the XML resource that describes an + {@link android.media.tv.TvInputService}, which is referenced from its + {@link android.media.tv.TvInputService#SERVICE_META_DATA} meta-data entry. + Described here are the attributes that can be included in that tag. --> + <declare-styleable name="TvInputService"> + <!-- Component name of an activity for setup of this service. + The setup includes scanning channels and registering EPG data. --> + <attr name="setupActivity" format="string" /> + <!-- Component name of an activity that allows the user to modify + the settings for this service. --> + <attr name="settingsActivity" /> + </declare-styleable> </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b1f256e..acfbe2d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -885,41 +885,57 @@ be passed a persistable Bundle in their Intent.extras. --> <attr name="persistable" format="boolean" /> - <!-- Specify whether this activity should always be launched in doc-centric mode. For - values other than <code>none</code> the activity must be defined with + <!-- This attribute specifies that an activity shall become the root activity of a + new task each time it is launched. Using this attribute permits the user to + have multiple documents from the same applications appear in the recent tasks list. + + <p>Such a document is any kind of item for which an application may want to + maintain multiple simultaneous instances. Examples might be text files, web + pages, spreadsheets, or emails. Each such document will be in a separate + task in the recent taskss list. + + <p>This attribute is equivalent to adding the flag {@link + android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} to every Intent used to launch + the activity. + + <p>The documentLaunchMode attribute may be assigned one of three values, "none", + "intoExisting" and "always", described in detail below. For values other than + <code>none</code> the activity must be defined with {@link android.R.attr#launchMode} <code>standard</code> or <code>singleTop</code>. - This attribute can be overridden by {@link - android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. - - <p>If this attribute is not specified, <code>none</code> will be used. - Note that this launch behavior can be changed in some ways at runtime - through the {@link android.content.Intent} flags - {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. --> + If this attribute is not specified, <code>none</code> will be used. + Note that <code>none</code> can be overridden at run time if the Intent used + to launch it contains the flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. + Similarly <code>intoExisting</code> will be overridden by the flag + {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} combined with + {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. --> <attr name="documentLaunchMode"> <!-- The default mode, which will create a new task only when {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK} is set. --> <enum name="none" value="0" /> - <!-- All tasks will be searched for a matching Intent. If one is found - That task will cleared and restarted with the root activity receiving a call + <!-- All tasks will be searched for one whose base Intent's ComponentName and + data URI match those of the launching Intent. If such a task is found + that task will be cleared and restarted with the root activity receiving a call to {@link android.app.Activity#onNewIntent Activity.onNewIntent}. If no such task is found a new task will be created. - This is the equivalent of with {@link + <p>This is the equivalent of launching an activity with {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} - without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK - Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + set and without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK} set. --> <enum name="intoExisting" value="1" /> - <!-- A new task rooted at this activity will be created. - This is the equivalent of with {@link + <!-- A new task rooted at this activity will be created. This will happen whether or + not there is an existing task whose ComponentName and data URI match + that of the launcing intent This is the equivalent of launching an activity + with {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} - paired with {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK - Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + and {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK} both set. --> <enum name="always" value="2" /> </attr> - <!-- Tasks launched by activities with this attribute will remain in the recent task + <!-- Tasks launched by activities with this attribute will remain in the recent tasks list until the last activity in the task is completed. When that happens the task - will be automatically removed from the recent task list. + will be automatically removed from the recent tasks list. This attribute is the equivalent of {@link android.content.Intent#FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml index 556463e..976930c 100644 --- a/core/res/res/values/colors_quantum.xml +++ b/core/res/res/values/colors_quantum.xml @@ -16,8 +16,14 @@ <!-- Colors specific to Quantum themes. --> <resources> - <color name="background_quantum_dark">#ff303030</color> - <color name="background_quantum_light">@color/white</color> + <color name="background_quantum_dark">#ff414042</color> + <color name="background_quantum_light">#fff1f2f2</color> + + <color name="ripple_quantum_dark">#30ffffff</color> + <color name="ripple_quantum_light">#30000000</color> + + <color name="button_quantum_dark">#ff5a595b</color> + <color name="button_quantum_light">#ffd6d7d7</color> <color name="bright_foreground_quantum_dark">@color/white</color> <color name="bright_foreground_quantum_light">@color/black</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5375c14..f6732d3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1538,9 +1538,7 @@ --> <string-array translatable="false" name="config_globalActionsList"> <item>power</item> - <item>airplane</item> <item>bugreport</item> - <item>silent</item> <item>users</item> </string-array> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 38f00fe..657f614 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -202,9 +202,6 @@ <!-- Default width for a textview error popup --> <dimen name="textview_error_popup_default_width">240dip</dimen> - <!-- Volume panel y offset --> - <dimen name="volume_panel_top">16dp</dimen> - <!-- Default padding to apply to AppWidgetHostViews containing widgets targeting API level 14 and up. --> <dimen name="default_app_widget_padding_left">8dp</dimen> <dimen name="default_app_widget_padding_top">8dp</dimen> diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml index 53e97fd..b5ba1ca 100644 --- a/core/res/res/values/dimens_quantum.xml +++ b/core/res/res/values/dimens_quantum.xml @@ -47,8 +47,15 @@ <dimen name="text_size_menu_quantum">14sp</dimen> <dimen name="text_size_button_quantum">14sp</dimen> + <dimen name="text_size_large_quantum">22sp</dimen> + <dimen name="text_size_medium_quantum">18sp</dimen> + <dimen name="text_size_small_quantum">14sp</dimen> + <dimen name="floating_window_z">16dp</dimen> - <dimen name="floating_window_margin">32dp</dimen> + <dimen name="floating_window_margin_left">16dp</dimen> + <dimen name="floating_window_margin_top">8dp</dimen> + <dimen name="floating_window_margin_right">16dp</dimen> + <dimen name="floating_window_margin_bottom">32dp</dimen> <!-- the amount of elevation for pressed button state--> <dimen name="button_pressed_z">2dp</dimen> diff --git a/core/res/res/values/donottranslate_quantum.xml b/core/res/res/values/donottranslate_quantum.xml index 83cc4e5..e53c40e 100644 --- a/core/res/res/values/donottranslate_quantum.xml +++ b/core/res/res/values/donottranslate_quantum.xml @@ -27,6 +27,6 @@ <string name="font_family_body_1_quantum">sans-serif</string> <string name="font_family_caption_quantum">sans-serif</string> <string name="font_family_menu_quantum">sans-serif-medium</string> - <string name="font_family_button_quantum">sans-serif</string> + <string name="font_family_button_quantum">sans-serif-medium</string> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7dc967c..f6ffd15 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2096,6 +2096,7 @@ <public type="attr" name="windowSwipeToDismiss" id="0x10103f3" /> <public type="attr" name="isGame" id="0x10103f4" /> <public type="attr" name="allowEmbedded" id="0x10103f5" /> + <public type="attr" name="setupActivity" id="0x10103f6"/> <!-- =============================================================== Resources added in version 21 of the platform @@ -2178,6 +2179,11 @@ <public type="attr" name="contentInsetLeft" /> <public type="attr" name="contentInsetRight" /> <public type="attr" name="paddingMode" /> + <public type="attr" name="layout_rowWeight" /> + <public type="attr" name="layout_columnWeight" /> + <public type="attr" name="translateX" /> + <public type="attr" name="translateY" /> + <public type="attr" name="selectableItemBackgroundBorderless" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2252,6 +2258,7 @@ <public type="style" name="Theme.Quantum.NoActionBar.Overscan" /> <public type="style" name="Theme.Quantum.NoActionBar.TranslucentDecor" /> <public type="style" name="Theme.Quantum.Panel" /> + <public type="style" name="Theme.Quantum.Voice" /> <public type="style" name="Theme.Quantum.Wallpaper" /> <public type="style" name="Theme.Quantum.Wallpaper.NoTitleBar" /> @@ -2268,6 +2275,7 @@ <public type="style" name="Theme.Quantum.Light.NoActionBar.Overscan" /> <public type="style" name="Theme.Quantum.Light.NoActionBar.TranslucentDecor" /> <public type="style" name="Theme.Quantum.Light.Panel" /> + <public type="style" name="Theme.Quantum.Light.Voice" /> <public type="style" name="ThemeOverlay" /> <public type="style" name="ThemeOverlay.Quantum" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8286ef9..9ff67b4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -419,6 +419,8 @@ current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient.</string> + <!-- Format for build summary info [CHAR LIMIT=NONE] --> + <string name="bugreport_status" translatable="false">%s (%s)</string> <!-- label for item that enables silent mode in phone options dialog --> <string name="global_action_toggle_silent_mode">Silent mode</string> @@ -455,10 +457,10 @@ <string name="android_system_label">Android System</string> <!-- Label for the user owner in the intent forwarding app. --> - <string name="user_owner_label">Personal</string> + <string name="user_owner_label">Personal apps</string> <!-- Label for a corporate profile in the intent forwarding app. --> - <string name="managed_profile_label">Work</string> + <string name="managed_profile_label">Android for Work</string> <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_costMoney">Services that cost you money</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 4a27ebe..59bd667 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -169,10 +169,16 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/input_method_exit</item> </style> + <!-- Window animations that are applied to voice activity windows. --> + <style name="Animation.VoiceActivity"> + <item name="windowEnterAnimation">@anim/voice_activity_open_enter</item> + <item name="windowExitAnimation">@anim/voice_activity_close_exit</item> + </style> + <!-- Window animations that are applied to voice interaction overlay windows. --> <style name="Animation.VoiceInteractionSession"> - <item name="windowEnterAnimation">@anim/input_method_enter</item> - <item name="windowExitAnimation">@anim/input_method_exit</item> + <item name="windowEnterAnimation">@anim/voice_layer_enter</item> + <item name="windowExitAnimation">@anim/voice_layer_exit</item> </style> <!-- Special optional fancy IM animations. @hide --> @@ -231,14 +237,6 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/fast_fade_out</item> </style> - <!-- Window animations for swipe-dismissable windows. {@hide} --> - <style name="Animation.SwipeDismiss"> - <item name="taskOpenEnterAnimation">@anim/swipe_window_enter</item> - <item name="taskOpenExitAnimation">@anim/swipe_window_exit</item> - <item name="taskCloseEnterAnimation">@anim/swipe_window_enter</item> - <item name="taskCloseExitAnimation">@anim/swipe_window_exit</item> - </style> - <!-- Status Bar Styles --> <style name="TextAppearance.StatusBar"> <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> @@ -277,37 +275,6 @@ please see styles_device_defaults.xml. <item name="android:textColor">#CCCCCC</item> </style> - <style name="TextAppearance.StatusBar.Quantum"> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent"> - <item name="android:textColor">#90000000</item> - <item name="android:textSize">@dimen/notification_text_size</item> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent.Title"> - <item name="android:textColor">#DD000000</item> - <item name="android:textSize">@dimen/notification_title_text_size</item> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent.Line2"> - <item name="android:textSize">@dimen/notification_subtext_size</item> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent.Info"> - <item name="android:textSize">@dimen/notification_subtext_size</item> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent.Time"> - <item name="android:textSize">@dimen/notification_subtext_size</item> - </style> - <style name="TextAppearance.StatusBar.Quantum.EventContent.Emphasis"> - <item name="android:textColor">#66000000</item> - </style> - <style name="Widget.StatusBar.Quantum.ProgressBar" - parent="Widget.Quantum.Light.ProgressBar.Horizontal"> - <item name="android:progressDrawable">@drawable/notification_quantum_media_progress</item> - </style> - - <style name="Widget.StatusBar.Quantum.ProgressBar" - parent="Widget.Quantum.Light.ProgressBar.Horizontal"> - </style> - <style name="TextAppearance.Small.CalendarViewWeekDayView"> <item name="android:textStyle">bold</item> </style> @@ -1228,6 +1195,16 @@ please see styles_device_defaults.xml. <item name="android:subtitleTextAppearance">@android:style/TextAppearance.Widget.Toolbar.Subtitle</item> <item name="android:minHeight">?android:attr/actionBarSize</item> <item name="android:titleMargins">4dp</item> + <item name="android:maxButtonHeight">56dp</item> + <item name="android:buttonGravity">top</item> + <item name="android:navigationButtonStyle">@android:style/Widget.Toolbar.Button.Navigation</item> + <item name="android:collapseIcon">?android:attr/homeAsUpIndicator</item> + </style> + + <style name="Widget.Toolbar.Button.Navigation" parent="@android:style/Widget"> + <item name="android:background">?android:attr/selectableItemBackground</item> + <item name="android:minWidth">56dp</item> + <item name="android:scaleType">center</item> </style> <style name="TextAppearance.Widget.ActionBar.Title" @@ -2425,7 +2402,6 @@ please see styles_device_defaults.xml. <item name="android:background">@android:drawable/ab_transparent_light_holo</item> <item name="android:backgroundStacked">@android:drawable/ab_stacked_transparent_light_holo</item> <item name="android:backgroundSplit">@android:drawable/ab_bottom_transparent_light_holo</item> - <item name="android:homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item> <item name="android:progressBarStyle">@android:style/Widget.Holo.Light.ProgressBar.Horizontal</item> <item name="android:indeterminateProgressStyle">@android:style/Widget.Holo.Light.ProgressBar</item> </style> diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml index 5bac1f9..0c854d3 100644 --- a/core/res/res/values/styles_micro.xml +++ b/core/res/res/values/styles_micro.xml @@ -14,6 +14,19 @@ limitations under the License. --> <resources> + <style name="Animation.Micro"/> + + <style name="Animation.Micro.Activity" parent="Animation.Holo.Activity"> + <item name="activityOpenEnterAnimation">@anim/slide_in_micro</item> + <item name="activityOpenExitAnimation">@null</item> + <item name="activityCloseEnterAnimation">@null</item> + <item name="activityCloseExitAnimation">@anim/slide_out_micro</item> + <item name="taskOpenEnterAnimation">@anim/slide_in_micro</item> + <item name="taskOpenExitAnimation">@null</item> + <item name="taskCloseEnterAnimation">@null</item> + <item name="taskCloseExitAnimation">@anim/slide_out_micro</item> + </style> + <style name="AlertDialog.Micro" parent="AlertDialog.Holo.Light"> <item name="fullDark">@null</item> <item name="topDark">@null</item> diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml index e528278..108334f 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -182,7 +182,10 @@ please see styles_device_defaults.xml. <item name="textColorLink">?attr/textColorLinkInverse</item> </style> - <style name="TextAppearance.Quantum.Large" parent="TextAppearance.Quantum.Headline" /> + <style name="TextAppearance.Quantum.Large"> + <item name="textSize">@dimen/text_size_large_quantum</item> + <item name="textColor">?attr/textColorPrimary</item> + </style> <style name="TextAppearance.Quantum.Large.Inverse"> <item name="textColor">?attr/textColorPrimaryInverse</item> @@ -191,7 +194,10 @@ please see styles_device_defaults.xml. <item name="textColorLink">?attr/textColorLinkInverse</item> </style> - <style name="TextAppearance.Quantum.Medium" parent="TextAppearance.Quantum.Body1" /> + <style name="TextAppearance.Quantum.Medium"> + <item name="textSize">@dimen/text_size_medium_quantum</item> + <item name="textColor">?attr/textColorSecondary</item> + </style> <style name="TextAppearance.Quantum.Medium.Inverse"> <item name="textColor">?attr/textColorSecondaryInverse</item> @@ -200,7 +206,10 @@ please see styles_device_defaults.xml. <item name="textColorLink">?attr/textColorLinkInverse</item> </style> - <style name="TextAppearance.Quantum.Small" parent="TextAppearance.Quantum.Caption" /> + <style name="TextAppearance.Quantum.Small"> + <item name="textSize">@dimen/text_size_small_quantum</item> + <item name="textColor">?attr/textColorTertiary</item> + </style> <style name="TextAppearance.Quantum.Small.Inverse"> <item name="textColor">?attr/textColorTertiaryInverse</item> @@ -351,6 +360,38 @@ please see styles_device_defaults.xml. <item name="textStyle">bold</item> </style> + <style name="TextAppearance.StatusBar.Quantum" /> + + <style name="TextAppearance.StatusBar.Quantum.EventContent"> + <item name="android:textColor">#90000000</item> + <item name="android:textSize">@dimen/notification_text_size</item> + </style> + + <style name="TextAppearance.StatusBar.Quantum.EventContent.Title"> + <item name="android:textColor">#DD000000</item> + <item name="android:textSize">@dimen/notification_title_text_size</item> + </style> + + <style name="TextAppearance.StatusBar.Quantum.EventContent.Line2"> + <item name="android:textSize">@dimen/notification_subtext_size</item> + </style> + + <style name="TextAppearance.StatusBar.Quantum.EventContent.Info"> + <item name="android:textSize">@dimen/notification_subtext_size</item> + </style> + + <style name="TextAppearance.StatusBar.Quantum.EventContent.Time"> + <item name="android:textSize">@dimen/notification_subtext_size</item> + </style> + + <style name="TextAppearance.StatusBar.Quantum.EventContent.Emphasis"> + <item name="android:textColor">#66000000</item> + </style> + + <style name="Widget.StatusBar.Quantum.ProgressBar" parent="Widget.Quantum.Light.ProgressBar.Horizontal"> + <item name="android:progressDrawable">@drawable/notification_quantum_media_progress</item> + </style> + <!-- Widget Styles --> <style name="Quantum"/> @@ -363,8 +404,10 @@ please see styles_device_defaults.xml. <item name="textAppearance">?attr/textAppearanceButton</item> <item name="textColor">?attr/textColorPrimary</item> <item name="minHeight">48dip</item> - <item name="minWidth">96dip</item> - <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> + <item name="minWidth">88dip</item> + + <!-- TODO: Turn this back on when we support inset drawable outlines. --> + <!-- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> --> </style> <!-- Small bordered ink button --> @@ -390,10 +433,9 @@ please see styles_device_defaults.xml. </style> <style name="Widget.Quantum.Button.Toggle"> - <item name="background">@drawable/btn_toggle_holo_dark</item> + <item name="background">@drawable/btn_toggle_quantum</item> <item name="textOn">@string/capital_on</item> <item name="textOff">@string/capital_off</item> - <item name="textAppearance">?attr/textAppearanceSmall</item> <item name="minHeight">48dip</item> </style> @@ -427,46 +469,41 @@ please see styles_device_defaults.xml. <item name="paddingEnd">8dp</item> </style> - <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView"> - <item name="drawablePadding">4dip</item> - </style> - + <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView" /> <style name="Widget.Quantum.TextSelectHandle" parent="Widget.TextSelectHandle"/> <style name="Widget.Quantum.TextSuggestionsPopupWindow" parent="Widget.TextSuggestionsPopupWindow"/> <style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/> <style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView"> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> </style> <style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/> <style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox"> - <item name="background">?attr/selectableItemBackground</item> - <item name="drawablePadding">4dip</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton"> - <item name="background">?attr/selectableItemBackground</item> - <item name="drawablePadding">4dip</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star"> <item name="button">@drawable/btn_star_quantum</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.CompoundButton.Switch"> <item name="track">@drawable/switch_track_quantum</item> - <item name="thumb">@drawable/switch_inner_quantum</item> + <item name="thumb">@drawable/switch_thumb_quantum_anim</item> <item name="splitTrack">true</item> <item name="switchTextAppearance">@style/TextAppearance.Quantum.Widget.Switch</item> <item name="textOn"></item> <item name="textOff"></item> - <item name="switchMinWidth">72dip</item> - <item name="switchPadding">16dip</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="switchMinWidth">4dip</item> + <item name="switchPadding">4dip</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.EditText" parent="Widget.EditText"/> @@ -481,7 +518,10 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.ExpandableListView.White"/> <style name="Widget.Quantum.Gallery" parent="Widget.Gallery"/> <style name="Widget.Quantum.GestureOverlayView" parent="Widget.GestureOverlayView"/> - <style name="Widget.Quantum.GridView" parent="Widget.GridView"/> + + <style name="Widget.Quantum.GridView" parent="Widget.GridView"> + <item name="android:listSelector">?attr/selectableItemBackground</item> + </style> <style name="Widget.Quantum.CalendarView" parent="Widget.CalendarView"> <item name="selectedWeekBackgroundColor">#330099FF</item> @@ -545,7 +585,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.PopupWindow" parent="Widget.PopupWindow"/> <style name="Widget.Quantum.PopupWindow.ActionMode"> - <item name="popupBackground">@color/black</item> + <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item> </style> @@ -579,13 +619,13 @@ please see styles_device_defaults.xml. <item name="indeterminateOnly">false</item> <item name="progressDrawable">@drawable/scrubber_progress_horizontal_quantum</item> <item name="indeterminateDrawable">@drawable/scrubber_progress_horizontal_quantum</item> - <item name="thumb">@drawable/scrubber_control_selector_quantum</item> + <item name="thumb">@drawable/scrubber_control_quantum_anim</item> <item name="splitTrack">true</item> <item name="focusable">true</item> <item name="paddingStart">16dip</item> <item name="paddingEnd">16dip</item> <item name="mirrorForRtl">true</item> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> </style> <style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar"> @@ -612,7 +652,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown"> <item name="background">@drawable/spinner_background_quantum</item> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> @@ -671,7 +711,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/> <style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow"> - <item name="dropDownSelector">@drawable/list_selector_quantum</item> + <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_quantum</item> <item name="popupAnimationStyle">@style/Animation.Quantum.Popup</item> <item name="dropDownVerticalOffset">0dip</item> @@ -732,7 +772,7 @@ please see styles_device_defaults.xml. <item name="background">@null</item> <item name="backgroundStacked">@null</item> <item name="backgroundSplit">@null</item> - <item name="displayOptions">useLogo|showHome|showTitle</item> + <item name="displayOptions">showTitle</item> <item name="divider">?attr/dividerVertical</item> <item name="titleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Title</item> <item name="subtitleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Subtitle</item> @@ -768,7 +808,7 @@ please see styles_device_defaults.xml. </style> <style name="Widget.Quantum.MediaRouteButton"> - <item name="background">?attr/selectableItemBackground</item> + <item name="background">?attr/selectableItemBackgroundBorderless</item> <item name="externalRouteEnabledDrawable">@drawable/ic_media_route_quantum</item> <item name="minWidth">56dp</item> <item name="minHeight">48dp</item> @@ -784,15 +824,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Light.Button.Borderless" parent="Widget.Quantum.Button.Borderless"/> <style name="Widget.Quantum.Light.Button.Borderless.Small" parent="Widget.Quantum.Button.Borderless.Small"/> <style name="Widget.Quantum.Light.Button.Inset" parent="Widget.Quantum.Button.Inset"/> - - <style name="Widget.Quantum.Light.Button.Toggle"> - <item name="background">@drawable/btn_toggle_holo_light</item> - <item name="textOn">@string/capital_on</item> - <item name="textOff">@string/capital_off</item> - <item name="textAppearance">?attr/textAppearanceSmall</item> - <item name="minHeight">48dip</item> - </style> - + <style name="Widget.Quantum.Light.Button.Toggle" parent="Widget.Quantum.Button.Toggle" /> <style name="Widget.Quantum.Light.ButtonBar" parent="Widget.Quantum.ButtonBar"/> <style name="Widget.Quantum.Light.ButtonBar.AlertDialog" parent="Widget.Quantum.ButtonBar.AlertDialog"/> @@ -846,12 +878,7 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Light.ListView" parent="Widget.Quantum.ListView"/> <style name="Widget.Quantum.Light.ListView.White" parent="Widget.Quantum.ListView.White"/> <style name="Widget.Quantum.Light.PopupWindow" parent="Widget.Quantum.PopupWindow"/> - - <style name="Widget.Quantum.Light.PopupWindow.ActionMode"> - <item name="popupBackground">@color/white</item> - <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item> - </style> - + <style name="Widget.Quantum.Light.PopupWindow.ActionMode" parent="Widget.Quantum.PopupWindow.ActionMode"/> <style name="Widget.Quantum.Light.ProgressBar" parent="Widget.Quantum.ProgressBar"/> <style name="Widget.Quantum.Light.ProgressBar.Horizontal" parent="Widget.Quantum.ProgressBar.Horizontal"/> <style name="Widget.Quantum.Light.ProgressBar.Small" parent="Widget.Quantum.ProgressBar.Small"/> @@ -861,7 +888,6 @@ please see styles_device_defaults.xml. <style name="Widget.Quantum.Light.ProgressBar.Small.Inverse" parent="Widget.Quantum.ProgressBar.Small.Inverse"/> <style name="Widget.Quantum.Light.ProgressBar.Large.Inverse" parent="Widget.Quantum.ProgressBar.Large.Inverse"/> <style name="Widget.Quantum.Light.SeekBar" parent="Widget.Quantum.SeekBar"/> - <style name="Widget.Quantum.Light.RatingBar" parent="Widget.Quantum.RatingBar" /> <style name="Widget.Quantum.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index dcff978..8b1ca31 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -69,8 +69,6 @@ <java-symbol type="id" name="edittext_container" /> <java-symbol type="id" name="enter_pin_section" /> <java-symbol type="id" name="expand_activities_button" /> - <java-symbol type="id" name="expand_button" /> - <java-symbol type="id" name="expand_button_divider" /> <java-symbol type="id" name="expires_on" /> <java-symbol type="id" name="find_next" /> <java-symbol type="id" name="find_prev" /> @@ -161,9 +159,7 @@ <java-symbol type="id" name="share" /> <java-symbol type="id" name="shortcut" /> <java-symbol type="id" name="skip_button" /> - <java-symbol type="id" name="slider_group" /> <java-symbol type="id" name="split_action_bar" /> - <java-symbol type="id" name="stream_icon" /> <java-symbol type="id" name="submit_area" /> <java-symbol type="id" name="switch_new" /> <java-symbol type="id" name="switch_old" /> @@ -181,7 +177,6 @@ <java-symbol type="id" name="topPanel" /> <java-symbol type="id" name="up" /> <java-symbol type="id" name="value" /> - <java-symbol type="id" name="visible_panel" /> <java-symbol type="id" name="websearch" /> <java-symbol type="id" name="wifi_p2p_wps_pin" /> <java-symbol type="id" name="year" /> @@ -343,7 +338,6 @@ <java-symbol type="dimen" name="search_view_preferred_width" /> <java-symbol type="dimen" name="textview_error_popup_default_width" /> <java-symbol type="dimen" name="toast_y_offset" /> - <java-symbol type="dimen" name="volume_panel_top" /> <java-symbol type="dimen" name="action_bar_stacked_max_height" /> <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" /> <java-symbol type="dimen" name="notification_text_size" /> @@ -1199,8 +1193,6 @@ <java-symbol type="layout" name="time_picker_legacy" /> <java-symbol type="layout" name="time_picker_dialog" /> <java-symbol type="layout" name="transient_notification" /> - <java-symbol type="layout" name="volume_adjust" /> - <java-symbol type="layout" name="volume_adjust_item" /> <java-symbol type="layout" name="voice_interaction_session" /> <java-symbol type="layout" name="web_text_view_dropdown" /> <java-symbol type="layout" name="webview_find" /> @@ -1294,6 +1286,10 @@ <java-symbol type="anim" name="dock_left_exit" /> <java-symbol type="anim" name="dock_right_enter" /> <java-symbol type="anim" name="dock_right_exit" /> + <java-symbol type="anim" name="voice_activity_close_exit" /> + <java-symbol type="anim" name="voice_activity_close_enter" /> + <java-symbol type="anim" name="voice_activity_open_exit" /> + <java-symbol type="anim" name="voice_activity_open_enter" /> <java-symbol type="array" name="config_hdmiCecLogicalDeviceType" /> <java-symbol type="array" name="config_keyboardTapVibePattern" /> @@ -1392,6 +1388,7 @@ <java-symbol type="string" name="android_upgrading_title" /> <java-symbol type="string" name="bugreport_title" /> <java-symbol type="string" name="bugreport_message" /> + <java-symbol type="string" name="bugreport_status" /> <java-symbol type="string" name="faceunlock_multiple_failures" /> <java-symbol type="string" name="global_action_power_off" /> <java-symbol type="string" name="global_actions_airplane_mode_off_status" /> @@ -1683,7 +1680,6 @@ <java-symbol type="anim" name="push_down_out" /> <java-symbol type="anim" name="push_up_in" /> <java-symbol type="anim" name="push_up_out" /> - <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" /> <java-symbol type="anim" name="lock_screen_behind_enter" /> <java-symbol type="bool" name="config_alwaysUseCdmaRssi" /> @@ -1870,6 +1866,7 @@ <java-symbol type="attr" name="toolbarStyle" /> <java-symbol type="attr" name="titleTextAppearance" /> <java-symbol type="attr" name="subtitleTextAppearance" /> + <java-symbol type="attr" name="windowActionBarFullscreenDecorLayout" /> <java-symbol type="drawable" name="ic_lock_bugreport" /> <java-symbol type="id" name="icon_frame" /> <java-symbol type="style" name="Animation.VolumePanel" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index aaab949..648660b 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -124,6 +124,7 @@ please see themes_device_defaults.xml. <item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item> <item name="selectableItemBackground">@android:drawable/item_background</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">?android:attr/buttonStyle</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item> @@ -192,6 +193,7 @@ please see themes_device_defaults.xml. <item name="windowDrawsSystemBarBackgrounds">false</item> <item name="statusBarColor">@android:color/black</item> <item name="navigationBarColor">@android:color/black</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item> <!-- Define these here; ContextThemeWrappers around themes that define them should always clear these values. --> @@ -1031,6 +1033,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_dark</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Button.Borderless</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item> @@ -1371,6 +1374,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.Light.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_light</item> + <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Light.Button.Borderless</item> <item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item> diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml index ebdab5b..7e0467b 100644 --- a/core/res/res/values/themes_micro.xml +++ b/core/res/res/values/themes_micro.xml @@ -19,15 +19,14 @@ <item name="alertDialogStyle">@style/AlertDialog.Micro</item> <item name="dialogTheme">@style/Theme.Micro.Dialog</item> <item name="textViewStyle">@style/Widget.Micro.TextView</item> - <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item> - <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item> + <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item> <item name="windowBackground">@color/black</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> <item name="windowIsTranslucent">true</item> <item name="windowSwipeToDismiss">true</item> - </style> + </style> <style name="Theme.Micro.Light" parent="Theme.Holo.Light.NoActionBar"> <item name="alertDialogTheme">@style/Theme.Micro.Dialog.Alert</item> @@ -35,7 +34,7 @@ <item name="dialogTheme">@style/Theme.Micro.Dialog</item> <item name="textViewStyle">@style/Widget.Micro.TextView</item> <item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item> - <item name="windowAnimationStyle">@style/Animation.SwipeDismiss</item> + <item name="windowAnimationStyle">@style/Animation.Micro.Activity</item> <item name="windowBackground">@color/white</item> <item name="windowContentOverlay">@null</item> <item name="windowIsFloating">false</item> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index bb787bb..cdbd771 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -101,6 +101,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@style/Widget.Quantum.MediaRouteButton</item> <item name="selectableItemBackground">@drawable/item_background_quantum</item> + <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item> <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item> <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item> @@ -125,8 +126,7 @@ please see themes_device_defaults.xml. <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item> <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> - <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> - + <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item> <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item> <item name="listDividerAlertDialog">@drawable/list_divider_quantum</item> @@ -162,6 +162,7 @@ please see themes_device_defaults.xml. <item name="windowActionBar">true</item> <item name="windowActionModeOverlay">false</item> <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> <item name="statusBarColor">?attr/colorPrimaryDark</item> <item name="navigationBarColor">?attr/colorPrimaryDark</item> @@ -307,7 +308,7 @@ please see themes_device_defaults.xml. <item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item> <item name="actionBarTheme">@null</item> - <item name="actionBarItemBackground">@drawable/item_background_quantum</item> + <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item> <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item> <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item> @@ -377,8 +378,8 @@ please see themes_device_defaults.xml. <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorPrimary</item> - <item name="colorControlHighlight">#30ffffff</item> + <item name="colorControlHighlight">@color/ripple_quantum_dark</item> <item name="colorButtonNormal">@color/btn_default_quantum_dark</item> </style> @@ -445,6 +446,7 @@ please see themes_device_defaults.xml. <item name="mediaRouteButtonStyle">@style/Widget.Quantum.Light.MediaRouteButton</item> <item name="selectableItemBackground">@drawable/item_background_quantum</item> + <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item> <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item> @@ -469,8 +471,7 @@ please see themes_device_defaults.xml. <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item> <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> - <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> - + <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item> <item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item> <item name="expandableListPreferredItemPaddingLeft">40dip</item> @@ -505,6 +506,7 @@ please see themes_device_defaults.xml. <item name="windowActionBar">true</item> <item name="windowActionModeOverlay">false</item> <item name="windowDrawsSystemBarBackgrounds">true</item> + <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> <item name="statusBarColor">?attr/colorPrimaryDark</item> <item name="navigationBarColor">?attr/colorPrimaryDark</item> @@ -653,7 +655,7 @@ please see themes_device_defaults.xml. <item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item> <item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item> <item name="actionBarTheme">@null</item> - <item name="actionBarItemBackground">@drawable/item_background_quantum</item> + <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item> <item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item> <item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item> @@ -666,7 +668,7 @@ please see themes_device_defaults.xml. <item name="dividerVertical">?attr/listDivider</item> <item name="dividerHorizontal">?attr/listDivider</item> <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item> - <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item> + <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item> <!-- SearchView attributes --> @@ -719,8 +721,8 @@ please see themes_device_defaults.xml. <item name="colorControlNormal">?attr/textColorSecondary</item> <item name="colorControlActivated">?attr/colorPrimary</item> - <item name="colorControlHighlight">#30000000</item> + <item name="colorControlHighlight">@color/ripple_quantum_light</item> <item name="colorButtonNormal">@color/btn_default_quantum_light</item> </style> @@ -768,7 +770,8 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item> - <item name="colorButtonNormal">@color/quantum_grey_100</item> + <item name="colorControlHighlight">@color/ripple_quantum_light</item> + <item name="colorButtonNormal">@color/btn_default_quantum_light</item> </style> <!-- Theme overlay that replaces colors with their dark versions but preserves @@ -803,7 +806,8 @@ please see themes_device_defaults.xml. <item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item> <item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item> - <item name="colorButtonNormal">@color/quantum_grey_700</item> + <item name="colorControlHighlight">@color/ripple_quantum_dark</item> + <item name="colorButtonNormal">@color/btn_default_quantum_dark</item> </style> <!-- Theme overlay that replaces the activated control color (which by default @@ -911,6 +915,22 @@ please see themes_device_defaults.xml. <item name="windowNoTitle">true</item> </style> + <!-- Quantum theme for an activity that is to be used for voice interaction. + This gives the activity a floating dialog style, to incorporate with the + system voice experience. --> + <style name="Theme.Quantum.Voice" parent="@style/Theme.Quantum.Dialog"> + <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item> + <item name="backgroundDimEnabled">false</item> + </style> + + <!-- Quantum light theme for an activity that is to be used for voice interaction. + This gives the activity a floating dialog style, to incorporate with the + system voice experience. --> + <style name="Theme.Quantum.Light.Voice" parent="@style/Theme.Quantum.Light.Dialog"> + <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item> + <item name="backgroundDimEnabled">false</item> + </style> + <!-- Default theme for quantum style input methods, which is used by the {@link android.inputmethodservice.InputMethodService} class. this inherits from Theme.Panel, but sets up IME appropriate animations @@ -927,7 +947,7 @@ please see themes_device_defaults.xml. this inherits from Theme.Panel, but sets up appropriate animations and a few custom attributes. --> <style name="Theme.Quantum.VoiceInteractionSession" parent="Theme.Quantum.Light.Panel"> - <item name="android:windowAnimationStyle">@android:style/Animation.VoiceInteractionSession</item> + <item name="windowAnimationStyle">@style/Animation.VoiceInteractionSession</item> </style> <!-- Theme for the search input bar. --> @@ -984,7 +1004,7 @@ please see themes_device_defaults.xml. <item name="colorBackgroundCacheHint">@null</item> <item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar.AlertDialog</item> - <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item> + <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item> <item name="textAppearance">@style/TextAppearance.Quantum</item> <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item> @@ -1103,7 +1123,7 @@ please see themes_device_defaults.xml. <item name="colorBackgroundCacheHint">@null</item> <item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar.AlertDialog</item> - <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item> + <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item> <item name="textAppearance">@style/TextAppearance.Quantum</item> <item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java index ec35d85..bf34f1d 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.ParcelUuid; import android.test.suitebuilder.annotation.SmallTest; @@ -26,22 +27,21 @@ import junit.framework.TestCase; /** * Unit test cases for Bluetooth LE scan filters. * <p> - * To run this test, use adb shell am instrument -e class - * 'android.bluetooth.BluetoothLeScanFilterTest' -w + * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeScanFilterTest extends TestCase { +public class ScanFilterTest extends TestCase { private static final String DEVICE_MAC = "01:02:03:04:05:AB"; private ScanResult mScanResult; - private BluetoothLeScanFilter.Builder mFilterBuilder; + private ScanFilter.Builder mFilterBuilder; @Override protected void setUp() throws Exception { byte[] scanRecord = new byte[] { 0x02, 0x01, 0x1a, // advertising flags 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // name + 0x04, 0x09, 0x50, 0x65, 0x64, // setName 0x02, 0x0A, (byte) 0xec, // tx power level 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data @@ -51,134 +51,135 @@ public class BluetoothLeScanFilterTest extends TestCase { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); mScanResult = new ScanResult(device, scanRecord, -10, 1397545200000000L); - mFilterBuilder = BluetoothLeScanFilter.newBuilder(); + mFilterBuilder = new ScanFilter.Builder(); } @SmallTest - public void testNameFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.name("Ped").build(); - assertTrue("name filter fails", filter.matches(mScanResult)); + public void testsetNameFilter() { + ScanFilter filter = mFilterBuilder.setName("Ped").build(); + assertTrue("setName filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.name("Pem").build(); - assertFalse("name filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setName("Pem").build(); + assertFalse("setName filter fails", filter.matches(mScanResult)); } @SmallTest public void testDeviceFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.macAddress(DEVICE_MAC).build(); + ScanFilter filter = mFilterBuilder.setMacAddress(DEVICE_MAC).build(); assertTrue("device filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build(); assertFalse("device filter fails", filter.matches(mScanResult)); } @SmallTest - public void testServiceUuidFilter() { - BluetoothLeScanFilter filter = mFilterBuilder.serviceUuid( + public void testsetServiceUuidFilter() { + ScanFilter filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); assertTrue("uuid filter fails", filter.matches(mScanResult)); - filter = mFilterBuilder.serviceUuid( + filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); assertFalse("uuid filter fails", filter.matches(mScanResult)); filter = mFilterBuilder - .serviceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")) - .serviceUuidMask(ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), + ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) .build(); assertTrue("uuid filter fails", filter.matches(mScanResult)); } @SmallTest - public void testServiceDataFilter() { - byte[] serviceData = new byte[] { + public void testsetServiceDataFilter() { + byte[] setServiceData = new byte[] { 0x0b, 0x11, 0x50, 0x64 }; - BluetoothLeScanFilter filter = mFilterBuilder.serviceData(serviceData).build(); + ScanFilter filter = mFilterBuilder.setServiceData(setServiceData).build(); assertTrue("service data filter fails", filter.matches(mScanResult)); byte[] nonMatchData = new byte[] { 0x0b, 0x01, 0x50, 0x64 }; - filter = mFilterBuilder.serviceData(nonMatchData).build(); + filter = mFilterBuilder.setServiceData(nonMatchData).build(); assertFalse("service data filter fails", filter.matches(mScanResult)); byte[] mask = new byte[] { (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.serviceData(nonMatchData).serviceDataMask(mask).build(); + filter = mFilterBuilder.setServiceData(nonMatchData, mask).build(); assertTrue("partial service data filter fails", filter.matches(mScanResult)); } @SmallTest public void testManufacturerSpecificData() { - byte[] manufacturerData = new byte[] { + byte[] setManufacturerData = new byte[] { (byte) 0xE0, 0x00, 0x02, 0x15 }; int manufacturerId = 224; - BluetoothLeScanFilter filter = - mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); - assertTrue("manufacturerData filter fails", filter.matches(mScanResult)); + ScanFilter filter = + mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build(); + assertTrue("setManufacturerData filter fails", filter.matches(mScanResult)); byte[] nonMatchData = new byte[] { (byte) 0xF0, 0x00, 0x02, 0x15 }; - filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData).build(); - assertFalse("manufacturerData filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build(); + assertFalse("setManufacturerData filter fails", filter.matches(mScanResult)); byte[] mask = new byte[] { (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData) - .manufacturerDataMask(mask).build(); - assertTrue("partial manufacturerData filter fails", filter.matches(mScanResult)); + filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build(); + assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult)); } @SmallTest public void testReadWriteParcel() { - BluetoothLeScanFilter filter = mFilterBuilder.build(); + ScanFilter filter = mFilterBuilder.build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.name("Ped").build(); + filter = mFilterBuilder.setName("Ped").build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build(); + filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.serviceUuid( + filter = mFilterBuilder.setServiceUuid( ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); testReadWriteParcelForFilter(filter); - filter = mFilterBuilder.serviceUuidMask( + filter = mFilterBuilder.setServiceUuid( + ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); testReadWriteParcelForFilter(filter); - byte[] serviceData = new byte[] { + byte[] setServiceData = new byte[] { 0x0b, 0x11, 0x50, 0x64 }; - filter = mFilterBuilder.serviceData(serviceData).build(); + filter = mFilterBuilder.setServiceData(setServiceData).build(); testReadWriteParcelForFilter(filter); byte[] serviceDataMask = new byte[] { (byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.serviceDataMask(serviceDataMask).build(); + filter = mFilterBuilder.setServiceData(setServiceData, serviceDataMask).build(); testReadWriteParcelForFilter(filter); byte[] manufacturerData = new byte[] { (byte) 0xE0, 0x00, 0x02, 0x15 }; int manufacturerId = 224; - filter = mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build(); + filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build(); testReadWriteParcelForFilter(filter); byte[] manufacturerDataMask = new byte[] { (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.manufacturerDataMask(manufacturerDataMask).build(); + filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData, + manufacturerDataMask).build(); testReadWriteParcelForFilter(filter); } - private void testReadWriteParcelForFilter(BluetoothLeScanFilter filter) { + private void testReadWriteParcelForFilter(ScanFilter filter) { Parcel parcel = Parcel.obtain(); filter.writeToParcel(parcel, 0); parcel.setDataPosition(0); - BluetoothLeScanFilter filterFromParcel = - BluetoothLeScanFilter.CREATOR.createFromParcel(parcel); + ScanFilter filterFromParcel = + ScanFilter.CREATOR.createFromParcel(parcel); System.out.println(filterFromParcel); assertEquals(filter, filterFromParcel); } diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java index eb6c419..cece96b 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; +import android.bluetooth.le.ScanRecord; import android.os.ParcelUuid; import android.test.suitebuilder.annotation.SmallTest; @@ -24,13 +25,13 @@ import junit.framework.TestCase; import java.util.Arrays; /** - * Unit test cases for {@link BluetoothLeAdvertiseScanData}. + * Unit test cases for {@link ScanRecord}. * <p> * To run this test, use adb shell am instrument -e class - * 'android.bluetooth.BluetoothLeAdvertiseScanDataTest' -w + * 'android.bluetooth.ScanRecordTest' -w * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeAdvertiseScanDataTest extends TestCase { +public class ScanRecordTest extends TestCase { @SmallTest public void testParser() { @@ -43,8 +44,7 @@ public class BluetoothLeAdvertiseScanDataTest extends TestCase { 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble }; - BluetoothLeAdvertiseScanData.ScanRecord data = BluetoothLeAdvertiseScanData.ScanRecord - .getParser().parseFromScanRecord(scanRecord); + ScanRecord data = ScanRecord.parseFromBytes(scanRecord); assertEquals(0x1a, data.getAdvertiseFlags()); ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java index 8064ba8..241e88f 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package android.bluetooth; +package android.bluetooth.le; -import android.bluetooth.BluetoothLeScanner.ScanResult; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; @@ -25,17 +26,18 @@ import junit.framework.TestCase; /** * Unit test cases for Bluetooth LE scans. * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothLeScannerTest' - * -w 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' + * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w + * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' */ -public class BluetoothLeScannerTest extends TestCase { +public class ScanResultTest extends TestCase { /** * Test read and write parcel of ScanResult */ @SmallTest public void testScanResultParceling() { - BluetoothDevice device = new BluetoothDevice("01:02:03:04:05:06"); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + "01:02:03:04:05:06"); byte[] scanRecord = new byte[] { 1, 2, 3 }; int rssi = -10; diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java index 5dc9ef8..433d4d2 100644 --- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java @@ -16,6 +16,9 @@ package com.android.internal.util; +import android.test.MoreAsserts; + +import java.util.Arrays; import junit.framework.TestCase; /** @@ -77,4 +80,79 @@ public class ArrayUtilsTest extends TestCase { assertFalse(ArrayUtils.containsAll(new Object[] { }, new Object[] { null })); assertFalse(ArrayUtils.containsAll(new Object[] { A }, new Object[] { null })); } + + public void testContainsInt() throws Exception { + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new int[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new int[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new int[] { }, 2)); + } + + public void testAppendInt() throws Exception { + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(null, 1)); + MoreAsserts.assertEquals(new int[] { 1 }, + ArrayUtils.appendInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1 }, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2 }, + ArrayUtils.appendInt(new int[] { 1, 2 }, 1)); + } + + public void testRemoveInt() throws Exception { + assertNull(ArrayUtils.removeInt(null, 1)); + MoreAsserts.assertEquals(new int[] { }, + ArrayUtils.removeInt(new int[] { }, 1)); + MoreAsserts.assertEquals(new int[] { 1, 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new int[] { 2, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new int[] { 1, 3, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new int[] { 1, 2, }, + ArrayUtils.removeInt(new int[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new int[] { 2, 3, 1 }, + ArrayUtils.removeInt(new int[] { 1, 2, 3, 1 }, 1)); + } + + public void testContainsLong() throws Exception { + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 1)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 2)); + assertTrue(ArrayUtils.contains(new long[] { 1, 2, 3 }, 3)); + + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 0)); + assertFalse(ArrayUtils.contains(new long[] { 1, 2, 3 }, 4)); + assertFalse(ArrayUtils.contains(new long[] { }, 2)); + } + + public void testAppendLong() throws Exception { + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(null, 1)); + MoreAsserts.assertEquals(new long[] { 1 }, + ArrayUtils.appendLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1 }, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2 }, + ArrayUtils.appendLong(new long[] { 1, 2 }, 1)); + } + + public void testRemoveLong() throws Exception { + assertNull(ArrayUtils.removeLong(null, 1)); + MoreAsserts.assertEquals(new long[] { }, + ArrayUtils.removeLong(new long[] { }, 1)); + MoreAsserts.assertEquals(new long[] { 1, 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 4)); + MoreAsserts.assertEquals(new long[] { 2, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 1)); + MoreAsserts.assertEquals(new long[] { 1, 3, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 2)); + MoreAsserts.assertEquals(new long[] { 1, 2, }, + ArrayUtils.removeLong(new long[] { 1, 2, 3}, 3)); + MoreAsserts.assertEquals(new long[] { 2, 3, 1 }, + ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1)); + } + } diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk new file mode 100644 index 0000000..d649154 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk @@ -0,0 +1,42 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + + +## The application with a minimal main dex +include $(CLEAR_VARS) + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := MultiDexLegacyAndException + +mainDexList:= \ + $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list + +LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex + +include $(BUILD_PACKAGE) + +$(mainDexList): $(full_classes_proguard_jar) | $(HOST_OUT_EXECUTABLES)/mainDexClasses + $(HOST_OUT_EXECUTABLES)/mainDexClasses $< 1>$@ + echo "com/android/multidexlegacyandexception/Test.class" >> $@ + +$(built_dex_intermediate): $(mainDexList) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml new file mode 100644 index 0000000..7fff711 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.multidexlegacyandexception" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18"/> + + <application + android:name="com.android.multidexlegacyandexception.TestApplication" + android:label="multidexlegacyandexception" + > + <activity + android:name="com.android.multidexlegacyandexception.MainActivity" + android:label="multidexlegacyandexception" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.multidexlegacyandexception" + android:label="Test for MultiDexLegacyAndException" /> +</manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml new file mode 100644 index 0000000..37eb613 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/layout/activity_main.xml @@ -0,0 +1,13 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + tools:context=".MainActivity" > + + <TextView + android:id="@+id/label_nb" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/label_nb" /> + +</RelativeLayout> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml new file mode 100644 index 0000000..e56e049 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">MultidexLegacyAndException</string> + <string name="action_settings">Settings</string> + <string name="label_nb">Here\'s the count: </string> + +</resources> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java new file mode 100644 index 0000000..d6883ec --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyByIntermediateException.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class CaughtOnlyByIntermediateException extends RuntimeException { + +} diff --git a/core/java/android/bluetooth/BluetoothLeScanner.aidl b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java index 8cecdd7..4903e01 100644 --- a/core/java/android/bluetooth/BluetoothLeScanner.aidl +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/CaughtOnlyException.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package android.bluetooth; +package com.android.multidexlegacyandexception; -parcelable BluetoothLeScanner.ScanResult; -parcelable BluetoothLeScanner.Settings; +public class CaughtOnlyException extends RuntimeException { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java new file mode 100644 index 0000000..b08a11a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ClassInSecondaryDex.java @@ -0,0 +1,84 @@ +/* + * 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.multidexlegacyandexception; + +public class ClassInSecondaryDex { + private boolean condition; + + public ClassInSecondaryDex(boolean condition) { + this.condition = condition; + } + + public void canThrow1() throws ExceptionInMainDex, ExceptionInMainDex2, + ExceptionInSecondaryDexWithSuperInMain { + if (condition) { + throw new ExceptionInMainDex(); + } + } + + public void canThrow2() throws ExceptionInSecondaryDex, ExceptionInSecondaryDex2, + ExceptionInSecondaryDexWithSuperInMain { + if (condition) { + throw new ExceptionInSecondaryDex(); + } + } + + public static void canThrowAll(Throwable toThrow) throws Throwable { + if (toThrow != null) { + throw toThrow; + } + } + + public int get1() { + try { + canThrow1(); + canThrow2(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) { + return 23; + } + } + + public int get2() { + try { + canThrow2(); + canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex e) { + return 23; + } catch (SuperExceptionInMainDex e) { + return 27; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java new file mode 100644 index 0000000..7fc3d73 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex.java @@ -0,0 +1,20 @@ +/* + * 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.multidexlegacyandexception; + +public class ExceptionInMainDex extends SuperExceptionInMainDex { +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java new file mode 100644 index 0000000..3fbeac6 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInMainDex2.java @@ -0,0 +1,20 @@ +/* + * 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.multidexlegacyandexception; + +public class ExceptionInMainDex2 extends SuperExceptionInMainDex { +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java new file mode 100644 index 0000000..9401c05 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class ExceptionInSecondaryDex extends SuperExceptionInSecondaryDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java new file mode 100644 index 0000000..d1aa103 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDex2.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class ExceptionInSecondaryDex2 extends SuperExceptionInSecondaryDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java new file mode 100644 index 0000000..9327882 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/ExceptionInSecondaryDexWithSuperInMain.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class ExceptionInSecondaryDexWithSuperInMain extends SuperExceptionInMainDex { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java new file mode 100644 index 0000000..dfdc4af --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/IntermediateClass.java @@ -0,0 +1,107 @@ +/* + * 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.multidexlegacyandexception; + +public class IntermediateClass { + + public static int get1(boolean condition) { + return new ClassInSecondaryDex(condition).get1(); + } + + public static int get2(boolean condition) { + return new ClassInSecondaryDex(condition).get2(); + } + + public static int get3(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInMainDex2 e) { + return 10; + } catch (ExceptionInSecondaryDex2 e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) { + return 23; + } + } + + public static int get4(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInSecondaryDex2 e) { + return 11; + } catch (OutOfMemoryError e) { + return 12; + } catch (ExceptionInMainDex2 e) { + return 10; + } catch (ExceptionInMainDex e) { + return 10; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInSecondaryDex e) { + } catch (SuperExceptionInMainDex e) { + } catch (CaughtOnlyByIntermediateException e) { + return 35; + } + return 39; + } + + + public static int get5(Throwable thrown) { + try { + ClassInSecondaryDex.canThrowAll(thrown); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (ExceptionInMainDex2 e) { + return 12; + } catch (ExceptionInSecondaryDex2 e) { + return 13; + } catch (OutOfMemoryError e) { + return 14; + } catch (CaughtOnlyException e) { + return 17; + } catch (ExceptionInSecondaryDexWithSuperInMain e) { + return 39; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex|CaughtOnlyByIntermediateException e) { + return 23; + } catch (Throwable e) { + return 37; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java new file mode 100644 index 0000000..dd2ce7a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MainActivity.java @@ -0,0 +1,44 @@ +/* + * 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.multidexlegacyandexception; + +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + public MainActivity() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + } + + public int get1(boolean condition) { + return IntermediateClass.get1(condition); + } + + public int get2(boolean condition) { + return IntermediateClass.get2(condition); + } + public int get3(boolean condition) { + return MiniIntermediateClass.get3(condition); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java new file mode 100644 index 0000000..5957662 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/MiniIntermediateClass.java @@ -0,0 +1,35 @@ +/* + * 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.multidexlegacyandexception; + +public class MiniIntermediateClass { + + public static int get3(boolean condition) { + ClassInSecondaryDex thrower = new ClassInSecondaryDex(condition); + try { + thrower.canThrow2(); + thrower.canThrow1(); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (ExceptionInSecondaryDex e) { + return 11; + } catch (SuperExceptionInSecondaryDex|SuperExceptionInMainDex e) { + return 23; + } + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java new file mode 100644 index 0000000..c94b30a --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInMainDex.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class SuperExceptionInMainDex extends Exception { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java new file mode 100644 index 0000000..6366fae --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/SuperExceptionInSecondaryDex.java @@ -0,0 +1,21 @@ +/* + * 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.multidexlegacyandexception; + +public class SuperExceptionInSecondaryDex extends Exception { + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java new file mode 100644 index 0000000..5e931bc --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java @@ -0,0 +1,57 @@ +/* + * 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.multidexlegacyandexception; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * Run the tests with: <code>adb shell am instrument -w + com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner +</code> + */ +public class Test extends ActivityInstrumentationTestCase2<MainActivity> { + public Test() { + super(MainActivity.class); + } + + public void testExceptionInMainDex() { + assertEquals(10, TestApplication.get(true)); + } + + public void testExceptionInSecondaryDex() { + assertEquals(10, getActivity().get1(true)); + assertEquals(11, getActivity().get2(true)); + } + + public void testExceptionInIntermediate() { + assertEquals(11, IntermediateClass.get3(true)); + assertEquals(11, MiniIntermediateClass.get3(true)); + assertEquals(11, IntermediateClass.get4(true)); + assertEquals(1, IntermediateClass.get5(null)); + assertEquals(10, IntermediateClass.get5(new ExceptionInMainDex())); + assertEquals(11, IntermediateClass.get5(new ExceptionInSecondaryDex())); + assertEquals(12, IntermediateClass.get5(new ExceptionInMainDex2())); + assertEquals(13, IntermediateClass.get5(new ExceptionInSecondaryDex2())); + assertEquals(14, IntermediateClass.get5(new OutOfMemoryError())); + assertEquals(17, IntermediateClass.get5(new CaughtOnlyException())); + assertEquals(39, IntermediateClass.get5(new ExceptionInSecondaryDexWithSuperInMain())); + assertEquals(23, IntermediateClass.get5(new SuperExceptionInSecondaryDex())); + assertEquals(23, IntermediateClass.get5(new SuperExceptionInMainDex())); + assertEquals(23, IntermediateClass.get5(new CaughtOnlyByIntermediateException())); + assertEquals(37, IntermediateClass.get5(new ArrayIndexOutOfBoundsException())); + } + +} diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java new file mode 100644 index 0000000..dece9a4 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/TestApplication.java @@ -0,0 +1,45 @@ +/* + * 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.multidexlegacyandexception; + +import android.support.multidex.MultiDexApplication; + +public class TestApplication extends MultiDexApplication { + + private static void canThrow1(boolean condition) throws ExceptionInMainDex { + if (condition) { + throw new ExceptionInMainDex(); + } + } + + + public static int get(boolean condition) { + try { + canThrow1(condition); + return 1; + } catch (ExceptionInMainDex e) { + return 10; + } catch (OutOfMemoryError e) { + return 12; + } catch (CaughtOnlyException e) { + return 17; + } catch (SuperExceptionInMainDex e) { + return 27; + } + } + +} diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java index 0f343b1..ca68e93 100644 --- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java @@ -25,8 +25,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; -import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; +import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; +import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; @@ -39,6 +40,7 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe private static final boolean DUMMY_FORCE_DEFAULT = false; private static final int DUMMY_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; + private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static InputMethodSubtype createDummySubtype(final String locale) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); @@ -64,142 +66,233 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe si.exported = true; si.nonLocalizedLabel = imeLabel; ri.serviceInfo = si; - final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); - for (String subtypeLocale : subtypeLocales) { - subtypes.add(createDummySubtype(subtypeLocale)); + List<InputMethodSubtype> subtypes = null; + if (subtypeLocales != null) { + subtypes = new ArrayList<InputMethodSubtype>(); + for (String subtypeLocale : subtypeLocales) { + subtypes.add(createDummySubtype(subtypeLocale)); + } } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); - for (int i = 0; i < subtypes.size(); ++i) { - final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + if (subtypes == null) { + items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, + NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); + } else { + for (int i = 0; i < subtypes.size(); ++i) { + final String subtypeLocale = subtypeLocales.get(i); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, + SYSTEM_LOCALE)); + } } } - private static List<ImeSubtypeListItem> createTestData() { + private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); - addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme", - Arrays.asList("en_US", "es_US", "fr"), + addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme", + addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", Arrays.asList("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); - addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme", - Arrays.asList("ja_JP"), + addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", + Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); + return items; + } + + private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { + final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); + addDummyImeSubtypeListItems(items, + "UnknownIme", "UnknownIme", + Arrays.asList("en_US", "hi"), + true /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, + "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", + Arrays.asList("en_US"), + false /* supportsSwitchingToNextInputMethod*/); + addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", + "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } + private void assertNextInputMethod(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { + InputMethodSubtype subtype = null; + if (currentItem.mSubtypeName != null) { + subtype = createDummySubtype(currentItem.mSubtypeName.toString()); + } + final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, + currentItem.mImi, subtype); + assertEquals(nextItem, nextIme); + } + + private void assertRotationOrder(final ControllerImpl controller, + final boolean onlyCurrentIme, + final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { + final int N = expectedRotationOrderOfImeSubtypeList.length; + for (int i = 0; i < N; i++) { + final int currentIndex = i; + final int nextIndex = (currentIndex + 1) % N; + final ImeSubtypeListItem currentItem = + expectedRotationOrderOfImeSubtypeList[currentIndex]; + final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; + assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem); + } + } + + private void onUserAction(final ControllerImpl controller, + final ImeSubtypeListItem subtypeListItem) { + InputMethodSubtype subtype = null; + if (subtypeListItem.mSubtypeName != null) { + subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString()); + } + controller.onUserActionLocked(subtypeListItem.mImi, subtype); + } + @SmallTest - public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = false; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(5), nextIme); - // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(6), nextIme); - // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK" - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); + public void testControllerImpl() throws Exception { + final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); + final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); + final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); + final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); + final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); + + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); + + // switching-aware loop + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + + // switching-unaware loop + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + + // test onlyCurrentIme == true + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertRotationOrder(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + + // Make sure that disabled IMEs are not accepted. + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, false /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_en_US, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledIme_hi, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSwitchingUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + disabledSubtypeUnawareIme, null); } @SmallTest - public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception { - final List<ImeSubtypeListItem> imList = createTestData(); - - final boolean ONLY_CURRENT_IME = true; - ImeSubtypeListItem currentIme; - ImeSubtypeListItem nextIme; - - // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" - currentIme = imList.get(0); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(1), nextIme); - // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" - currentIme = imList.get(1); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(2), nextIme); - // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US" - currentIme = imList.get(2); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(0), nextIme); - - // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" - currentIme = imList.get(3); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(4), nextIme); - // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK" - currentIme = imList.get(4); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertEquals(imList.get(3), nextIme); - - // "switchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(5); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); - - // "nonSwitchAwareJapaneseIme/ja_JP" -> null - currentIme = imList.get(6); - nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( - imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( - currentIme.mSubtypeName.toString())); - assertNull(nextIme); + public void testControllerImplWithUserAction() throws Exception { + final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); + final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); + final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); + final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); + final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); + final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); + final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); + final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); + + final ControllerImpl controller = ControllerImpl.createFrom( + null /* currentInstance */, enabledItems); + + // === switching-aware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for latinIme_fr again. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); + // Then notify that a user did something for japaneseIme_ja_JP. + onUserAction(controller, latinIme_fr); + assertRotationOrder(controller, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + // Check onlyCurrentIme == true. + assertNextInputMethod(controller, true /* onlyCurrentIme */, + japaneseIme_ja_JP, null); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_fr, latinIme_en_US); + assertRotationOrder(controller, true /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + + // === switching-unaware loop === + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchingUnawarelatinIme_hi); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // User action should be ignored for switching unaware IMEs. + onUserAction(controller, switchUnawareJapaneseIme_ja_JP); + assertRotationOrder(controller, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + // Check onlyCurrentIme == true. + assertRotationOrder(controller, true /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + subtypeUnawareIme, null); + assertNextInputMethod(controller, true /* onlyCurrentIme */, + switchUnawareJapaneseIme_ja_JP, null); + + // Rotation order should be preserved when created with the same subtype list. + final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); + final ControllerImpl newController = ControllerImpl.createFrom(controller, + sameEnabledItems); + assertRotationOrder(newController, false /* onlyCurrentIme */, + japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); + assertRotationOrder(newController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, + switchUnawareJapaneseIme_ja_JP); + + // Rotation order should be initialized when created with a different subtype list. + final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( + latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK, + switchUnawareJapaneseIme_ja_JP); + final ControllerImpl anotherController = ControllerImpl.createFrom(controller, + differentEnabledItems); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + latinIme_en_US, latinIme_fr); + assertRotationOrder(anotherController, false /* onlyCurrentIme */, + switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); } - } +} |