diff options
Diffstat (limited to 'core/java/android')
57 files changed, 3295 insertions, 148 deletions
diff --git a/core/java/android/annotation/TransitionRes.java b/core/java/android/annotation/TransitionRes.java new file mode 100644 index 0000000..06bac74 --- /dev/null +++ b/core/java/android/annotation/TransitionRes.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Denotes that an integer parameter, field or method return value is expected + * to be a transition resource reference. + * + * {@hide} + */ +@Documented +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface TransitionRes { +} diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 997f69d..1484af8 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2440,6 +2440,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case SET_VOICE_KEEP_AWAKE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface( + data.readStrongBinder()); + boolean keepAwake = data.readInt() != 0; + setVoiceKeepAwake(session, keepAwake); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -5658,5 +5668,19 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + @Override + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(session.asBinder()); + data.writeInt(keepAwake ? 1 : 0); + mRemote.transact(SET_VOICE_KEEP_AWAKE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index f35e746..9f81670 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1662,7 +1662,7 @@ final class ApplicationPackageManager extends PackageManager { int flags) { try { mPM.addCrossProfileIntentFilter(filter, mContext.getOpPackageName(), - mContext.getUserId(), sourceUserId, targetUserId, flags); + sourceUserId, targetUserId, flags); } catch (RemoteException e) { // Should never happen! } @@ -1674,8 +1674,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public void clearCrossProfileIntentFilters(int sourceUserId) { try { - mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName(), - mContext.getUserId()); + mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName()); } catch (RemoteException e) { // Should never happen! } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3dcbdd2..d794aa3 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -485,6 +485,9 @@ public interface IActivityManager extends IInterface { public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException; public void dumpHeapFinished(String path) throws RemoteException; + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -818,4 +821,5 @@ public interface IActivityManager extends IInterface { int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286; int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287; int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288; + int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289; } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 8abe223..79797c9 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -27,6 +27,7 @@ import android.content.res.ResourcesKey; import android.hardware.display.DisplayManagerGlobal; import android.util.ArrayMap; import android.util.DisplayMetrics; +import android.util.Log; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -164,7 +165,7 @@ public class ResourcesManager { WeakReference<Resources> wr = mActiveResources.get(key); r = wr != null ? wr.get() : null; - //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); + //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); if (r != null && r.getAssets().isUpToDate()) { if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir + ": appScale=" + r.getCompatibilityInfo().applicationScale @@ -174,7 +175,7 @@ public class ResourcesManager { } //if (r != null) { - // Slog.w(TAG, "Throwing away out-of-date resources!!!! " + // Log.w(TAG, "Throwing away out-of-date resources!!!! " // + r + " " + resDir); //} @@ -204,14 +205,18 @@ public class ResourcesManager { if (libDirs != null) { for (String libDir : libDirs) { - if (assets.addAssetPath(libDir) == 0) { - Slog.w(TAG, "Asset path '" + libDir + - "' does not exist or contains no resources."); + if (libDir.endsWith(".apk")) { + // Avoid opening files we know do not have resources, + // like code-only .jar files. + if (assets.addAssetPath(libDir) == 0) { + Log.w(TAG, "Asset path '" + libDir + + "' does not exist or contains no resources."); + } } } } - //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); + //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics dm = getDisplayMetricsLocked(displayId); Configuration config; final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 993f416..fd7bae7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -49,6 +49,7 @@ import android.hardware.hdmi.IHdmiControlService; import android.hardware.input.InputManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; +import android.hardware.radio.RadioManager; import android.location.CountryDetector; import android.location.ICountryDetector; import android.location.ILocationManager; @@ -683,6 +684,13 @@ final class SystemServiceRegistry { IBinder b = ServiceManager.getService(Context.MIDI_SERVICE); return new MidiManager(ctx, IMidiManager.Stub.asInterface(b)); }}); + + registerService(Context.RADIO_SERVICE, RadioManager.class, + new CachedServiceFetcher<RadioManager>() { + @Override + public RadioManager createService(ContextImpl ctx) { + return new RadioManager(ctx); + }}); } /** diff --git a/core/java/android/app/VoiceInteractor.aidl b/core/java/android/app/VoiceInteractor.aidl new file mode 100644 index 0000000..40a4a0e --- /dev/null +++ b/core/java/android/app/VoiceInteractor.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015, 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; + +parcelable VoiceInteractor.PickOptionRequest.Option; diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java index 7b84cb4..da7bb05 100644 --- a/core/java/android/app/VoiceInteractor.java +++ b/core/java/android/app/VoiceInteractor.java @@ -21,6 +21,8 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -69,6 +71,7 @@ public class VoiceInteractor { public void executeMessage(Message msg) { SomeArgs args = (SomeArgs)msg.obj; Request request; + boolean complete; switch (msg.what) { case MSG_CONFIRMATION_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, true); @@ -81,13 +84,28 @@ public class VoiceInteractor { request.clear(); } break; + case MSG_PICK_OPTION_RESULT: + complete = msg.arg1 != 0; + request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); + if (DEBUG) Log.d(TAG, "onPickOptionResult: req=" + + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + + " finished=" + complete + " selection=" + args.arg2 + + " result=" + args.arg3); + if (request != null) { + ((PickOptionRequest)request).onPickOptionResult(complete, + (PickOptionRequest.Option[]) args.arg2, (Bundle) args.arg3); + if (complete) { + request.clear(); + } + } + break; case MSG_COMPLETE_VOICE_RESULT: request = pullRequest((IVoiceInteractorRequest)args.arg1, true); if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + " result=" + args.arg1); if (request != null) { - ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2); + ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg1); request.clear(); } break; @@ -95,21 +113,22 @@ public class VoiceInteractor { request = pullRequest((IVoiceInteractorRequest)args.arg1, true); if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request - + " result=" + args.arg1); + + " result=" + args.arg2); if (request != null) { ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2); request.clear(); } break; case MSG_COMMAND_RESULT: - request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0); + complete = msg.arg1 != 0; + request = pullRequest((IVoiceInteractorRequest)args.arg1, complete); if (DEBUG) Log.d(TAG, "onCommandResult: req=" + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request + " completed=" + msg.arg1 + " result=" + args.arg2); if (request != null) { ((CommandRequest)request).onCommandResult(msg.arg1 != 0, (Bundle) args.arg2); - if (msg.arg1 != 0) { + if (complete) { request.clear(); } } @@ -129,10 +148,17 @@ public class VoiceInteractor { final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() { @Override - public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed, + public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished, Bundle result) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO( - MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result)); + MSG_CONFIRMATION_RESULT, finished ? 1 : 0, request, result)); + } + + @Override + public void deliverPickOptionResult(IVoiceInteractorRequest request, + boolean finished, PickOptionRequest.Option[] options, Bundle result) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO( + MSG_PICK_OPTION_RESULT, finished ? 1 : 0, request, options, result)); } @Override @@ -164,17 +190,22 @@ public class VoiceInteractor { final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>(); static final int MSG_CONFIRMATION_RESULT = 1; - static final int MSG_COMPLETE_VOICE_RESULT = 2; - static final int MSG_ABORT_VOICE_RESULT = 3; - static final int MSG_COMMAND_RESULT = 4; - static final int MSG_CANCEL_RESULT = 5; + static final int MSG_PICK_OPTION_RESULT = 2; + static final int MSG_COMPLETE_VOICE_RESULT = 3; + static final int MSG_ABORT_VOICE_RESULT = 4; + static final int MSG_COMMAND_RESULT = 5; + static final int MSG_CANCEL_RESULT = 6; + /** + * Base class for voice interaction requests that can be submitted to the interactor. + * Do not instantiate this directly -- instead, use the appropriate subclass. + */ public static abstract class Request { IVoiceInteractorRequest mRequestInterface; Context mContext; Activity mActivity; - public Request() { + Request() { } public void cancel() { @@ -212,22 +243,25 @@ public class VoiceInteractor { String packageName, IVoiceInteractorCallback callback) throws RemoteException; } + /** + * Confirms an operation with the user via the trusted system + * VoiceInteractionService. This allows an Activity to complete an unsafe operation that + * would require the user to touch the screen when voice interaction mode is not enabled. + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or + * {@link #onCancel()}. + * + * <p>In some cases this may be a simple yes / no confirmation or the confirmation could + * include context information about how the action will be completed + * (e.g. booking a cab might include details about how long until the cab arrives) + * so the user can give a confirmation. + */ public static class ConfirmationRequest extends Request { final CharSequence mPrompt; final Bundle mExtras; /** - * Confirms an operation with the user via the trusted system - * VoiceInteractionService. This allows an Activity to complete an unsafe operation that - * would require the user to touch the screen when voice interaction mode is not enabled. - * The result of the confirmation will be returned through an asynchronous call to - * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or - * {@link #onCancel()}. - * - * <p>In some cases this may be a simple yes / no confirmation or the confirmation could - * include context information about how the action will be completed - * (e.g. booking a cab might include details about how long until the cab arrives) - * so the user can give a confirmation. + * Create a new confirmation request. * @param prompt Optional confirmation text to read to the user as the action being * confirmed. * @param extras Additional optional information. @@ -246,19 +280,155 @@ public class VoiceInteractor { } } + /** + * Select a single option from multiple potential options with the user via the trusted system + * VoiceInteractionService. Typically, the application would present this visually as + * a list view to allow selecting the option by touch. + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onPickOptionResult} or {@link #onCancel()}. + */ + public static class PickOptionRequest extends Request { + final CharSequence mPrompt; + final Option[] mOptions; + final Bundle mExtras; + + /** + * Represents a single option that the user may select using their voice. + */ + public static final class Option implements Parcelable { + final CharSequence mLabel; + ArrayList<CharSequence> mSynonyms; + Bundle mExtras; + + /** + * Creates an option that a user can select with their voice by matching the label + * or one of several synonyms. + * @param label The label that will both be matched against what the user speaks + * and displayed visually. + */ + public Option(CharSequence label) { + mLabel = label; + } + + /** + * Add a synonym term to the option to indicate an alternative way the content + * may be matched. + * @param synonym The synonym that will be matched against what the user speaks, + * but not displayed. + */ + public Option addSynonym(CharSequence synonym) { + if (mSynonyms == null) { + mSynonyms = new ArrayList<>(); + } + mSynonyms.add(synonym); + return this; + } + + public CharSequence getLabel() { + return mLabel; + } + + public int countSynonyms() { + return mSynonyms != null ? mSynonyms.size() : 0; + } + + public CharSequence getSynonymAt(int index) { + return mSynonyms != null ? mSynonyms.get(index) : null; + } + + /** + * Set optional extra information associated with this option. Note that this + * method takes ownership of the supplied extras Bundle. + */ + public void setExtras(Bundle extras) { + mExtras = extras; + } + + /** + * Return any optional extras information associated with this option, or null + * if there is none. Note that this method returns a reference to the actual + * extras Bundle in the option, so modifications to it will directly modify the + * extras in the option. + */ + public Bundle getExtras() { + return mExtras; + } + + Option(Parcel in) { + mLabel = in.readCharSequence(); + mSynonyms = in.readCharSequenceList(); + mExtras = in.readBundle(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeCharSequence(mLabel); + dest.writeCharSequenceList(mSynonyms); + dest.writeBundle(mExtras); + } + + public static final Parcelable.Creator<Option> CREATOR + = new Parcelable.Creator<Option>() { + public Option createFromParcel(Parcel in) { + return new Option(in); + } + + public Option[] newArray(int size) { + return new Option[size]; + } + }; + }; + + /** + * Create a new pick option request. + * @param prompt Optional question to be spoken to the user via text to speech. + * @param options The set of {@link Option}s the user is selecting from. + * @param extras Additional optional information. + */ + public PickOptionRequest(CharSequence prompt, Option[] options, Bundle extras) { + mPrompt = prompt; + mOptions = options; + mExtras = extras; + } + + /** + * Called when a single option is confirmed or narrowed to one of several options. + * @param finished True if the voice interaction has finished making a selection, in + * which case {@code selections} contains the final result. If false, this request is + * still active and you will continue to get calls on it. + * @param selections Either a single {@link Option} or one of several {@link Option}s the + * user has narrowed the choices down to. + * @param result Additional optional information. + */ + public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { + } + + IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName, + IVoiceInteractorCallback callback) throws RemoteException { + return interactor.startPickOption(packageName, callback, mPrompt, mOptions, mExtras); + } + } + + /** + * Reports that the current interaction was successfully completed with voice, so the + * application can report the final status to the user. 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 or finish. 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. + */ public static class CompleteVoiceRequest extends Request { final CharSequence mMessage; final Bundle mExtras; /** - * Reports that the current interaction was successfully completed with voice, so the - * application can report the final status to the user. 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 or finish. 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. - * + * Create a new completed voice interaction request. * @param message Optional message to tell user about the completion status of the task. * @param extras Additional optional information. */ @@ -276,21 +446,23 @@ public class VoiceInteractor { } } + /** + * 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. + */ 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. - * + * Create a new voice abort request. * @param message Optional message to tell user about not being able to complete * the interaction with voice. * @param extras Additional optional information. @@ -309,25 +481,27 @@ public class VoiceInteractor { } } + /** + * Execute an extended command using the trusted system VoiceInteractionService. + * This allows an Activity to request additional information from the user needed to + * complete an action (e.g. booking a table might have several possible times that the + * user could select from or an app might need the user to agree to a terms of service). + * The result of the confirmation will be returned through an asynchronous call to + * either {@link #onCommandResult(boolean, android.os.Bundle)} or + * {@link #onCancel()}. + * + * <p>The command is a string that describes the generic operation to be performed. + * The command will determine how the properties in extras are interpreted and the set of + * available commands is expected to grow over time. An example might be + * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of + * airline check-in. (This is not an actual working example.) + */ public static class CommandRequest extends Request { final String mCommand; final Bundle mArgs; /** - * Execute a command using the trusted system VoiceInteractionService. - * This allows an Activity to request additional information from the user needed to - * complete an action (e.g. booking a table might have several possible times that the - * user could select from or an app might need the user to agree to a terms of service). - * The result of the confirmation will be returned through an asynchronous call to - * either {@link #onCommandResult(boolean, android.os.Bundle)} or - * {@link #onCancel()}. - * - * <p>The command is a string that describes the generic operation to be performed. - * The command will determine how the properties in extras are interpreted and the set of - * available commands is expected to grow over time. An example might be - * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of - * airline check-in. (This is not an actual working example.) - * + * Create a new generic command request. * @param command The desired command to perform. * @param args Additional arguments to control execution of the command. */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 245db06..a659acb 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1766,7 +1766,7 @@ public class DevicePolicyManager { public static final int ENCRYPTION_STATUS_INACTIVE = 1; /** - * Result code for {@link #setStorageEncryption} and {@link #getStorageEncryptionStatus}: + * Result code for {@link #getStorageEncryptionStatus}: * indicating that encryption is not currently active, but is currently * being activated. This is only reported by devices that support * encryption of data and only when the storage is currently @@ -1782,6 +1782,13 @@ public class DevicePolicyManager { public static final int ENCRYPTION_STATUS_ACTIVE = 3; /** + * Result code for {@link #getStorageEncryptionStatus}: + * indicating that encryption is active, but an encryption key has not + * been set by the user. + */ + public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; + + /** * Activity action: begin the process of encrypting data on the device. This activity should * be launched after using {@link #setStorageEncryption} to request encryption be activated. * After resuming from this activity, use {@link #getStorageEncryption} @@ -1905,12 +1912,15 @@ public class DevicePolicyManager { * storage system does not support encryption. If the * result is {@link #ENCRYPTION_STATUS_INACTIVE}, use {@link * #ACTION_START_ENCRYPTION} to begin the process of encrypting or decrypting the - * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or + * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, the + * storage system has enabled encryption but no password is set so further action + * may be required. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or * {@link #ENCRYPTION_STATUS_ACTIVE}, no further action is required. * * @return current status of encryption. The value will be one of * {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE}, - * {@link #ENCRYPTION_STATUS_ACTIVATING}, or{@link #ENCRYPTION_STATUS_ACTIVE}. + * {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, + * or {@link #ENCRYPTION_STATUS_ACTIVE}. */ public int getStorageEncryptionStatus() { return getStorageEncryptionStatus(UserHandle.myUserId()); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 61cdec3..80b5e0b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2200,6 +2200,7 @@ public abstract class Context { //@hide: PERSISTENT_DATA_BLOCK_SERVICE, MEDIA_PROJECTION_SERVICE, MIDI_SERVICE, + RADIO_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3026,6 +3027,17 @@ public abstract class Context { */ public static final String MIDI_SERVICE = "midi"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service. + * + * @see #getSystemService + * @hide + */ + public static final String RADIO_SERVICE = "radio"; + + /** * 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/Intent.java b/core/java/android/content/Intent.java index 2ed8c44..f685475 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2790,6 +2790,31 @@ public class Intent implements Parcelable, Cloneable { /** {@hide} */ public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; + /** + * Broadcast action: report that a settings element is being restored from backup. The intent + * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting, + * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE + * is the value of that settings entry prior to the restore operation. All of these values are + * represented as strings. + * + * <p>This broadcast is sent only for settings provider entries known to require special handling + * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within + * the provider's backup agent implementation. + * + * @see #EXTRA_SETTING_NAME + * @see #EXTRA_SETTING_PREVIOUS_VALUE + * @see #EXTRA_SETTING_NEW_VALUE + * {@hide} + */ + public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED"; + + /** {@hide} */ + public static final String EXTRA_SETTING_NAME = "setting_name"; + /** {@hide} */ + public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value"; + /** {@hide} */ + public static final String EXTRA_SETTING_NEW_VALUE = "new_value"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index b518498..3e5d362 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -261,9 +261,9 @@ interface IPackageManager { void clearPackagePersistentPreferredActivities(String packageName, int userId); void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage, - int ownerUserId, int sourceUserId, int targetUserId, int flags); + int sourceUserId, int targetUserId, int flags); - void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage, int ownerUserId); + void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage); /** * Report the set of 'Home' activity candidates, plus (if any) which of them diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index faa782a..b513379 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1013,10 +1013,10 @@ public final class CameraManager { if (DEBUG) { Log.v(TAG, String.format( - "Device status was previously available (%d), " + - " and is now again available (%d)" + + "Device status was previously available (%b), " + + " and is now again available (%b)" + "so no new client visible update will be sent", - isAvailable(status), isAvailable(status))); + isAvailable(oldStatus), isAvailable(status))); } return; } diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java index 33d539c..f16d650 100644 --- a/core/java/android/hardware/camera2/DngCreator.java +++ b/core/java/android/hardware/camera2/DngCreator.java @@ -453,7 +453,7 @@ public final class DngCreator implements AutoCloseable { height + ") passed to write"); } long capacity = pixels.capacity(); - long totalSize = rowStride * height + offset; + long totalSize = ((long) rowStride) * height + offset; if (capacity < totalSize) { throw new IllegalArgumentException("Image size " + capacity + " is too small (must be larger than " + totalSize + ")"); diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index 367a078..b5a019d 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -292,6 +292,10 @@ public class LegacyCameraDevice implements AutoCloseable { Log.e(TAG, "configureOutputs - null outputs are not allowed"); return BAD_VALUE; } + if (!output.isValid()) { + Log.e(TAG, "configureOutputs - invalid output surfaces are not allowed"); + return BAD_VALUE; + } StreamConfigurationMap streamConfigurations = mStaticCharacteristics. get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); @@ -522,7 +526,7 @@ public class LegacyCameraDevice implements AutoCloseable { * @return the width and height of the surface * * @throws NullPointerException if the {@code surface} was {@code null} - * @throws IllegalStateException if the {@code surface} was invalid + * @throws BufferQueueAbandonedException if the {@code surface} was invalid */ public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException { checkNotNull(surface); diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java index 7e0c01b..4b7cfbf 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java +++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java @@ -60,7 +60,7 @@ public class LegacyExceptionUtils { case CameraBinderDecorator.NO_ERROR: { return CameraBinderDecorator.NO_ERROR; } - case CameraBinderDecorator.ENODEV: { + case CameraBinderDecorator.BAD_VALUE: { throw new BufferQueueAbandonedException(); } } diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index ff74c59..691798f 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -498,6 +498,10 @@ public class RequestThreadManager { return; } for(Surface s : surfaces) { + if (s == null || !s.isValid()) { + Log.w(TAG, "Jpeg surface is invalid, skipping..."); + continue; + } try { LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB); } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java index 6e5d064..4696b2a 100644 --- a/core/java/android/hardware/location/GeofenceHardwareImpl.java +++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java @@ -436,7 +436,7 @@ public final class GeofenceHardwareImpl { int monitoringType, int sourcesUsed) { if(location == null) { - Log.e(TAG, String.format("Invalid Geofence Transition: location=%p", location)); + Log.e(TAG, String.format("Invalid Geofence Transition: location=null")); return; } if(DEBUG) { diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java new file mode 100644 index 0000000..32930a7 --- /dev/null +++ b/core/java/android/hardware/radio/RadioManager.java @@ -0,0 +1,1308 @@ +/** + * Copyright (C) 2015 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.radio; + +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import java.util.List; +import java.util.Arrays; + +/** + * The RadioManager class allows to control a broadcast radio tuner present on the device. + * It provides data structures and methods to query for available radio modules, list their + * properties and open an interface to control tuning operations and receive callbacks when + * asynchronous operations complete or events occur. + * @hide + */ +@SystemApi +public class RadioManager { + + /** Method return status: successful operation */ + public static final int STATUS_OK = 0; + /** Method return status: unspecified error */ + public static final int STATUS_ERROR = Integer.MIN_VALUE; + /** Method return status: permission denied */ + public static final int STATUS_PERMISSION_DENIED = -1; + /** Method return status: initialization failure */ + public static final int STATUS_NO_INIT = -19; + /** Method return status: invalid argument provided */ + public static final int STATUS_BAD_VALUE = -22; + /** Method return status: cannot reach service */ + public static final int STATUS_DEAD_OBJECT = -32; + /** Method return status: invalid or out of sequence operation */ + public static final int STATUS_INVALID_OPERATION = -38; + /** Method return status: time out before operation completion */ + public static final int STATUS_TIMED_OUT = -110; + + + // keep in sync with radio_class_t in /system/core/incluse/system/radio.h + /** Radio module class supporting FM (including HD radio) and AM */ + public static final int CLASS_AM_FM = 0; + /** Radio module class supporting satellite radio */ + public static final int CLASS_SAT = 1; + /** Radio module class supporting Digital terrestrial radio */ + public static final int CLASS_DT = 2; + + // keep in sync with radio_band_t in /system/core/incluse/system/radio.h + /** AM radio band (LW/MW/SW). + * @see BandDescriptor */ + public static final int BAND_AM = 0; + /** FM radio band. + * @see BandDescriptor */ + public static final int BAND_FM = 1; + /** FM HD radio or DRM band. + * @see BandDescriptor */ + public static final int BAND_FM_HD = 2; + /** AM HD radio or DRM band. + * @see BandDescriptor */ + public static final int BAND_AM_HD = 3; + + // keep in sync with radio_region_t in /system/core/incluse/system/radio.h + /** Africa, Europe. + * @see BandDescriptor */ + public static final int REGION_ITU_1 = 0; + /** Americas. + * @see BandDescriptor */ + public static final int REGION_ITU_2 = 1; + /** Russia. + * @see BandDescriptor */ + public static final int REGION_OIRT = 2; + /** Japan. + * @see BandDescriptor */ + public static final int REGION_JAPAN = 3; + /** Korea. + * @see BandDescriptor */ + public static final int REGION_KOREA = 4; + + /***************************************************************************** + * Lists properties, options and radio bands supported by a given broadcast radio module. + * Each module has a unique ID used to address it when calling RadioManager APIs. + * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method. + ****************************************************************************/ + public static class ModuleProperties implements Parcelable { + + private final int mId; + private final int mClassId; + private final String mImplementor; + private final String mProduct; + private final String mVersion; + private final String mSerial; + private final int mNumTuners; + private final int mNumAudioSources; + private final boolean mIsCaptureSupported; + private final BandDescriptor[] mBands; + + ModuleProperties(int id, int classId, String implementor, String product, String version, + String serial, int numTuners, int numAudioSources, boolean isCaptureSupported, + BandDescriptor[] bands) { + mId = id; + mClassId = classId; + mImplementor = implementor; + mProduct = product; + mVersion = version; + mSerial = serial; + mNumTuners = numTuners; + mNumAudioSources = numAudioSources; + mIsCaptureSupported = isCaptureSupported; + mBands = bands; + } + + + /** Unique module identifier provided by the native service. + * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}. + * @return the radio module unique identifier. + */ + public int getId() { + return mId; + } + + /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT} + * @return the radio module class identifier. + */ + public int getClassId() { + return mClassId; + } + + /** Human readable broadcast radio module implementor + * @return the name of the radio module implementator. + */ + public String getImplementor() { + return mImplementor; + } + + /** Human readable broadcast radio module product name + * @return the radio module product name. + */ + public String getProduct() { + return mProduct; + } + + /** Human readable broadcast radio module version number + * @return the radio module version. + */ + public String getVersion() { + return mVersion; + } + + /** Radio module serial number. + * Can be used for subscription services. + * @return the radio module serial number. + */ + public String getSerial() { + return mSerial; + } + + /** Number of tuners available. + * This is the number of tuners that can be open simultaneously. + * @return the number of tuners supported. + */ + public int getNumTuners() { + return mNumTuners; + } + + /** Number tuner audio sources available. Must be less or equal to getNumTuners(). + * When more than one tuner is supported, one is usually for playback and has one + * associated audio source and the other is for pre scanning and building a + * program list. + * @return the number of audio sources available. + */ + public int getNumAudioSources() { + return mNumAudioSources; + } + + /** {@code true} if audio capture is possible from radio tuner output. + * This indicates if routing to audio devices not connected to the same HAL as the FM radio + * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented. + * @return {@code true} if audio capture is possible, {@code false} otherwise. + */ + public boolean isCaptureSupported() { + return mIsCaptureSupported; + } + + /** List of descriptors for all bands supported by this module. + * @return an array of {@link BandDescriptor}. + */ + public BandDescriptor[] getBands() { + return mBands; + } + + private ModuleProperties(Parcel in) { + mId = in.readInt(); + mClassId = in.readInt(); + mImplementor = in.readString(); + mProduct = in.readString(); + mVersion = in.readString(); + mSerial = in.readString(); + mNumTuners = in.readInt(); + mNumAudioSources = in.readInt(); + mIsCaptureSupported = in.readInt() == 1; + Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader()); + mBands = new BandDescriptor[tmp.length]; + for (int i = 0; i < tmp.length; i++) { + mBands[i] = (BandDescriptor) tmp[i]; + } + } + + public static final Parcelable.Creator<ModuleProperties> CREATOR + = new Parcelable.Creator<ModuleProperties>() { + public ModuleProperties createFromParcel(Parcel in) { + return new ModuleProperties(in); + } + + public ModuleProperties[] newArray(int size) { + return new ModuleProperties[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeInt(mClassId); + dest.writeString(mImplementor); + dest.writeString(mProduct); + dest.writeString(mVersion); + dest.writeString(mSerial); + dest.writeInt(mNumTuners); + dest.writeInt(mNumAudioSources); + dest.writeInt(mIsCaptureSupported ? 1 : 0); + dest.writeParcelableArray(mBands, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "ModuleProperties [mId=" + mId + ", mClassId=" + mClassId + + ", mImplementor=" + mImplementor + ", mProduct=" + mProduct + + ", mVersion=" + mVersion + ", mSerial=" + mSerial + + ", mNumTuners=" + mNumTuners + + ", mNumAudioSources=" + mNumAudioSources + + ", mIsCaptureSupported=" + mIsCaptureSupported + + ", mBands=" + Arrays.toString(mBands) + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mId; + result = prime * result + mClassId; + result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode()); + result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode()); + result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); + result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode()); + result = prime * result + mNumTuners; + result = prime * result + mNumAudioSources; + result = prime * result + (mIsCaptureSupported ? 1 : 0); + result = prime * result + Arrays.hashCode(mBands); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ModuleProperties)) + return false; + ModuleProperties other = (ModuleProperties) obj; + if (mId != other.getId()) + return false; + if (mClassId != other.getClassId()) + return false; + if (mImplementor == null) { + if (other.getImplementor() != null) + return false; + } else if (!mImplementor.equals(other.getImplementor())) + return false; + if (mProduct == null) { + if (other.getProduct() != null) + return false; + } else if (!mProduct.equals(other.getProduct())) + return false; + if (mVersion == null) { + if (other.getVersion() != null) + return false; + } else if (!mVersion.equals(other.getVersion())) + return false; + if (mSerial == null) { + if (other.getSerial() != null) + return false; + } else if (!mSerial.equals(other.getSerial())) + return false; + if (mNumTuners != other.getNumTuners()) + return false; + if (mNumAudioSources != other.getNumAudioSources()) + return false; + if (mIsCaptureSupported != other.isCaptureSupported()) + return false; + if (!Arrays.equals(mBands, other.getBands())) + return false; + return true; + } + } + + /** Radio band descriptor: an element in ModuleProperties bands array. + * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */ + public static class BandDescriptor implements Parcelable { + + private final int mRegion; + private final int mType; + private final int mLowerLimit; + private final int mUpperLimit; + private final int mSpacing; + + BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) { + mRegion = region; + mType = type; + mLowerLimit = lowerLimit; + mUpperLimit = upperLimit; + mSpacing = spacing; + } + + /** Region this band applies to. E.g. {@link #REGION_ITU_1} + * @return the region this band is associated to. + */ + public int getRegion() { + return mRegion; + } + /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to: + * <ul> + * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li> + * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li> + * </ul> + * @return the band type. + */ + public int getType() { + return mType; + } + /** Lower band limit expressed in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the lower band limit. + */ + public int getLowerLimit() { + return mLowerLimit; + } + /** Upper band limit expressed in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the upper band limit. + */ + public int getUpperLimit() { + return mUpperLimit; + } + /** Channel spacing in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the channel spacing. + */ + public int getSpacing() { + return mSpacing; + } + + private BandDescriptor(Parcel in) { + mRegion = in.readInt(); + mType = in.readInt(); + mLowerLimit = in.readInt(); + mUpperLimit = in.readInt(); + mSpacing = in.readInt(); + } + + public static final Parcelable.Creator<BandDescriptor> CREATOR + = new Parcelable.Creator<BandDescriptor>() { + public BandDescriptor createFromParcel(Parcel in) { + return new BandDescriptor(in); + } + + public BandDescriptor[] newArray(int size) { + return new BandDescriptor[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRegion); + dest.writeInt(mType); + dest.writeInt(mLowerLimit); + dest.writeInt(mUpperLimit); + dest.writeInt(mSpacing); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit=" + + mLowerLimit + ", mUpperLimit=" + mUpperLimit + ", mSpacing=" + mSpacing + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mRegion; + result = prime * result + mType; + result = prime * result + mLowerLimit; + result = prime * result + mUpperLimit; + result = prime * result + mSpacing; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof BandDescriptor)) + return false; + BandDescriptor other = (BandDescriptor) obj; + if (mRegion != other.getRegion()) + return false; + if (mType != other.getType()) + return false; + if (mLowerLimit != other.getLowerLimit()) + return false; + if (mUpperLimit != other.getUpperLimit()) + return false; + if (mSpacing != other.getSpacing()) + return false; + return true; + } + } + + /** FM band descriptor + * @see #BAND_FM + * @see #BAND_FM_HD */ + public static class FmBandDescriptor extends BandDescriptor { + private final boolean mStereo; + private final boolean mRds; + private final boolean mTa; + private final boolean mAf; + + FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, + boolean stereo, boolean rds, boolean ta, boolean af) { + super(region, type, lowerLimit, upperLimit, spacing); + mStereo = stereo; + mRds = rds; + mTa = ta; + mAf = af; + } + + /** Stereo is supported + * @return {@code true} if stereo is supported, {@code false} otherwise. + */ + public boolean isStereoSupported() { + return mStereo; + } + /** RDS or RBDS(if region is ITU2) is supported + * @return {@code true} if RDS or RBDS is supported, {@code false} otherwise. + */ + public boolean isRdsSupported() { + return mRds; + } + /** Traffic announcement is supported + * @return {@code true} if TA is supported, {@code false} otherwise. + */ + public boolean isTaSupported() { + return mTa; + } + /** Alternate Frequency Switching is supported + * @return {@code true} if AF switching is supported, {@code false} otherwise. + */ + public boolean isAfSupported() { + return mAf; + } + + /* Parcelable implementation */ + private FmBandDescriptor(Parcel in) { + super(in); + mStereo = in.readByte() == 1; + mRds = in.readByte() == 1; + mTa = in.readByte() == 1; + mAf = in.readByte() == 1; + } + + public static final Parcelable.Creator<FmBandDescriptor> CREATOR + = new Parcelable.Creator<FmBandDescriptor>() { + public FmBandDescriptor createFromParcel(Parcel in) { + return new FmBandDescriptor(in); + } + + public FmBandDescriptor[] newArray(int size) { + return new FmBandDescriptor[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (mStereo ? 1 : 0)); + dest.writeByte((byte) (mRds ? 1 : 0)); + dest.writeByte((byte) (mTa ? 1 : 0)); + dest.writeByte((byte) (mAf ? 1 : 0)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + + ", mRds=" + mRds + ", mTa=" + mTa + ", mAf=" + mAf + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (mStereo ? 1 : 0); + result = prime * result + (mRds ? 1 : 0); + result = prime * result + (mTa ? 1 : 0); + result = prime * result + (mAf ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof FmBandDescriptor)) + return false; + FmBandDescriptor other = (FmBandDescriptor) obj; + if (mStereo != other.isStereoSupported()) + return false; + if (mRds != other.isRdsSupported()) + return false; + if (mTa != other.isTaSupported()) + return false; + if (mAf != other.isAfSupported()) + return false; + return true; + } + } + + /** AM band descriptor. + * @see #BAND_AM */ + public static class AmBandDescriptor extends BandDescriptor { + + private final boolean mStereo; + + AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, + boolean stereo) { + super(region, type, lowerLimit, upperLimit, spacing); + mStereo = stereo; + } + + /** Stereo is supported + * @return {@code true} if stereo is supported, {@code false} otherwise. + */ + public boolean isStereoSupported() { + return mStereo; + } + + private AmBandDescriptor(Parcel in) { + super(in); + mStereo = in.readByte() == 1; + } + + public static final Parcelable.Creator<AmBandDescriptor> CREATOR + = new Parcelable.Creator<AmBandDescriptor>() { + public AmBandDescriptor createFromParcel(Parcel in) { + return new AmBandDescriptor(in); + } + + public AmBandDescriptor[] newArray(int size) { + return new AmBandDescriptor[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (mStereo ? 1 : 0)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (mStereo ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof AmBandDescriptor)) + return false; + AmBandDescriptor other = (AmBandDescriptor) obj; + if (mStereo != other.isStereoSupported()) + return false; + return true; + } + } + + + /** Radio band configuration. */ + public static class BandConfig implements Parcelable { + + final BandDescriptor mDescriptor; + + BandConfig(BandDescriptor descriptor) { + mDescriptor = descriptor; + } + + BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) { + mDescriptor = new BandDescriptor(region, type, lowerLimit, upperLimit, spacing); + } + + private BandConfig(Parcel in) { + mDescriptor = new BandDescriptor(in); + } + + BandDescriptor getDescriptor() { + return mDescriptor; + } + + /** Region this band applies to. E.g. {@link #REGION_ITU_1} + * @return the region associated with this band. + */ + public int getRegion() { + return mDescriptor.getRegion(); + } + /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to: + * <ul> + * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li> + * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li> + * </ul> + * @return the band type. + */ + public int getType() { + return mDescriptor.getType(); + } + /** Lower band limit expressed in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the lower band limit. + */ + public int getLowerLimit() { + return mDescriptor.getLowerLimit(); + } + /** Upper band limit expressed in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the upper band limit. + */ + public int getUpperLimit() { + return mDescriptor.getUpperLimit(); + } + /** Channel spacing in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the channel spacing. + */ + public int getSpacing() { + return mDescriptor.getSpacing(); + } + + + public static final Parcelable.Creator<BandConfig> CREATOR + = new Parcelable.Creator<BandConfig>() { + public BandConfig createFromParcel(Parcel in) { + return new BandConfig(in); + } + + public BandConfig[] newArray(int size) { + return new BandConfig[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + mDescriptor.writeToParcel(dest, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "BandConfig [ " + mDescriptor.toString() + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mDescriptor.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof BandConfig)) + return false; + BandConfig other = (BandConfig) obj; + if (mDescriptor != other.getDescriptor()) + return false; + return true; + } + } + + /** FM band configuration. + * @see #BAND_FM + * @see #BAND_FM_HD */ + public static class FmBandConfig extends BandConfig { + private final boolean mStereo; + private final boolean mRds; + private final boolean mTa; + private final boolean mAf; + + FmBandConfig(FmBandDescriptor descriptor) { + super((BandDescriptor)descriptor); + mStereo = descriptor.isStereoSupported(); + mRds = descriptor.isRdsSupported(); + mTa = descriptor.isTaSupported(); + mAf = descriptor.isAfSupported(); + } + + FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, + boolean stereo, boolean rds, boolean ta, boolean af) { + super(region, type, lowerLimit, upperLimit, spacing); + mStereo = stereo; + mRds = rds; + mTa = ta; + mAf = af; + } + + /** Get stereo enable state + * @return the enable state. + */ + public boolean getStereo() { + return mStereo; + } + + /** Get RDS or RBDS(if region is ITU2) enable state + * @return the enable state. + */ + public boolean getRds() { + return mRds; + } + + /** Get Traffic announcement enable state + * @return the enable state. + */ + public boolean getTa() { + return mTa; + } + + /** Get Alternate Frequency Switching enable state + * @return the enable state. + */ + public boolean getAf() { + return mAf; + } + + private FmBandConfig(Parcel in) { + super(in); + mStereo = in.readByte() == 1; + mRds = in.readByte() == 1; + mTa = in.readByte() == 1; + mAf = in.readByte() == 1; + } + + public static final Parcelable.Creator<FmBandConfig> CREATOR + = new Parcelable.Creator<FmBandConfig>() { + public FmBandConfig createFromParcel(Parcel in) { + return new FmBandConfig(in); + } + + public FmBandConfig[] newArray(int size) { + return new FmBandConfig[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (mStereo ? 1 : 0)); + dest.writeByte((byte) (mRds ? 1 : 0)); + dest.writeByte((byte) (mTa ? 1 : 0)); + dest.writeByte((byte) (mAf ? 1 : 0)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "FmBandConfig [" + super.toString() + + ", mStereo=" + mStereo + ", mRds=" + mRds + ", mTa=" + mTa + + ", mAf=" + mAf + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (mStereo ? 1 : 0); + result = prime * result + (mRds ? 1 : 0); + result = prime * result + (mTa ? 1 : 0); + result = prime * result + (mAf ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof FmBandConfig)) + return false; + FmBandConfig other = (FmBandConfig) obj; + if (mStereo != other.mStereo) + return false; + if (mRds != other.mRds) + return false; + if (mTa != other.mTa) + return false; + if (mAf != other.mAf) + return false; + return true; + } + + /** + * Builder class for {@link FmBandConfig} objects. + */ + public static class Builder { + private final BandDescriptor mDescriptor; + private boolean mStereo; + private boolean mRds; + private boolean mTa; + private boolean mAf; + + /** + * Constructs a new Builder with the defaults from an {@link FmBandDescriptor} . + * @param descriptor the FmBandDescriptor defaults are read from . + */ + public Builder(FmBandDescriptor descriptor) { + mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(), + descriptor.getLowerLimit(), descriptor.getUpperLimit(), + descriptor.getSpacing()); + mStereo = descriptor.isStereoSupported(); + mRds = descriptor.isRdsSupported(); + mTa = descriptor.isTaSupported(); + mAf = descriptor.isAfSupported(); + } + + /** + * Constructs a new Builder from a given {@link FmBandConfig} + * @param config the FmBandConfig object whose data will be reused in the new Builder. + */ + public Builder(FmBandConfig config) { + mDescriptor = new BandDescriptor(config.getRegion(), config.getType(), + config.getLowerLimit(), config.getUpperLimit(), config.getSpacing()); + mStereo = config.getStereo(); + mRds = config.getRds(); + mTa = config.getTa(); + mAf = config.getAf(); + } + + /** + * Combines all of the parameters that have been set and return a new + * {@link FmBandConfig} object. + * @return a new {@link FmBandConfig} object + */ + public FmBandConfig build() { + FmBandConfig config = new FmBandConfig(mDescriptor.getRegion(), + mDescriptor.getType(), mDescriptor.getLowerLimit(), + mDescriptor.getUpperLimit(), mDescriptor.getSpacing(), + mStereo, mRds, mTa, mAf); + return config; + } + + /** Set stereo enable state + * @param state The new enable state. + * @return the same Builder instance. + */ + public Builder setStereo(boolean state) { + mStereo = state; + return this; + } + + /** Set RDS or RBDS(if region is ITU2) enable state + * @param state The new enable state. + * @return the same Builder instance. + */ + public Builder setRds(boolean state) { + mRds = state; + return this; + } + + /** Set Traffic announcement enable state + * @param state The new enable state. + * @return the same Builder instance. + */ + public Builder setTa(boolean state) { + mTa = state; + return this; + } + + /** Set Alternate Frequency Switching enable state + * @param state The new enable state. + * @return the same Builder instance. + */ + public Builder setAf(boolean state) { + mAf = state; + return this; + } + }; + } + + /** AM band configuration. + * @see #BAND_AM */ + public static class AmBandConfig extends BandConfig { + private final boolean mStereo; + + AmBandConfig(AmBandDescriptor descriptor) { + super((BandDescriptor)descriptor); + mStereo = descriptor.isStereoSupported(); + } + + AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, + boolean stereo) { + super(region, type, lowerLimit, upperLimit, spacing); + mStereo = stereo; + } + + /** Get stereo enable state + * @return the enable state. + */ + public boolean getStereo() { + return mStereo; + } + + private AmBandConfig(Parcel in) { + super(in); + mStereo = in.readByte() == 1; + } + + public static final Parcelable.Creator<AmBandConfig> CREATOR + = new Parcelable.Creator<AmBandConfig>() { + public AmBandConfig createFromParcel(Parcel in) { + return new AmBandConfig(in); + } + + public AmBandConfig[] newArray(int size) { + return new AmBandConfig[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeByte((byte) (mStereo ? 1 : 0)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "AmBandConfig [" + super.toString() + + ", mStereo=" + mStereo + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (mStereo ? 1 : 0); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (!(obj instanceof AmBandConfig)) + return false; + AmBandConfig other = (AmBandConfig) obj; + if (mStereo != other.getStereo()) + return false; + return true; + } + + /** + * Builder class for {@link AmBandConfig} objects. + */ + public static class Builder { + private final BandDescriptor mDescriptor; + private boolean mStereo; + + /** + * Constructs a new Builder with the defaults from an {@link AmBandDescriptor} . + * @param descriptor the FmBandDescriptor defaults are read from . + */ + public Builder(AmBandDescriptor descriptor) { + mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(), + descriptor.getLowerLimit(), descriptor.getUpperLimit(), + descriptor.getSpacing()); + mStereo = descriptor.isStereoSupported(); + } + + /** + * Constructs a new Builder from a given {@link AmBandConfig} + * @param config the FmBandConfig object whose data will be reused in the new Builder. + */ + public Builder(AmBandConfig config) { + mDescriptor = new BandDescriptor(config.getRegion(), config.getType(), + config.getLowerLimit(), config.getUpperLimit(), config.getSpacing()); + mStereo = config.getStereo(); + } + + /** + * Combines all of the parameters that have been set and return a new + * {@link AmBandConfig} object. + * @return a new {@link AmBandConfig} object + */ + public AmBandConfig build() { + AmBandConfig config = new AmBandConfig(mDescriptor.getRegion(), + mDescriptor.getType(), mDescriptor.getLowerLimit(), + mDescriptor.getUpperLimit(), mDescriptor.getSpacing(), + mStereo); + return config; + } + + /** Set stereo enable state + * @param state The new enable state. + * @return the same Builder instance. + */ + public Builder setStereo(boolean state) { + mStereo = state; + return this; + } + }; + } + + /** Radio program information returned by + * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */ + public static class ProgramInfo implements Parcelable { + + private final int mChannel; + private final int mSubChannel; + private final boolean mTuned; + private final boolean mStereo; + private final boolean mDigital; + private final int mSignalStrength; + private final RadioMetadata mMetadata; + + ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo, + boolean digital, int signalStrength, RadioMetadata metadata) { + mChannel = channel; + mSubChannel = subChannel; + mTuned = tuned; + mStereo = stereo; + mDigital = digital; + mSignalStrength = signalStrength; + mMetadata = metadata; + } + + /** Main channel expressed in units according to band type. + * Currently all defined band types express channels as frequency in kHz + * @return the program channel + */ + public int getChannel() { + return mChannel; + } + /** Sub channel ID. E.g 1 for HD radio HD1 + * @return the program sub channel + */ + public int getSubChannel() { + return mSubChannel; + } + /** {@code true} if the tuner is currently tuned on a valid station + * @return {@code true} if currently tuned, {@code false} otherwise. + */ + public boolean isTuned() { + return mTuned; + } + /** {@code true} if the received program is stereo + * @return {@code true} if stereo, {@code false} otherwise. + */ + public boolean isStereo() { + return mStereo; + } + /** {@code true} if the received program is digital (e.g HD radio) + * @return {@code true} if digital, {@code false} otherwise. + */ + public boolean isDigital() { + return mDigital; + } + /** Signal strength indicator from 0 (no signal) to 100 (excellent) + * @return the signal strength indication. + */ + public int getSignalStrength() { + return mSignalStrength; + } + /** Metadata currently received from this station. + * null if no metadata have been received + * @return current meta data received from this program. + */ + public RadioMetadata getMetadata() { + return mMetadata; + } + + private ProgramInfo(Parcel in) { + mChannel = in.readInt(); + mSubChannel = in.readInt(); + mTuned = in.readByte() == 1; + mStereo = in.readByte() == 1; + mDigital = in.readByte() == 1; + mSignalStrength = in.readInt(); + if (in.readByte() == 1) { + mMetadata = RadioMetadata.CREATOR.createFromParcel(in); + } else { + mMetadata = null; + } + } + + public static final Parcelable.Creator<ProgramInfo> CREATOR + = new Parcelable.Creator<ProgramInfo>() { + public ProgramInfo createFromParcel(Parcel in) { + return new ProgramInfo(in); + } + + public ProgramInfo[] newArray(int size) { + return new ProgramInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mChannel); + dest.writeInt(mSubChannel); + dest.writeByte((byte)(mTuned ? 1 : 0)); + dest.writeByte((byte)(mStereo ? 1 : 0)); + dest.writeByte((byte)(mDigital ? 1 : 0)); + dest.writeInt(mSignalStrength); + if (mMetadata == null) { + dest.writeByte((byte)0); + } else { + dest.writeByte((byte)1); + mMetadata.writeToParcel(dest, flags); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel + + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital + + ", mSignalStrength=" + mSignalStrength + + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString())) + + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mChannel; + result = prime * result + mSubChannel; + result = prime * result + (mTuned ? 1 : 0); + result = prime * result + (mStereo ? 1 : 0); + result = prime * result + (mDigital ? 1 : 0); + result = prime * result + mSignalStrength; + result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ProgramInfo)) + return false; + ProgramInfo other = (ProgramInfo) obj; + if (mChannel != other.getChannel()) + return false; + if (mSubChannel != other.getSubChannel()) + return false; + if (mTuned != other.isTuned()) + return false; + if (mStereo != other.isStereo()) + return false; + if (mDigital != other.isDigital()) + return false; + if (mSignalStrength != other.getSignalStrength()) + return false; + if (mMetadata == null) { + if (other.getMetadata() != null) + return false; + } else if (!mMetadata.equals(other.getMetadata())) + return false; + return true; + } + } + + + /** + * Returns a list of descriptors for all broadcast radio modules present on the device. + * @param modules An List of {@link ModuleProperties} where the list will be returned. + * @return + * <ul> + * <li>{@link #STATUS_OK} in case of success, </li> + * <li>{@link #STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link #STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link #STATUS_BAD_VALUE} if modules is null, </li> + * <li>{@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails, </li> + * </ul> + */ + public native int listModules(List <ModuleProperties> modules); + + /** + * Open an interface to control a tuner on a given broadcast radio module. + * Optionally selects and applies the configuration passed as "config" argument. + * @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory. + * @param config desired band and configuration to apply when enabling the hardware module. + * optional, can be null. + * @param withAudio {@code true} to request a tuner with an audio source. + * This tuner is intended for live listening or recording or a radio program. + * If {@code false}, the tuner can only be used to retrieve program informations. + * @param callback {@link RadioTuner.Callback} interface. Mandatory. + * @param handler the Handler on which the callbacks will be received. + * Can be null if default handler is OK. + * @return a valid {@link RadioTuner} interface in case of success or null in case of error. + */ + public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio, + RadioTuner.Callback callback, Handler handler) { + if (callback == null) { + return null; + } + RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler); + if (module != null) { + if (!module.initCheck()) { + module = null; + } + } + return (RadioTuner)module; + } + + private final Context mContext; + + /** + * @hide + */ + public RadioManager(Context context) { + mContext = context; + } +} diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java new file mode 100644 index 0000000..8b1851b --- /dev/null +++ b/core/java/android/hardware/radio/RadioMetadata.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2015 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.radio; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Set; + +/** + * Contains meta data about a radio program such as station name, song title, artist etc... + * @hide + */ +@SystemApi +public final class RadioMetadata implements Parcelable { + private static final String TAG = "RadioMetadata"; + + /** + * The RDS Program Information. + */ + public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI"; + + /** + * The RDS Program Service. + */ + public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS"; + + /** + * The RDS PTY. + */ + public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY"; + + /** + * The RBDS PTY. + */ + public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY"; + + /** + * The RBDS Radio Text. + */ + public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT"; + + /** + * The song title. + */ + public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE"; + + /** + * The artist name. + */ + public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST"; + + /** + * The album name. + */ + public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM"; + + /** + * The music genre. + */ + public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE"; + + /** + * The radio station icon {@link Bitmap}. + */ + public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON"; + + /** + * The artwork for the song/album {@link Bitmap}. + */ + public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART"; + + + private static final int METADATA_TYPE_INVALID = -1; + private static final int METADATA_TYPE_INT = 0; + private static final int METADATA_TYPE_TEXT = 1; + private static final int METADATA_TYPE_BITMAP = 2; + + private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; + + static { + METADATA_KEYS_TYPE = new ArrayMap<String, Integer>(); + METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT); + METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT); + METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT); + METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP); + METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); + } + + // keep in sync with: system/media/radio/include/system/radio_metadata.h + private static final int NATIVE_KEY_INVALID = -1; + private static final int NATIVE_KEY_RDS_PI = 0; + private static final int NATIVE_KEY_RDS_PS = 1; + private static final int NATIVE_KEY_RDS_PTY = 2; + private static final int NATIVE_KEY_RBDS_PTY = 3; + private static final int NATIVE_KEY_RDS_RT = 4; + private static final int NATIVE_KEY_TITLE = 5; + private static final int NATIVE_KEY_ARTIST = 6; + private static final int NATIVE_KEY_ALBUM = 7; + private static final int NATIVE_KEY_GENRE = 8; + private static final int NATIVE_KEY_ICON = 9; + private static final int NATIVE_KEY_ART = 10; + + private static final SparseArray<String> NATIVE_KEY_MAPPING; + + static { + NATIVE_KEY_MAPPING = new SparseArray<String>(); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON); + NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART); + } + + private final Bundle mBundle; + + RadioMetadata() { + mBundle = new Bundle(); + } + + private RadioMetadata(Bundle bundle) { + mBundle = new Bundle(bundle); + } + + private RadioMetadata(Parcel in) { + mBundle = in.readBundle(); + } + + /** + * Returns {@code true} if the given key is contained in the meta data + * + * @param key a String key + * @return {@code true} if the key exists in this meta data, {@code false} otherwise + */ + public boolean containsKey(String key) { + return mBundle.containsKey(key); + } + + /** + * Returns the text value associated with the given key as a String, or null + * if the key is not found in the meta data. + * + * @param key The key the value is stored under + * @return a String value, or null + */ + public String getString(String key) { + return mBundle.getString(key); + } + + /** + * Returns the value associated with the given key, + * or 0 if the key is not found in the meta data. + * + * @param key The key the value is stored under + * @return an int value + */ + public int getInt(String key) { + return mBundle.getInt(key, 0); + } + + /** + * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data. + * + * @param key The key the value is stored under + * @return a {@link Bitmap} or null + */ + public Bitmap getBitmap(String key) { + Bitmap bmp = null; + try { + bmp = mBundle.getParcelable(key); + } catch (Exception e) { + // ignore, value was not a bitmap + Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); + } + return bmp; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBundle(mBundle); + } + + /** + * Returns the number of fields in this meta data. + * + * @return the number of fields in the meta data. + */ + public int size() { + return mBundle.size(); + } + + /** + * Returns a Set containing the Strings used as keys in this meta data. + * + * @return a Set of String keys + */ + public Set<String> keySet() { + return mBundle.keySet(); + } + + /** + * Helper for getting the String key used by {@link RadioMetadata} from the + * corrsponding native integer key. + * + * @param editorKey The key used by the editor + * @return the key used by this class or null if no mapping exists + * @hide + */ + public static String getKeyFromNativeKey(int nativeKey) { + return NATIVE_KEY_MAPPING.get(nativeKey, null); + } + + public static final Parcelable.Creator<RadioMetadata> CREATOR = + new Parcelable.Creator<RadioMetadata>() { + @Override + public RadioMetadata createFromParcel(Parcel in) { + return new RadioMetadata(in); + } + + @Override + public RadioMetadata[] newArray(int size) { + return new RadioMetadata[size]; + } + }; + + /** + * Use to build RadioMetadata objects. + */ + public static final class Builder { + private final Bundle mBundle; + + /** + * Create an empty Builder. Any field that should be included in the + * {@link RadioMetadata} must be added. + */ + public Builder() { + mBundle = new Bundle(); + } + + /** + * Create a Builder using a {@link RadioMetadata} instance to set the + * initial values. All fields in the source meta data will be included in + * the new meta data. Fields can be overwritten by adding the same key. + * + * @param source + */ + public Builder(RadioMetadata source) { + mBundle = new Bundle(source.mBundle); + } + + /** + * Create a Builder using a {@link RadioMetadata} instance to set + * initial values, but replace bitmaps with a scaled down copy if they + * are larger than maxBitmapSize. + * + * @param source The original meta data to copy. + * @param maxBitmapSize The maximum height/width for bitmaps contained + * in the meta data. + * @hide + */ + public Builder(RadioMetadata source, int maxBitmapSize) { + this(source); + for (String key : mBundle.keySet()) { + Object value = mBundle.get(key); + if (value != null && value instanceof Bitmap) { + Bitmap bmp = (Bitmap) value; + if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { + putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); + } + } + } + } + + /** + * Put a String value into the meta data. Custom keys may be used, but if + * the METADATA_KEYs defined in this class are used they may only be one + * of the following: + * <ul> + * <li>{@link #METADATA_KEY_RDS_PI}</li> + * <li>{@link #METADATA_KEY_RDS_PS}</li> + * <li>{@link #METADATA_KEY_RDS_RT}</li> + * <li>{@link #METADATA_KEY_TITLE}</li> + * <li>{@link #METADATA_KEY_ARTIST}</li> + * <li>{@link #METADATA_KEY_ALBUM}</li> + * <li>{@link #METADATA_KEY_GENRE}</li> + * </ul> + * + * @param key The key for referencing this value + * @param value The String value to store + * @return the same Builder instance + */ + public Builder putString(String key, String value) { + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { + throw new IllegalArgumentException("The " + key + + " key cannot be used to put a String"); + } + mBundle.putString(key, value); + return this; + } + + /** + * Put an int value into the meta data. Custom keys may be used, but if + * the METADATA_KEYs defined in this class are used they may only be one + * of the following: + * <ul> + * <li>{@link #METADATA_KEY_RDS_PTY}</li> + * <li>{@link #METADATA_KEY_RBDS_PTY}</li> + * </ul> + * + * @param key The key for referencing this value + * @param value The int value to store + * @return the same Builder instance + */ + public Builder putInt(String key, int value) { + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) { + throw new IllegalArgumentException("The " + key + + " key cannot be used to put a long"); + } + mBundle.putInt(key, value); + return this; + } + + /** + * Put a {@link Bitmap} into the meta data. Custom keys may be used, but + * if the METADATA_KEYs defined in this class are used they may only be + * one of the following: + * <ul> + * <li>{@link #METADATA_KEY_ICON}</li> + * <li>{@link #METADATA_KEY_ART}</li> + * </ul> + * <p> + * + * @param key The key for referencing this value + * @param value The Bitmap to store + * @return the same Builder instance + */ + public Builder putBitmap(String key, Bitmap value) { + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { + throw new IllegalArgumentException("The " + key + + " key cannot be used to put a Bitmap"); + } + mBundle.putParcelable(key, value); + return this; + } + + /** + * Creates a {@link RadioMetadata} instance with the specified fields. + * + * @return a new {@link RadioMetadata} object + */ + public RadioMetadata build() { + return new RadioMetadata(mBundle); + } + + private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { + float maxSizeF = maxSize; + float widthScale = maxSizeF / bmp.getWidth(); + float heightScale = maxSizeF / bmp.getHeight(); + float scale = Math.min(widthScale, heightScale); + int height = (int) (bmp.getHeight() * scale); + int width = (int) (bmp.getWidth() * scale); + return Bitmap.createScaledBitmap(bmp, width, height, true); + } + } + + int putIntFromNative(int nativeKey, int value) { + String key = getKeyFromNativeKey(nativeKey); + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) { + return -1; + } + mBundle.putInt(key, value); + return 0; + } + + int putStringFromNative(int nativeKey, String value) { + String key = getKeyFromNativeKey(nativeKey); + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { + return -1; + } + mBundle.putString(key, value); + return 0; + } + + int putBitmapFromNative(int nativeKey, byte[] value) { + String key = getKeyFromNativeKey(nativeKey); + if (!METADATA_KEYS_TYPE.containsKey(key) || + METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { + return -1; + } + Bitmap bmp = null; + try { + bmp = BitmapFactory.decodeByteArray(value, 0, value.length); + } catch (Exception e) { + } finally { + if (bmp == null) { + return -1; + } + mBundle.putParcelable(key, bmp); + return 0; + } + } +} diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java new file mode 100644 index 0000000..15916ae --- /dev/null +++ b/core/java/android/hardware/radio/RadioModule.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2015 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.radio; + +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import java.lang.ref.WeakReference; +import java.util.UUID; + +/** + * A RadioModule implements the RadioTuner interface for a broadcast radio tuner physically + * present on the device and exposed by the radio HAL. + * + * @hide + */ +public class RadioModule extends RadioTuner { + private long mNativeContext = 0; + private int mId; + private NativeEventHandlerDelegate mEventHandlerDelegate; + + RadioModule(int moduleId, RadioManager.BandConfig config, boolean withAudio, + RadioTuner.Callback callback, Handler handler) { + mId = moduleId; + mEventHandlerDelegate = new NativeEventHandlerDelegate(callback, handler); + native_setup(new WeakReference<RadioModule>(this), config, withAudio); + } + private native void native_setup(Object module_this, + RadioManager.BandConfig config, boolean withAudio); + + @Override + protected void finalize() { + native_finalize(); + } + private native void native_finalize(); + + boolean initCheck() { + return mNativeContext != 0; + } + + // RadioTuner implementation + public native void close(); + + public native int setConfiguration(RadioManager.BandConfig config); + + public native int getConfiguration(RadioManager.BandConfig[] config); + + public native int setMute(boolean mute); + + public native boolean getMute(); + + public native int step(int direction, boolean skipSubChannel); + + public native int scan(int direction, boolean skipSubChannel); + + public native int tune(int channel, int subChannel); + + public native int cancel(); + + public native int getProgramInformation(RadioManager.ProgramInfo[] info); + + public native boolean isAntennaConnected(); + + public native boolean hasControl(); + + + /* keep in sync with radio_event_type_t in system/core/include/system/radio.h */ + static final int EVENT_HW_FAILURE = 0; + static final int EVENT_CONFIG = 1; + static final int EVENT_ANTENNA = 2; + static final int EVENT_TUNED = 3; + static final int EVENT_METADATA = 4; + static final int EVENT_TA = 5; + static final int EVENT_AF_SWITCH = 6; + static final int EVENT_CONTROL = 100; + static final int EVENT_SERVER_DIED = 101; + + private class NativeEventHandlerDelegate { + private final Handler mHandler; + + NativeEventHandlerDelegate(final RadioTuner.Callback callback, + Handler handler) { + // find the looper for our new event handler + Looper looper; + if (handler != null) { + looper = handler.getLooper(); + } else { + looper = Looper.getMainLooper(); + } + + // construct the event handler with this looper + if (looper != null) { + // implement the event handler delegate + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_HW_FAILURE: + if (callback != null) { + callback.onError(RadioTuner.ERROR_HARDWARE_FAILURE); + } + break; + case EVENT_CONFIG: { + RadioManager.BandConfig config = (RadioManager.BandConfig)msg.obj; + switch(msg.arg1) { + case RadioManager.STATUS_OK: + if (callback != null) { + callback.onConfigurationChanged(config); + } + break; + default: + if (callback != null) { + callback.onError(RadioTuner.ERROR_CONFIG); + } + break; + } + } break; + case EVENT_ANTENNA: + if (callback != null) { + callback.onAntennaState(msg.arg2 == 1); + } + break; + case EVENT_AF_SWITCH: + case EVENT_TUNED: { + RadioManager.ProgramInfo info = (RadioManager.ProgramInfo)msg.obj; + switch (msg.arg1) { + case RadioManager.STATUS_OK: + if (callback != null) { + callback.onProgramInfoChanged(info); + } + break; + case RadioManager.STATUS_TIMED_OUT: + if (callback != null) { + callback.onError(RadioTuner.ERROR_SCAN_TIMEOUT); + } + break; + case RadioManager.STATUS_INVALID_OPERATION: + default: + if (callback != null) { + callback.onError(RadioTuner.ERROR_CANCELLED); + } + break; + } + } break; + case EVENT_METADATA: { + RadioMetadata metadata = (RadioMetadata)msg.obj; + if (callback != null) { + callback.onMetadataChanged(metadata); + } + } break; + case EVENT_TA: + if (callback != null) { + callback.onTrafficAnnouncement(msg.arg2 == 1); + } + break; + case EVENT_CONTROL: + if (callback != null) { + callback.onControlChanged(msg.arg2 == 1); + } + break; + case EVENT_SERVER_DIED: + if (callback != null) { + callback.onError(RadioTuner.ERROR_SERVER_DIED); + } + break; + default: + // Should not happen + break; + } + } + }; + } else { + mHandler = null; + } + } + + Handler handler() { + return mHandler; + } + } + + + @SuppressWarnings("unused") + private static void postEventFromNative(Object module_ref, + int what, int arg1, int arg2, Object obj) { + RadioModule module = (RadioModule)((WeakReference)module_ref).get(); + if (module == null) { + return; + } + + NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate; + if (delegate != null) { + Handler handler = delegate.handler(); + if (handler != null) { + Message m = handler.obtainMessage(what, arg1, arg2, obj); + handler.sendMessage(m); + } + } + } +} + diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java new file mode 100644 index 0000000..376900a --- /dev/null +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2015 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.radio; + +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import java.lang.ref.WeakReference; +import java.util.UUID; + +/** + * RadioTuner interface provides methods to control a radio tuner on the device: selecting and + * configuring the active band, muting/unmuting, scanning and tuning, etc... + * + * Obtain a RadioTuner interface by calling {@link RadioManager#openTuner(int, + * RadioManager.BandConfig, boolean, RadioTuner.Callback, Handler)}. + * @hide + */ +@SystemApi +public abstract class RadioTuner { + + /** Scanning direction UP for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */ + public static final int DIRECTION_UP = 0; + + /** Scanning directions DOWN for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */ + public static final int DIRECTION_DOWN = 1; + + /** + * Close the tuner interface. The {@link Callback} callback will not be called + * anymore and associated resources will be released. + * Must be called when the tuner is not needed to make hardware resources available to others. + * */ + public abstract void close(); + + /** + * Set the active band configuration for this module. + * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed + * in the ModuleProperties of the module with the specified ID. + * @param config The desired band configuration (FmBandConfig or AmBandConfig). + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int setConfiguration(RadioManager.BandConfig config); + + /** + * Get current configuration. + * @param config a BandConfig array of lengh 1 where the configuration is returned. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int getConfiguration(RadioManager.BandConfig[] config); + + + /** + * Set mute state. When muted, the radio tuner audio source is not available for playback on + * any audio device. when unmuted, the radio tuner audio source is output as a media source + * and renderd over the audio device selected for media use case. + * The radio tuner audio source is muted by default when the tuner is first attached. + * Only effective if the tuner is attached with audio enabled. + * + * @param mute the requested mute state. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int setMute(boolean mute); + + /** + * Get mute state. + * + * @return {@code true} if the radio tuner audio source is muted or a problem occured + * retrieving the mute state, {@code false} otherwise. + */ + public abstract boolean getMute(); + + /** + * Step up or down by one channel spacing. + * The operation is asynchronous and {@link Callback} + * onProgramInfoChanged() will be called when step completes or + * onError() when cancelled or timeout. + * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}. + * @param skipSubChannel indicates to skip sub channels when the configuration currently + * selected supports sub channel (e.g HD Radio). N/A otherwise. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int step(int direction, boolean skipSubChannel); + + /** + * Scan up or down to next valid station. + * The operation is asynchronous and {@link Callback} + * onProgramInfoChanged() will be called when scan completes or + * onError() when cancelled or timeout. + * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}. + * @param skipSubChannel indicates to skip sub channels when the configuration currently + * selected supports sub channel (e.g HD Radio). N/A otherwise. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int scan(int direction, boolean skipSubChannel); + + /** + * Tune to a specific frequency. + * The operation is asynchronous and {@link Callback} + * onProgramInfoChanged() will be called when tune completes or + * onError() when cancelled or timeout. + * @param channel the specific channel or frequency to tune to. + * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration + * does not support cub channels. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int tune(int channel, int subChannel); + + /** + * Cancel a pending scan or tune operation. + * If an operation is pending, {@link Callback} onError() will be called with + * {@link #ERROR_CANCELLED}. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int cancel(); + + /** + * Get current station information. + * @param info a ProgramInfo array of lengh 1 where the information is returned. + * @return + * <ul> + * <li>{@link RadioManager#STATUS_OK} in case of success, </li> + * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li> + * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li> + * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li> + * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li> + * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails, </li> + * </ul> + */ + public abstract int getProgramInformation(RadioManager.ProgramInfo[] info); + + /** + * Get current antenna connection state for current configuration. + * Only valid if a configuration has been applied. + * @return {@code true} if the antenna is connected, {@code false} otherwise. + */ + public abstract boolean isAntennaConnected(); + + /** + * Indicates if this client actually controls the tuner. + * Control is always granted after + * {@link RadioManager#openTuner(int, + * RadioManager.BandConfig, boolean, Callback, Handler)} + * returns a non null tuner interface. + * Control is lost when another client opens an interface on the same tuner. + * When this happens, {@link Callback#onControlChanged(boolean)} is received. + * The client can either wait for control to be returned (which is indicated by the same + * callback) or close and reopen the tuner interface. + * @return {@code true} if this interface controls the tuner, + * {@code false} otherwise or if a problem occured retrieving the state. + */ + public abstract boolean hasControl(); + + /** Indicates a failure of radio IC or driver. + * The application must close and re open the tuner */ + public static final int ERROR_HARDWARE_FAILURE = 0; + /** Indicates a failure of the radio service. + * The application must close and re open the tuner */ + public static final int ERROR_SERVER_DIED = 1; + /** A pending seek or tune operation was cancelled */ + public static final int ERROR_CANCELLED = 2; + /** A pending seek or tune operation timed out */ + public static final int ERROR_SCAN_TIMEOUT = 3; + /** The requested configuration could not be applied */ + public static final int ERROR_CONFIG = 4; + + /** + * Callback provided by the client application when opening a {@link RadioTuner} + * to receive asynchronous operation results, updates and error notifications. + */ + public static abstract class Callback { + /** + * onError() is called when an error occured while performing an asynchronous + * operation of when the hardware or system service experiences a problem. + * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED}, + * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT}, + * {@link #ERROR_CONFIG} + */ + public void onError(int status) {} + /** + * onConfigurationChanged() is called upon successful completion of + * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)} + * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)} + */ + public void onConfigurationChanged(RadioManager.BandConfig config) {} + /** + * onProgramInfoChanged() is called upon successful completion of + * {@link RadioTuner#step(int, boolean)}, {@link RadioTuner#scan(int, boolean)}, + * {@link RadioTuner#tune(int, int)} or when a switching to alternate frequency occurs. + * Note that if metadata only are updated, {@link #onMetadataChanged(RadioMetadata)} will + * be called. + */ + public void onProgramInfoChanged(RadioManager.ProgramInfo info) {} + /** + * onMetadataChanged() is called when new meta data are received on current program. + * Meta data are also received in {@link RadioManager.ProgramInfo} when + * {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} is called. + */ + public void onMetadataChanged(RadioMetadata metadata) {} + /** + * onTrafficAnnouncement() is called when a traffic announcement starts and stops. + */ + public void onTrafficAnnouncement(boolean active) {} + /** + * onAntennaState() is called when the antenna is connected or disconnected. + */ + public void onAntennaState(boolean connected) {} + /** + * onControlChanged() is called when the client loses or gains control of the radio tuner. + * The control is always granted after a successful call to + * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}. + * If another client opens the same tuner, onControlChanged() will be called with + * control set to {@code false} to indicate loss of control. + * At this point, RadioTuner APIs other than getters will return + * {@link RadioManager#STATUS_INVALID_OPERATION}. + * When the other client releases the tuner, onControlChanged() will be called + * with control set to {@code true}. + */ + public void onControlChanged(boolean control) {} + } + +} + diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index d2a2997..8003afb 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -56,6 +56,30 @@ public class NetworkUtils { /** * Start the DHCP client daemon, in order to have it request addresses + * for the named interface. This returns {@code true} if the DHCPv4 daemon + * starts, {@code false} otherwise. This call blocks until such time as a + * result is available or the default discovery timeout has been reached. + * Callers should check {@link #getDhcpResults} to determine whether DHCP + * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}. + * @param interfaceName the name of the interface to configure + * @return {@code true} for success, {@code false} for failure + */ + public native static boolean startDhcp(String interfaceName); + + /** + * Initiate renewal on the DHCP client daemon for the named interface. This + * returns {@code true} if the DHCPv4 daemon has been notified, {@code false} + * otherwise. This call blocks until such time as a result is available or + * the default renew timeout has been reached. Callers should check + * {@link #getDhcpResults} to determine whether DHCP succeeded or failed, + * and if it succeeded, to fetch the {@link DhcpResults}. + * @param interfaceName the name of the interface to configure + * @return {@code true} for success, {@code false} for failure + */ + public native static boolean startDhcpRenew(String interfaceName); + + /** + * Start the DHCP client daemon, in order to have it request addresses * for the named interface, and then configure the interface with those * addresses. This call blocks until it obtains a result (either success * or failure) from the daemon. @@ -64,17 +88,31 @@ public class NetworkUtils { * the IP address information. * @return {@code true} for success, {@code false} for failure */ - public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults); + public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) { + return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults); + } /** - * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains + * Initiate renewal on the DHCP client daemon. This call blocks until it obtains * a result (either success or failure) from the daemon. * @param interfaceName the name of the interface to configure * @param dhcpResults if the request succeeds, this object is filled in with * the IP address information. * @return {@code true} for success, {@code false} for failure */ - public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults); + public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) { + return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults); + } + + /** + * Fetch results from the DHCP client daemon. This call returns {@code true} if + * if there are results available to be read, {@code false} otherwise. + * @param interfaceName the name of the interface to configure + * @param dhcpResults if the request succeeds, this object is filled in with + * the IP address information. + * @return {@code true} for success, {@code false} for failure + */ + public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults); /** * Shut down the DHCP client daemon. diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index f0660eb..f93550a 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -178,6 +178,18 @@ interface INetworkManagementService String[] getDnsForwarders(); /** + * Enables unidirectional packet forwarding from {@code fromIface} to + * {@code toIface}. + */ + void startInterfaceForwarding(String fromIface, String toIface); + + /** + * Disables unidirectional packet forwarding from {@code fromIface} to + * {@code toIface}. + */ + void stopInterfaceForwarding(String fromIface, String toIface); + + /** * Enables Network Address Translation between two interfaces. * The address and netmask of the external interface is used for * the NAT'ed network. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 3d5215b..9d8a1ba 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1059,6 +1059,21 @@ public final class Parcel { } } + /** + * @hide + */ + public final void writeCharSequenceList(ArrayList<CharSequence> val) { + if (val != null) { + int N = val.size(); + writeInt(N); + for (int i=0; i<N; i++) { + writeCharSequence(val.get(i)); + } + } else { + writeInt(-1); + } + } + public final IBinder[] createBinderArray() { int N = readInt(); if (N >= 0) { @@ -1828,6 +1843,25 @@ public final class Parcel { } /** + * Read and return an ArrayList<CharSequence> object from the parcel. + * {@hide} + */ + public final ArrayList<CharSequence> readCharSequenceList() { + ArrayList<CharSequence> array = null; + + int length = readInt(); + if (length >= 0) { + array = new ArrayList<CharSequence>(length); + + for (int i = 0 ; i < length ; i++) { + array.add(readCharSequence()); + } + } + + return array; + } + + /** * Read and return a new ArrayList object from the parcel at the current * dataPosition(). Returns null if the previously written list object was * null. The given class loader will be used to load any enclosed diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index e303f61..de970cb 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -364,6 +364,12 @@ public final class PowerManager { public static final int GO_TO_SLEEP_REASON_HDMI = 5; /** + * Go to sleep reason code: Going to sleep due to the sleep button being pressed. + * @hide + */ + public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6; + + /** * Go to sleep flag: Skip dozing state and directly go to full sleep. * @hide */ diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 29f9ca1..66642de 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -17,6 +17,7 @@ package android.preference; import android.annotation.Nullable; +import android.annotation.XmlRes; import android.app.Activity; import android.app.Fragment; import android.content.Intent; @@ -294,7 +295,7 @@ public abstract class PreferenceFragment extends Fragment implements * * @param preferencesResId The XML resource ID to inflate. */ - public void addPreferencesFromResource(int preferencesResId) { + public void addPreferencesFromResource(@XmlRes int preferencesResId) { requirePreferenceManager(); setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(), diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index e4e753e..44e6410 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -212,7 +212,7 @@ public final class PrintDocumentInfo implements Parcelable { result = prime * result + mContentType; result = prime * result + mPageCount; result = prime * result + (int) mDataSize; - result = prime * result + (int) mDataSize >> 32; + result = prime * result + (int) (mDataSize >> 32); return result; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 7d57233..6517f35 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -454,6 +454,7 @@ public class CallLog { long start, int duration, Long dataUsage, boolean addForAllUsers) { final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; + boolean isHidden = false; TelecomManager tm = null; try { @@ -468,6 +469,12 @@ public class CallLog { if (address != null) { accountAddress = address.getSchemeSpecificPart(); } + } else { + // We could not find the account through telecom. For call log entries that + // are added with a phone account which is not registered, we automatically + // mark them as hidden. They are unhidden once the account is registered. + Log.i(LOG_TAG, "Marking call log entry as hidden."); + isHidden = true; } } @@ -513,6 +520,7 @@ public class CallLog { values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); values.put(PHONE_ACCOUNT_ID, accountId); values.put(PHONE_ACCOUNT_ADDRESS, accountAddress); + values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0)); values.put(NEW, Integer.valueOf(1)); if (callType == MISSED_TYPE) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cc84932..3813277 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5374,6 +5374,7 @@ public final class Settings { ACCESSIBILITY_SCRIPT_INJECTION, BACKUP_AUTO_RESTORE, ENABLED_ACCESSIBILITY_SERVICES, + ENABLED_NOTIFICATION_LISTENERS, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, TOUCH_EXPLORATION_ENABLED, ACCESSIBILITY_ENABLED, @@ -5398,6 +5399,9 @@ public final class Settings { WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global WIFI_NUM_OPEN_NETWORKS_KEPT, // moved to global + SELECTED_SPELL_CHECKER, + SELECTED_SPELL_CHECKER_SUBTYPE, + SPELL_CHECKER_ENABLED, MOUNT_PLAY_NOTIFICATION_SND, MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, @@ -7146,6 +7150,33 @@ public final class Settings { public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; /** + * Whether WFC is enabled + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + public static final String WFC_IMS_ENABLED = "wfc_ims_enabled"; + + /** + * WFC Mode. + * <p> + * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only + * + * @hide + */ + public static final String WFC_IMS_MODE = "wfc_ims_mode"; + + /** + * Whether WFC roaming is enabled + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled"; + + /** * Global override to disable VoLTE (independent of user setting) * <p> * Type: int (1 for disable VoLTE, 0 to use user configuration) diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index d71ad03..0da4fd5 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -19,10 +19,18 @@ package android.provider; import android.Manifest; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.database.ContentObserver; +import android.database.Cursor; import android.net.Uri; import android.provider.CallLog.Calls; +import android.telecom.PhoneAccountHandle; +import android.telecom.Voicemail; + +import java.util.List; /** * The contract between the voicemail provider and applications. Contains @@ -199,13 +207,100 @@ public class VoicemailContract { */ public static final String _DATA = "_data"; + // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming + // that was encoded into call log databases. + + /** + * The component name of the account in string form. + * <P>Type: TEXT</P> + */ + public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; + + /** + * The identifier of a account that is unique to a specified component. + * <P>Type: TEXT</P> + */ + public static final String PHONE_ACCOUNT_ID = "subscription_id"; + + /** + * Flag used to indicate that local, unsynced changes are present. + * Currently, this is used to indicate that the voicemail was read or deleted. + * The value will be 1 if dirty is true, 0 if false. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String DIRTY = "dirty"; + + /** + * Flag used to indicate that the voicemail was deleted but not synced to the server. + * A deleted row should be ignored. + * The value will be 1 if deleted is true, 0 if false. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String DELETED = "deleted"; + /** * A convenience method to build voicemail URI specific to a source package by appending * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. */ public static Uri buildSourceUri(String packageName) { return Voicemails.CONTENT_URI.buildUpon() - .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); + .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName) + .build(); + } + + /** + * Inserts a new voicemail into the voicemail content provider. + * + * @param context The context of the app doing the inserting + * @param voicemail Data to be inserted + * @return {@link Uri} of the newly inserted {@link Voicemail} + */ + public static Uri insert(Context context, Voicemail voicemail) { + ContentResolver contentResolver = context.getContentResolver(); + ContentValues contentValues = getContentValues(voicemail); + return contentResolver.insert(Voicemails.CONTENT_URI, contentValues); + } + + /** + * Inserts a list of voicemails into the voicemail content provider. + * + * @param context The context of the app doing the inserting + * @param voicemails Data to be inserted + * @return the number of voicemails inserted + */ + public static int insert(Context context, List<Voicemail> voicemails) { + ContentResolver contentResolver = context.getContentResolver(); + int count = voicemails.size(); + for (int i = 0; i < count; i++) { + ContentValues contentValues = getContentValues(voicemails.get(i)); + contentResolver.insert(Voicemails.CONTENT_URI, contentValues); + } + return count; + } + + /** + * Clears all voicemails accessible to this voicemail content provider for the calling + * package. By default, a package only has permission to delete voicemails it inserted. + * + * @return the number of voicemails deleted + */ + public static int deleteAll(Context context) { + return context.getContentResolver().delete( + buildSourceUri(context.getPackageName()), "", new String[0]); + } + + /** + * Maps structured {@link Voicemail} to {@link ContentValues} in content provider. + */ + private static ContentValues getContentValues(Voicemail voicemail) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Voicemails.DATE, String.valueOf(voicemail.getTimestampMillis())); + contentValues.put(Voicemails.NUMBER, voicemail.getNumber()); + contentValues.put(Voicemails.DURATION, String.valueOf(voicemail.getDuration())); + contentValues.put(Voicemails.SOURCE_PACKAGE, voicemail.getSourcePackage()); + contentValues.put(Voicemails.SOURCE_DATA, voicemail.getSourceData()); + contentValues.put(Voicemails.IS_READ, voicemail.isRead() ? 1 : 0); + return contentValues; } } @@ -222,10 +317,27 @@ public class VoicemailContract { private Status() { } /** - * The package name of the voicemail source. There can only be a one entry per source. + * The package name of the voicemail source. There can only be a one entry per account + * per source. * <P>Type: TEXT</P> */ public static final String SOURCE_PACKAGE = SOURCE_PACKAGE_FIELD; + + // Note: Multiple entries may exist for a single source if they are differentiated by the + // PHONE_ACCOUNT_* fields. + + /** + * The component name of the account in string form. + * <P>Type: TEXT</P> + */ + public static final String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; + + /** + * The identifier of a account that is unique to a specified component. + * <P>Type: TEXT</P> + */ + public static final String PHONE_ACCOUNT_ID = "phone_account_id"; + /** * The URI to call to invoke source specific voicemail settings screen. On a user request * to setup voicemail an intent with action VIEW with this URI will be fired by the system. @@ -318,5 +430,50 @@ public class VoicemailContract { return Status.CONTENT_URI.buildUpon() .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build(); } + + /** + * A helper method to set the status of a voicemail source. + * + * @param context The context from the package calling the method. This will be the source. + * @param accountHandle The handle for the account the source is associated with. + * @param configurationState See {@link Status#CONFIGURATION_STATE} + * @param dataChannelState See {@link Status#DATA_CHANNEL_STATE} + * @param notificationChannelState See {@link Status#NOTIFICATION_CHANNEL_STATE} + */ + public static void setStatus(Context context, PhoneAccountHandle accountHandle, + int configurationState, int dataChannelState, int notificationChannelState) { + ContentResolver contentResolver = context.getContentResolver(); + Uri statusUri = buildSourceUri(context.getPackageName()); + ContentValues values = new ContentValues(); + values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME, + accountHandle.getComponentName().toString()); + values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId()); + values.put(Status.CONFIGURATION_STATE, configurationState); + values.put(Status.DATA_CHANNEL_STATE, dataChannelState); + values.put(Status.NOTIFICATION_CHANNEL_STATE, notificationChannelState); + + if (isStatusPresent(contentResolver, statusUri)) { + contentResolver.update(statusUri, values, null, null); + } else { + contentResolver.insert(statusUri, values); + } + } + + /** + * Determines if a voicemail source exists in the status table. + * + * @param contentResolver A content resolver constructed from the appropriate context. + * @param statusUri The content uri for the source. + * @return {@code true} if a status entry for this source exists + */ + private static boolean isStatusPresent(ContentResolver contentResolver, Uri statusUri) { + Cursor cursor = null; + try { + cursor = contentResolver.query(statusUri, null, null, null, null); + return cursor != null && cursor.getCount() != 0; + } finally { + if (cursor != null) cursor.close(); + } + } } } diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 797457a..4f4b2d5 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -17,6 +17,7 @@ package android.service.voice; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Bundle; /** @@ -26,6 +27,7 @@ oneway interface IVoiceInteractionSession { void show(in Bundle sessionArgs, int flags); void hide(); void handleAssist(in Bundle assistData); + void handleScreenshot(in Bitmap screenshot); void taskStarted(in Intent intent, int taskId); void taskFinished(in Intent intent, int taskId); void closeSystemDialogs(); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 0c01b25..419b92b 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -71,11 +71,17 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_META_DATA = "android.voice_interaction"; /** - * Flag for use with {@link #showSession: request that the session be started with + * Flag for use with {@link #showSession}: request that the session be started with * assist data from the currently focused activity. */ public static final int START_WITH_ASSIST = 1<<0; + /** + * Flag for use with {@link #showSession}: request that the session be started with + * a screen shot of the currently focused activity. + */ + public static final int START_WITH_SCREENSHOT = 1<<1; + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { mHandler.sendEmptyMessage(MSG_READY); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 4cf0e4c..7a5bb90 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -18,9 +18,11 @@ package android.service.voice; import android.app.Dialog; import android.app.Instrumentation; +import android.app.VoiceInteractor; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.Region; import android.inputmethodservice.SoftInputWindow; @@ -105,6 +107,17 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public IVoiceInteractorRequest startPickOption(String callingPackage, + IVoiceInteractorCallback callback, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { + Request request = newRequest(callback); + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOO(MSG_START_PICK_OPTION, + new Caller(callingPackage, Binder.getCallingUid()), request, + prompt, options, extras)); + return request.mInterface; + } + + @Override public IVoiceInteractorRequest startCompleteVoice(String callingPackage, IVoiceInteractorCallback callback, CharSequence message, Bundle extras) { Request request = newRequest(callback); @@ -167,6 +180,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } @Override + public void handleScreenshot(Bitmap screenshot) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT, + screenshot)); + } + + @Override public void taskStarted(Intent intent, int taskId) { mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED, taskId, intent)); @@ -232,6 +251,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } + public void sendPickOptionResult(boolean finished, + VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) { + try { + if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface + + " finished=" + finished + " selections=" + selections + + " result=" + result); + if (finished) { + finishRequest(); + } + mCallback.deliverPickOptionResult(mInterface, finished, selections, result); + } catch (RemoteException e) { + } + } + public void sendCompleteVoiceResult(Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface @@ -252,12 +285,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } } - public void sendCommandResult(boolean complete, Bundle result) { + public void sendCommandResult(boolean finished, Bundle result) { try { if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface + " result=" + result); - finishRequest(); - mCallback.deliverCommandResult(mInterface, complete, result); + if (finished) { + finishRequest(); + } + mCallback.deliverCommandResult(mInterface, finished, result); } catch (RemoteException e) { } } @@ -283,19 +318,21 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } static final int MSG_START_CONFIRMATION = 1; - static final int MSG_START_COMPLETE_VOICE = 2; - static final int MSG_START_ABORT_VOICE = 3; - static final int MSG_START_COMMAND = 4; - static final int MSG_SUPPORTS_COMMANDS = 5; - static final int MSG_CANCEL = 6; + static final int MSG_START_PICK_OPTION = 2; + static final int MSG_START_COMPLETE_VOICE = 3; + static final int MSG_START_ABORT_VOICE = 4; + static final int MSG_START_COMMAND = 5; + static final int MSG_SUPPORTS_COMMANDS = 6; + static final int MSG_CANCEL = 7; static final int MSG_TASK_STARTED = 100; static final int MSG_TASK_FINISHED = 101; static final int MSG_CLOSE_SYSTEM_DIALOGS = 102; static final int MSG_DESTROY = 103; static final int MSG_HANDLE_ASSIST = 104; - static final int MSG_SHOW = 105; - static final int MSG_HIDE = 106; + static final int MSG_HANDLE_SCREENSHOT = 105; + static final int MSG_SHOW = 106; + static final int MSG_HIDE = 107; class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback { @Override @@ -309,6 +346,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, (Bundle)args.arg4); break; + case MSG_START_PICK_OPTION: + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "onPickOption: req=" + ((Request) args.arg2).mInterface + + " prompt=" + args.arg3 + " options=" + args.arg4 + + " extras=" + args.arg5); + onPickOption((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3, + (VoiceInteractor.PickOptionRequest.Option[])args.arg4, + (Bundle)args.arg5); + break; case MSG_START_COMPLETE_VOICE: args = (SomeArgs)msg.obj; if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface @@ -358,9 +404,13 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { doDestroy(); break; case MSG_HANDLE_ASSIST: - if (DEBUG) Log.d(TAG, "onHandleAssist: " + (Bundle)msg.obj); + if (DEBUG) Log.d(TAG, "onHandleAssist: " + msg.obj); onHandleAssist((Bundle) msg.obj); break; + case MSG_HANDLE_SCREENSHOT: + if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj); + onHandleScreenshot((Bitmap) msg.obj); + break; case MSG_SHOW: if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj + " flags=" + msg.arg1); @@ -614,6 +664,26 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { } /** + * Set whether this session will keep the device awake while it is running a voice + * activity. By default, the system holds a wake lock for it while in this state, + * so that it can work even if the screen is off. Setting this to false removes that + * wake lock, allowing the CPU to go to sleep. This is typically used if the + * session decides it has been waiting too long for a response from the user and + * doesn't want to let this continue to drain the battery. + * + * <p>Passing false here will release the wake lock, and you can call later with + * true to re-acquire it. It will also be automatically re-acquired for you each + * time you start a new voice activity task -- that is when you call + * {@link #startVoiceActivity}.</p> + */ + public void setKeepAwake(boolean keepAwake) { + try { + mSystemService.setKeepAwake(mToken, keepAwake); + } catch (RemoteException e) { + } + } + + /** * Convenience for inflating views. */ public LayoutInflater getLayoutInflater() { @@ -710,6 +780,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { public void onHandleAssist(Bundle assistBundle) { } + public void onHandleScreenshot(Bitmap screenshot) { + } + public boolean onKeyDown(int keyCode, KeyEvent event) { return false; } @@ -814,6 +887,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { Bundle extras); /** + * Request for the user to pick one of N options, corresponding to a + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * + * @param caller Who is making the request. + * @param request The active request. + * @param prompt The prompt informing the user of what they are picking, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * @param options The set of options the user is picking from, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + * @param extras Any additional information, as per + * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}. + */ + public abstract void onPickOption(Caller caller, Request request, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras); + + /** * Request to complete the voice interaction session because the voice activity successfully * completed its interaction using voice. Corresponds to * {@link android.app.VoiceInteractor.CompleteVoiceRequest diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 668e028..13fb657 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -918,7 +918,7 @@ public class TextToSpeech { * * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. */ - public int addSpeech(CharSequence text, String packagename, int resourceId) { + public int addSpeech(CharSequence text, String packagename, @RawRes int resourceId) { synchronized (mStartLock) { mUtterances.put(text, makeResourceUri(packagename, resourceId)); return SUCCESS; diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 8e9eb48..c119277 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -525,10 +525,6 @@ public class Linkify { return 0; } - - public final boolean equals(Object o) { - return false; - } }; Collections.sort(links, c); diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 9009d6a..a7d9503 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -16,6 +16,7 @@ package android.transition; +import android.annotation.TransitionRes; import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; @@ -71,7 +72,8 @@ public class TransitionInflater { * @throws android.content.res.Resources.NotFoundException when the * transition cannot be loaded */ - public Transition inflateTransition(int resource) { + public Transition inflateTransition(@TransitionRes int resource) { + //noinspection ResourceType XmlResourceParser parser = mContext.getResources().getXml(resource); try { return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null); @@ -98,7 +100,9 @@ public class TransitionInflater { * @throws android.content.res.Resources.NotFoundException when the * transition manager cannot be loaded */ - public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) { + public TransitionManager inflateTransitionManager(@TransitionRes int resource, + ViewGroup sceneRoot) { + //noinspection ResourceType XmlResourceParser parser = mContext.getResources().getXml(resource); try { return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot); diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index 92b19be..18dc262 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -164,7 +164,7 @@ public class PathParser { * @return array of floats */ private static float[] getFloats(String s) { - if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') { + if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') { return new float[0]; } try { diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java index a018138..9f202a9 100644 --- a/core/java/android/view/ActionMode.java +++ b/core/java/android/view/ActionMode.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.StringRes; +import android.graphics.Rect; /** * Represents a contextual mode of the user interface. Action modes can be used to provide @@ -197,6 +198,15 @@ public abstract class ActionMode { public abstract void invalidate(); /** + * Invalidate the content rect associated to this ActionMode. This only makes sense for + * action modes that support dynamic positioning on the screen, and provides a more efficient + * way to reposition it without invalidating the whole action mode. + * + * @see Callback2#onGetContentRect(ActionMode, View, Rect) . + */ + public void invalidateContentRect() {} + + /** * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will * have its {@link Callback#onDestroyActionMode(ActionMode)} method called. */ @@ -298,4 +308,31 @@ public abstract class ActionMode { */ public void onDestroyActionMode(ActionMode mode); } -}
\ No newline at end of file + + /** + * Extension of {@link ActionMode.Callback} to provide content rect information. This is + * required for ActionModes with dynamic positioning such as the ones with type + * {@link ActionMode#TYPE_FLOATING} to ensure the positioning doesn't obscure app content. If + * an app fails to provide a subclass of this class, a default implementation will be used. + */ + public static abstract class Callback2 implements ActionMode.Callback { + + /** + * Called when an ActionMode needs to be positioned on screen, potentially occluding view + * content. Note this may be called on a per-frame basis. + * + * @param mode The ActionMode that requires positioning. + * @param view The View that originated the ActionMode, in whose coordinates the Rect should + * be provided. + * @param outRect The Rect to be populated with the content position. + */ + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + if (view != null) { + outRect.set(0, 0, view.getWidth(), view.getHeight()); + } else { + outRect.set(0, 0, 0, 0); + } + } + + } +} diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index d9f6054..9047b1d 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.StyleRes; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Configuration; @@ -36,7 +37,7 @@ public class ContextThemeWrapper extends ContextWrapper { super(null); } - public ContextThemeWrapper(Context base, int themeResId) { + public ContextThemeWrapper(Context base, @StyleRes int themeResId) { super(base); mThemeResource = themeResId; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8ac8bc5..d6625c8 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -16,6 +16,7 @@ package android.view; +import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -220,10 +221,14 @@ interface IWindowManager boolean isRotationFrozen(); /** + * Used only for assist -- request a screenshot of the current application. + */ + boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver); + + /** * Create a screenshot of the applications currently displayed. */ - Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, - int maxHeight, boolean force565); + Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight); /** * Called by the status bar to notify Views of changes to System UI visiblity. diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index 8aef18a..543021e 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -1759,7 +1759,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } else { MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( mVolumeControlStreamType, direction, - AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE); + AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE + | AudioManager.FLAG_FROM_KEY); } return true; } @@ -1837,15 +1838,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: { + final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE + | AudioManager.FLAG_FROM_KEY; // If we have a session send it the volume command, otherwise // use the suggested stream. if (mMediaController != null) { - mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND - | AudioManager.FLAG_VIBRATE); + mMediaController.adjustVolume(0, flags); } else { MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy( - mVolumeControlStreamType, 0, - AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE); + mVolumeControlStreamType, 0, flags); } return true; } @@ -2669,17 +2670,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) { - // originalView can be used here to be sure that we don't obscure - // relevant content with the context mode UI. - return startActionMode(callback); + return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY); } @Override public ActionMode startActionModeForChild( View child, ActionMode.Callback callback, int type) { - // originalView can be used here to be sure that we don't obscure - // relevant content with the context mode UI. - return startActionMode(callback, type); + return startActionMode(child, callback, type); } @Override @@ -2689,7 +2686,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public ActionMode startActionMode(ActionMode.Callback callback, int type) { - ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); + return startActionMode(this, callback, type); + } + + private ActionMode startActionMode( + View originatingView, ActionMode.Callback callback, int type) { + ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); ActionMode mode = null; if (getCallback() != null && !isDestroyed()) { try { @@ -3291,12 +3293,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } /** - * Clears out internal reference when the action mode is destroyed. + * Clears out internal references when the action mode is destroyed. */ - private class ActionModeCallbackWrapper implements ActionMode.Callback { - private ActionMode.Callback mWrapped; + private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { + private final ActionMode.Callback mWrapped; - public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { + public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { mWrapped = wrapped; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3927096..d345bed 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -24,6 +24,7 @@ import android.annotation.DrawableRes; import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.IntDef; +import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; @@ -18646,7 +18647,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * layout_* parameters. * @see LayoutInflater */ - public static View inflate(Context context, int resource, ViewGroup root) { + public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root); } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index f36fd5a..9a92932 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -22,6 +22,7 @@ import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StyleRes; import android.annotation.SystemApi; import android.content.Context; import android.content.res.Configuration; @@ -753,7 +754,7 @@ public abstract class Window { * 0 here will override the animations the window would * normally retrieve from its theme. */ - public void setWindowAnimations(int resId) { + public void setWindowAnimations(@StyleRes int resId) { final WindowManager.LayoutParams attrs = getAttributes(); attrs.windowAnimations = resId; dispatchWindowAttributesChanged(attrs); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 905d6d7..66dae7b 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1989,7 +1989,8 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } - if (!surfaceInsets.equals(Insets.NONE) || hasManualSurfaceInsets) { + if (surfaceInsets.left != 0 || surfaceInsets.top != 0 || surfaceInsets.right != 0 || + surfaceInsets.bottom != 0 || hasManualSurfaceInsets) { sb.append(" surfaceInsets=").append(surfaceInsets); if (hasManualSurfaceInsets) { sb.append(" (manual)"); diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index e1942be..a75e8a7 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -573,7 +573,7 @@ public final class AccessibilityWindowInfo implements Parcelable { if (other.mType != mType) { throw new IllegalArgumentException("Not same type."); } - if (!mBoundsInScreen.equals(mBoundsInScreen)) { + if (!mBoundsInScreen.equals(other.mBoundsInScreen)) { return true; } if (mLayer != other.mLayer) { diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index a5524d8..be43952 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -17,6 +17,7 @@ package android.view.animation; import android.annotation.ColorInt; +import android.annotation.InterpolatorRes; import android.content.Context; import android.content.res.TypedArray; import android.graphics.RectF; @@ -388,7 +389,7 @@ public abstract class Animation implements Cloneable { * @param resID The resource identifier of the interpolator to load * @attr ref android.R.styleable#Animation_interpolator */ - public void setInterpolator(Context context, int resID) { + public void setInterpolator(Context context, @InterpolatorRes int resID) { setInterpolator(AnimationUtils.loadInterpolator(context, resID)); } diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java index 882e738..df2f18c 100644 --- a/core/java/android/view/animation/LayoutAnimationController.java +++ b/core/java/android/view/animation/LayoutAnimationController.java @@ -16,6 +16,8 @@ package android.view.animation; +import android.annotation.AnimRes; +import android.annotation.InterpolatorRes; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; @@ -180,7 +182,7 @@ public class LayoutAnimationController { * * @attr ref android.R.styleable#LayoutAnimation_animation */ - public void setAnimation(Context context, int resourceID) { + public void setAnimation(Context context, @AnimRes int resourceID) { setAnimation(AnimationUtils.loadAnimation(context, resourceID)); } @@ -225,7 +227,7 @@ public class LayoutAnimationController { * * @attr ref android.R.styleable#LayoutAnimation_interpolator */ - public void setInterpolator(Context context, int resourceID) { + public void setInterpolator(Context context, @InterpolatorRes int resourceID) { setInterpolator(AnimationUtils.loadInterpolator(context, resourceID)); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 67ad642..6711a6b 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -364,6 +364,20 @@ public class WebView extends AbsoluteLayout } /** + * Callback interface supplied to {@link #insertVisualStateCallback} for receiving + * notifications about the visual state. + */ + public static abstract class VisualStateCallback { + /** + * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}. + * + * @param requestId the id supplied to the corresponding {@link #insertVisualStateCallback} + * request + */ + public abstract void onComplete(long requestId); + } + + /** * Interface to listen for new pictures as they change. * * @deprecated This interface is now obsolete. @@ -1144,6 +1158,60 @@ public class WebView extends AbsoluteLayout } /** + * Inserts a {@link VisualStateCallback}. + * + * <p>Updates to the the DOM are reflected asynchronously such that when the DOM is updated the + * subsequent {@link WebView#onDraw} invocation might not reflect those updates. The + * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of + * the DOM at the current time are ready to be drawn the next time the {@link WebView} draws. + * By current time we mean the time at which this API was called. The next draw after the + * callback completes is guaranteed to reflect all the updates to the DOM applied before the + * current time, but it may also contain updates applied after the current time.</p> + * + * <p>The state of the DOM covered by this API includes the following: + * <ul> + * <li>primitive HTML elements (div, img, span, etc..)</li> + * <li>images</li> + * <li>CSS animations</li> + * <li>WebGL</li> + * <li>canvas</li> + * </ul> + * It does not include the state of: + * <ul> + * <li>the video tag</li> + * </ul></p> + * + * <p>To guarantee that the {@link WebView} will successfully render the first frame + * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions + * must be met: + * <ul> + * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then + * the {@link WebView} must be attached to the view hierarchy.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE} + * then the {@link WebView} must be attached to the view hierarchy and must be made + * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the + * {@link WebView} must be attached to the view hierarchy and its + * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed + * values and must be made {@link View#VISIBLE VISIBLE} from the + * {@link VisualStateCallback#onComplete} method.</li> + * </ul></p> + * + * <p>When using this API it is also recommended to enable pre-rasterization if the + * {@link WebView} is offscreen to avoid flickering. See WebSettings#setOffscreenPreRaster for + * more details and do consider its caveats.</p> + * + * @param requestId an id that will be returned in the callback to allow callers to match + * requests with callbacks. + * @param callback the callback to be invoked. + */ + public void insertVisualStateCallback(long requestId, VisualStateCallback callback) { + checkThread(); + if (TRACE) Log.d(LOGTAG, "insertVisualStateCallback"); + mProvider.insertVisualStateCallback(requestId, callback); + } + + /** * Clears this WebView so that onDraw() will draw nothing but white background, * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY. * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 34b8cf6..53c7e04 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -83,6 +83,32 @@ public class WebViewClient { } /** + * Notify the host application that the page commit is visible. + * + * <p>This is the earliest point at which we can guarantee that the contents of the previously + * loaded page will not longer be drawn in the next {@link WebView#onDraw}. The next draw will + * render the {@link WebView#setBackgroundColor background color} of the WebView or some of the + * contents from the committed page already. This callback may be useful when reusing + * {@link WebView}s to ensure that no stale content is shown. This method is only called for + * the main frame.</p> + * + * <p>This method is called when the state of the DOM at the point at which the + * body of the HTTP response (commonly the string of html) had started loading will be visible. + * If you set a background color for the page in the HTTP response body this will most likely + * be visible and perhaps some other elements. At that point no other resources had usually + * been loaded, so you can expect images for example to not be visible. If you want + * a finer level of granularity consider calling {@link WebView#insertVisualStateCallback} + * directly.</p> + * + * <p>Please note that all the conditions and recommendations presented in + * {@link WebView#insertVisualStateCallback} also apply to this API.<p> + * + * @param url the url of the committed page + */ + public void onPageCommitVisible(WebView view, String url) { + } + + /** * Notify the host application of a resource request and allow the * application to return the data. If the return value is null, the WebView * will continue to load the resource as usual. Otherwise, the return diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 0cdb875..fa2ce1b 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -40,6 +40,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.webkit.WebView.HitTestResult; import android.webkit.WebView.PictureListener; +import android.webkit.WebView.VisualStateCallback; + import java.io.BufferedWriter; import java.io.File; @@ -146,6 +148,8 @@ public interface WebViewProvider { public boolean pageDown(boolean bottom); + public void insertVisualStateCallback(long requestId, VisualStateCallback callback); + public void clearView(); public Picture capturePicture(); diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 9d3a5dc..d6f2276 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -15,6 +15,7 @@ */ package android.widget; +import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -86,7 +87,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo * @param resId theme used to inflate popup menus * @see #getPopupTheme() */ - public void setPopupTheme(int resId) { + public void setPopupTheme(@StyleRes int resId) { if (mPopupTheme != resId) { mPopupTheme = resId; if (resId == 0) { diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java index 89e508f..ae94a10 100644 --- a/core/java/android/widget/ArrayAdapter.java +++ b/core/java/android/widget/ArrayAdapter.java @@ -19,6 +19,7 @@ package android.widget; import android.annotation.ArrayRes; import android.annotation.IdRes; import android.annotation.LayoutRes; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.util.Log; @@ -133,7 +134,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, * instantiating views. * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, T[] objects) { + public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull T[] objects) { this(context, resource, 0, Arrays.asList(objects)); } @@ -146,7 +147,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, * @param textViewResourceId The id of the TextView within the layout resource to be populated * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, T[] objects) { + public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, + @NonNull T[] objects) { this(context, resource, textViewResourceId, Arrays.asList(objects)); } @@ -158,7 +160,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, * instantiating views. * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, List<T> objects) { + public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull List<T> objects) { this(context, resource, 0, objects); } @@ -171,7 +173,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, * @param textViewResourceId The id of the TextView within the layout resource to be populated * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) { + public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, + @NonNull List<T> objects) { mContext = context; mInflater = LayoutInflater.from(context); mResource = mDropDownResource = resource; diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index fe143de..133e102 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -326,7 +327,7 @@ class FastScroller { refreshDrawablePressedState(); } - public void setStyle(int resId) { + public void setStyle(@StyleRes int resId) { final Context context = mList.getContext(); final TypedArray ta = context.obtainStyledAttributes(null, com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId); diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java index a656712..2008ba8 100644 --- a/core/java/android/widget/SimpleAdapter.java +++ b/core/java/android/widget/SimpleAdapter.java @@ -16,6 +16,8 @@ package android.widget; +import android.annotation.IdRes; +import android.annotation.LayoutRes; import android.content.Context; import android.content.res.Resources; import android.view.ContextThemeWrapper; @@ -82,7 +84,7 @@ public class SimpleAdapter extends BaseAdapter implements Filterable, Spinner.Th * in the from parameter. */ public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, - int resource, String[] from, int[] to) { + @LayoutRes int resource, String[] from, @IdRes int[] to) { mData = data; mResource = mDropDownResource = resource; mFrom = from; diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index d2430bc..087406a 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -17,8 +17,12 @@ package android.widget; import android.annotation.ColorInt; +import android.annotation.DrawableRes; +import android.annotation.MenuRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; +import android.annotation.StyleRes; import android.app.ActionBar; import android.content.Context; import android.content.res.ColorStateList; @@ -296,7 +300,7 @@ public class Toolbar extends ViewGroup { * @param resId theme used to inflate popup menus * @see #getPopupTheme() */ - public void setPopupTheme(int resId) { + public void setPopupTheme(@StyleRes int resId) { if (mPopupTheme != resId) { mPopupTheme = resId; if (resId == 0) { @@ -331,7 +335,7 @@ public class Toolbar extends ViewGroup { * * @param resId ID of a drawable resource */ - public void setLogo(int resId) { + public void setLogo(@DrawableRes int resId) { setLogo(getContext().getDrawable(resId)); } @@ -481,7 +485,7 @@ public class Toolbar extends ViewGroup { * * @param resId String resource id */ - public void setLogoDescription(int resId) { + public void setLogoDescription(@StringRes int resId) { setLogoDescription(getContext().getText(resId)); } @@ -566,7 +570,7 @@ public class Toolbar extends ViewGroup { * * @param resId Resource ID of a string to set as the title */ - public void setTitle(int resId) { + public void setTitle(@StringRes int resId) { setTitle(getContext().getText(resId)); } @@ -621,7 +625,7 @@ public class Toolbar extends ViewGroup { * * @param resId String resource ID */ - public void setSubtitle(int resId) { + public void setSubtitle(@StringRes int resId) { setSubtitle(getContext().getText(resId)); } @@ -663,7 +667,7 @@ 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) { + public void setTitleTextAppearance(Context context, @StyleRes int resId) { mTitleTextAppearance = resId; if (mTitleTextView != null) { mTitleTextView.setTextAppearance(context, resId); @@ -674,7 +678,7 @@ public class Toolbar extends ViewGroup { * Sets the text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. */ - public void setSubtitleTextAppearance(Context context, int resId) { + public void setSubtitleTextAppearance(Context context, @StyleRes int resId) { mSubtitleTextAppearance = resId; if (mSubtitleTextView != null) { mSubtitleTextView.setTextAppearance(context, resId); @@ -729,7 +733,7 @@ public class Toolbar extends ViewGroup { * * @attr ref android.R.styleable#Toolbar_navigationContentDescription */ - public void setNavigationContentDescription(int resId) { + public void setNavigationContentDescription(@StringRes int resId) { setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null); } @@ -766,7 +770,7 @@ public class Toolbar extends ViewGroup { * * @attr ref android.R.styleable#Toolbar_navigationIcon */ - public void setNavigationIcon(int resId) { + public void setNavigationIcon(@DrawableRes int resId) { setNavigationIcon(getContext().getDrawable(resId)); } @@ -972,7 +976,7 @@ public class Toolbar extends ViewGroup { * * @param resId ID of a menu resource to inflate */ - public void inflateMenu(int resId) { + public void inflateMenu(@MenuRes int resId) { getMenuInflater().inflate(resId, getMenu()); } diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java index f30fdd8..1580f51 100644 --- a/core/java/android/widget/ViewAnimator.java +++ b/core/java/android/widget/ViewAnimator.java @@ -17,6 +17,7 @@ package android.widget; +import android.annotation.AnimRes; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; @@ -309,7 +310,7 @@ public class ViewAnimator extends FrameLayout { * @see #getInAnimation() * @see #setInAnimation(android.view.animation.Animation) */ - public void setInAnimation(Context context, int resourceID) { + public void setInAnimation(Context context, @AnimRes int resourceID) { setInAnimation(AnimationUtils.loadAnimation(context, resourceID)); } @@ -322,7 +323,7 @@ public class ViewAnimator extends FrameLayout { * @see #getOutAnimation() * @see #setOutAnimation(android.view.animation.Animation) */ - public void setOutAnimation(Context context, int resourceID) { + public void setOutAnimation(Context context, @AnimRes int resourceID) { setOutAnimation(AnimationUtils.loadAnimation(context, resourceID)); } |
