summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-09-18 17:38:50 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-09-18 17:38:50 -0400
commitbc81c7ada5aab3806dd0b17498f5c9672c9b33c4 (patch)
tree7fdcc541a9ac9e92134f1a80cec557fee772bcf8 /android
parent10d07c88d69cc64f73a069163e7ea5ba2519a099 (diff)
downloadandroid-28-bc81c7ada5aab3806dd0b17498f5c9672c9b33c4.tar.gz
Import Android SDK Platform P [4344336]
/google/data/ro/projects/android/fetch_artifact \ --bid 4344336 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4344336.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If482fcd4cfaf6c5e544e5574926be25a293e9a6d
Diffstat (limited to 'android')
-rw-r--r--android/app/FragmentManager.java9
-rw-r--r--android/app/LoadedApk.java20
-rw-r--r--android/app/Notification.java36
-rw-r--r--android/app/RemoteInput.java4
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/LiveData.java411
-rw-r--r--android/arch/paging/LivePagedListProvider.java111
-rw-r--r--android/arch/paging/PagedListAdapter.java4
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java4
-rw-r--r--android/arch/paging/integration/testapp/PagedListSampleActivity.java3
-rw-r--r--android/arch/persistence/db/SimpleSQLiteQuery.java2
-rw-r--r--android/arch/persistence/db/SupportSQLiteQueryBuilder.java2
-rw-r--r--android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java2
-rw-r--r--android/arch/persistence/room/ColumnInfo.java59
-rw-r--r--android/arch/persistence/room/Relation.java4
-rw-r--r--android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java3
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/UserDao.java3
-rw-r--r--android/arch/persistence/room/integration/testapp/migration/MigrationDb.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/migration/MigrationTest.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java10
-rw-r--r--android/arch/persistence/room/integration/testapp/vo/User.java2
-rw-r--r--android/bluetooth/BluetoothGatt.java88
-rw-r--r--android/bluetooth/BluetoothGattServerCallback.java4
-rw-r--r--android/content/pm/ApplicationInfo.java33
-rw-r--r--android/content/pm/PackageParser.java29
-rw-r--r--android/content/pm/PermissionInfo.java36
-rw-r--r--android/graphics/BitmapFactory.java7
-rw-r--r--android/graphics/drawable/VectorDrawable.java61
-rw-r--r--android/media/AudioManager.java6
-rw-r--r--android/media/AudioPlaybackConfiguration.java52
-rw-r--r--android/media/MediaMuxer.java2
-rw-r--r--android/media/MediaPlayer.java124
-rw-r--r--android/media/MediaRouter.java4
-rw-r--r--android/net/LinkProperties.java72
-rw-r--r--android/net/metrics/WakeupEvent.java34
-rw-r--r--android/net/metrics/WakeupStats.java87
-rw-r--r--android/os/BatteryStats.java94
-rw-r--r--android/os/Process.java14
-rw-r--r--android/os/ServiceManager.java101
-rw-r--r--android/os/StrictMode.java2
-rw-r--r--android/os/ZygoteProcess.java42
-rw-r--r--android/os/storage/StorageManager.java2
-rw-r--r--android/os/storage/VolumeInfo.java30
-rw-r--r--android/provider/Settings.java67
-rw-r--r--android/service/settings/suggestions/Suggestion.java152
-rw-r--r--android/service/settings/suggestions/SuggestionService.java71
-rw-r--r--android/support/v17/leanback/media/PlaybackTransportControlGlue.java4
-rw-r--r--android/support/v4/app/FragmentManager.java40
-rw-r--r--android/support/v4/media/MediaBrowserServiceCompat.java24
-rw-r--r--android/support/v4/os/BuildCompat.java5
-rw-r--r--android/support/v7/preference/CollapsiblePreferenceGroupController.java226
-rw-r--r--android/support/v7/preference/PreferenceGroup.java103
-rw-r--r--android/support/v7/preference/PreferenceGroupAdapter.java30
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapter.java2
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapterHelper.java2
-rw-r--r--android/support/v7/view/menu/CascadingMenuPopup.java31
-rw-r--r--android/support/v7/widget/AppCompatImageButton.java9
-rw-r--r--android/support/v7/widget/AppCompatImageView.java9
-rw-r--r--android/support/v7/widget/RecyclerView.java77
-rw-r--r--android/telecom/DefaultDialerManager.java6
-rw-r--r--android/telephony/MbmsDownloadManager.java586
-rw-r--r--android/telephony/MbmsDownloadSession.java773
-rw-r--r--android/telephony/MbmsStreamingSession.java (renamed from android/telephony/MbmsStreamingManager.java)201
-rw-r--r--android/telephony/mbms/DownloadRequest.java92
-rw-r--r--android/telephony/mbms/DownloadStateCallback.java (renamed from android/telephony/mbms/DownloadProgressListener.java)36
-rw-r--r--android/telephony/mbms/FileInfo.java23
-rw-r--r--android/telephony/mbms/FileServiceInfo.java18
-rw-r--r--android/telephony/mbms/InternalDownloadSessionCallback.java86
-rw-r--r--android/telephony/mbms/InternalDownloadStateCallback.java70
-rw-r--r--android/telephony/mbms/InternalStreamingServiceCallback.java35
-rw-r--r--android/telephony/mbms/InternalStreamingSessionCallback.java (renamed from android/telephony/mbms/InternalStreamingManagerCallback.java)32
-rw-r--r--android/telephony/mbms/MbmsDownloadManagerCallback.java64
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java233
-rw-r--r--android/telephony/mbms/MbmsDownloadSessionCallback.java66
-rw-r--r--android/telephony/mbms/MbmsErrors.java (renamed from android/telephony/mbms/MbmsException.java)32
-rw-r--r--android/telephony/mbms/MbmsStreamingSessionCallback.java (renamed from android/telephony/mbms/MbmsStreamingManagerCallback.java)22
-rw-r--r--android/telephony/mbms/MbmsTempFileProvider.java8
-rw-r--r--android/telephony/mbms/MbmsUtils.java7
-rw-r--r--android/telephony/mbms/ServiceInfo.java29
-rw-r--r--android/telephony/mbms/StreamingService.java66
-rw-r--r--android/telephony/mbms/StreamingServiceCallback.java6
-rw-r--r--android/telephony/mbms/UriPathPair.java28
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java235
-rw-r--r--android/telephony/mbms/vendor/MbmsStreamingServiceBase.java92
-rw-r--r--android/telephony/mbms/vendor/VendorUtils.java (renamed from android/telephony/mbms/vendor/VendorIntents.java)56
-rw-r--r--android/text/InputType.java4
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/view/SurfaceView.java1136
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/inputmethod/ExtractedText.java29
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java244
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/WebView.java2887
-rw-r--r--android/widget/DatePickerCalendarDelegate.java46
-rw-r--r--android/widget/Editor.java84
-rw-r--r--android/widget/TextView.java13
96 files changed, 4083 insertions, 7011 deletions
diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java
index ba5ea218..0d5cd021 100644
--- a/android/app/FragmentManager.java
+++ b/android/app/FragmentManager.java
@@ -1380,8 +1380,13 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
@Override
public void onAnimationEnd(Animator anim) {
container.endViewTransition(view);
- if (fragment.getAnimatingAway() != null) {
- fragment.setAnimatingAway(null);
+ Animator animator = f.getAnimatingAway();
+ f.setAnimatingAway(null);
+ // If the animation finished immediately, the fragment's
+ // view will still be there. If so, we can just pretend
+ // there was no animation and skip the moveToState()
+ if (container.indexOfChild(view) == -1
+ && animator != null) {
moveToState(fragment, fragment.getStateAfterAnimating(),
0, 0, false);
}
diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java
index b38be662..f6d9710d 100644
--- a/android/app/LoadedApk.java
+++ b/android/app/LoadedApk.java
@@ -625,17 +625,31 @@ public final class LoadedApk {
final List<String> zipPaths = new ArrayList<>(10);
final List<String> libPaths = new ArrayList<>(10);
- final boolean isBundledApp = mApplicationInfo.isSystemApp()
+ boolean isBundledApp = mApplicationInfo.isSystemApp()
&& !mApplicationInfo.isUpdatedSystemApp();
+ // Vendor apks are treated as bundled only when /vendor/lib is in the default search
+ // paths. If not, they are treated as unbundled; access to system libs is limited.
+ // Having /vendor/lib in the default search paths means that all system processes
+ // are allowed to use any vendor library, which in turn means that system is dependent
+ // on vendor partition. In the contrary, not having /vendor/lib in the default search
+ // paths mean that the two partitions are separated and thus we can treat vendor apks
+ // as unbundled.
+ final String defaultSearchPaths = System.getProperty("java.library.path");
+ final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
+ if (mApplicationInfo.getCodePath() != null
+ && mApplicationInfo.getCodePath().startsWith("/vendor/")
+ && treatVendorApkAsUnbundled) {
+ isBundledApp = false;
+ }
+
makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
String libraryPermittedPath = mDataDir;
if (isBundledApp) {
// This is necessary to grant bundled apps access to
// libraries located in subdirectories of /system/lib
- libraryPermittedPath += File.pathSeparator +
- System.getProperty("java.library.path");
+ libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
}
final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 841b9611..ee6c1cba 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -67,6 +67,7 @@ import android.text.style.TextAppearanceSpan;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -3905,6 +3906,7 @@ public class Notification implements Parcelable
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
+ updateTextSizePrimary(contentView, R.id.title);
if (!p.ambient) {
setTextViewColorPrimary(contentView, R.id.title);
}
@@ -3916,6 +3918,7 @@ public class Notification implements Parcelable
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, processTextSpans(p.text));
+ updateTextSizeSecondary(contentView, textId);
if (!p.ambient) {
setTextViewColorSecondary(contentView, textId);
}
@@ -3927,6 +3930,25 @@ public class Notification implements Parcelable
return contentView;
}
+ private void updateTextSizeSecondary(RemoteViews contentView, int textId) {
+ updateTextSizeColorized(contentView, textId,
+ com.android.internal.R.dimen.notification_text_size_colorized,
+ com.android.internal.R.dimen.notification_text_size);
+ }
+
+ private void updateTextSizePrimary(RemoteViews contentView, int textId) {
+ updateTextSizeColorized(contentView, textId,
+ com.android.internal.R.dimen.notification_title_text_size_colorized,
+ com.android.internal.R.dimen.notification_title_text_size);
+ }
+
+ private void updateTextSizeColorized(RemoteViews contentView, int textId,
+ int colorizedDimen, int normalDimen) {
+ int size = mContext.getResources().getDimensionPixelSize(isColorized()
+ ? colorizedDimen : normalDimen);
+ contentView.setTextViewTextSize(textId, TypedValue.COMPLEX_UNIT_PX, size);
+ }
+
private CharSequence processTextSpans(CharSequence text) {
if (hasForegroundColor()) {
return NotificationColorUtil.clearColorSpans(text);
@@ -5852,6 +5874,7 @@ public class Notification implements Parcelable
builder.setTextViewColorSecondary(contentView, R.id.big_text);
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
+ builder.updateTextSizeSecondary(contentView, R.id.big_text);
contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon());
}
}
@@ -6185,6 +6208,7 @@ public class Notification implements Parcelable
contentView.setViewVisibility(rowId, View.VISIBLE);
contentView.setTextViewText(rowId, mBuilder.processTextSpans(
makeMessageLine(m, mBuilder)));
+ mBuilder.updateTextSizeSecondary(contentView, rowId);
mBuilder.setTextViewColorSecondary(contentView, rowId);
if (contractedMessage == m) {
@@ -6552,6 +6576,7 @@ public class Notification implements Parcelable
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i],
mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
+ mBuilder.updateTextSizeSecondary(contentView, rowIds[i]);
mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
handleInboxImageMargin(contentView, rowIds[i], first);
@@ -8522,8 +8547,15 @@ public class Notification implements Parcelable
final StandardTemplateParams fillTextsFrom(Builder b) {
Bundle extras = b.mN.extras;
- title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE), ambient);
- text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT), ambient);
+ this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE), ambient);
+
+ // Big text notifications should contain their content when viewed in ambient mode.
+ CharSequence text = extras.getCharSequence(EXTRA_BIG_TEXT);
+ if (!ambient || TextUtils.isEmpty(text)) {
+ text = extras.getCharSequence(EXTRA_TEXT);
+ }
+ this.text = b.processLegacyText(text, ambient);
+
return this;
}
}
diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java
index 8ab19c06..02a01242 100644
--- a/android/app/RemoteInput.java
+++ b/android/app/RemoteInput.java
@@ -33,8 +33,8 @@ import java.util.Set;
* an intent inside a {@link android.app.PendingIntent} that is sent.
* Always use {@link RemoteInput.Builder} to create instances of this class.
* <p class="note"> See
- * <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from
- * a Notification</a> for more information on how to use this class.
+ * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#direct">Replying
+ * to notifications</a> for more information on how to use this class.
*
* <p>The following example adds a {@code RemoteInput} to a {@link Notification.Action},
* sets the result key as {@code quick_reply}, and sets the label as {@code Quick reply}.
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f1352446..fe18243f 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,9 +1,136 @@
-//ComputableLiveData interface for tests
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.arch.lifecycle;
-import android.arch.lifecycle.LiveData;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A LiveData class that can be invalidated & computed on demand.
+ * <p>
+ * This is an internal class for now, might be public if we see the necessity.
+ *
+ * @param <T> The type of the live data
+ * @hide internal
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ComputableLiveData<T> {
- public ComputableLiveData(){}
- abstract protected T compute();
- public LiveData<T> getLiveData() {return null;}
- public void invalidate() {}
+
+ private final LiveData<T> mLiveData;
+
+ private AtomicBoolean mInvalid = new AtomicBoolean(true);
+ private AtomicBoolean mComputing = new AtomicBoolean(false);
+
+ /**
+ * Creates a computable live data which is computed when there are active observers.
+ * <p>
+ * It can also be invalidated via {@link #invalidate()} which will result in a call to
+ * {@link #compute()} if there are active observers (or when they start observing)
+ */
+ @SuppressWarnings("WeakerAccess")
+ public ComputableLiveData() {
+ mLiveData = new LiveData<T>() {
+ @Override
+ protected void onActive() {
+ // TODO if we make this class public, we should accept an executor
+ AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ };
+ }
+
+ /**
+ * Returns the LiveData managed by this class.
+ *
+ * @return A LiveData that is controlled by ComputableLiveData.
+ */
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LiveData<T> getLiveData() {
+ return mLiveData;
+ }
+
+ @VisibleForTesting
+ final Runnable mRefreshRunnable = new Runnable() {
+ @WorkerThread
+ @Override
+ public void run() {
+ boolean computed;
+ do {
+ computed = false;
+ // compute can happen only in 1 thread but no reason to lock others.
+ if (mComputing.compareAndSet(false, true)) {
+ // as long as it is invalid, keep computing.
+ try {
+ T value = null;
+ while (mInvalid.compareAndSet(true, false)) {
+ computed = true;
+ value = compute();
+ }
+ if (computed) {
+ mLiveData.postValue(value);
+ }
+ } finally {
+ // release compute lock
+ mComputing.set(false);
+ }
+ }
+ // check invalid after releasing compute lock to avoid the following scenario.
+ // Thread A runs compute()
+ // Thread A checks invalid, it is false
+ // Main thread sets invalid to true
+ // Thread B runs, fails to acquire compute lock and skips
+ // Thread A releases compute lock
+ // We've left invalid in set state. The check below recovers.
+ } while (computed && mInvalid.get());
+ }
+ };
+
+ // invalidation check always happens on the main thread
+ @VisibleForTesting
+ final Runnable mInvalidationRunnable = new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ boolean isActive = mLiveData.hasActiveObservers();
+ if (mInvalid.compareAndSet(false, true)) {
+ if (isActive) {
+ // TODO if we make this class public, we should accept an executor.
+ AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ }
+ }
+ };
+
+ /**
+ * Invalidates the LiveData.
+ * <p>
+ * When there are active observers, this will trigger a call to {@link #compute()}.
+ */
+ public void invalidate() {
+ AppToolkitTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @WorkerThread
+ protected abstract T compute();
}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 3aea6acb..99d859c4 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,411 @@
-//LiveData interface for tests
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.arch.lifecycle;
-public class LiveData<T> {
+
+import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * LiveData is a data holder class that can be observed within a given lifecycle.
+ * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
+ * this observer will be notified about modifications of the wrapped data only if the paired
+ * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
+ * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
+ * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
+ * about modifications. For those observers, you should manually call
+ * {@link #removeObserver(Observer)}.
+ *
+ * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
+ * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
+ * activities and fragments where they can safely observe LiveData and not worry about leaks:
+ * they will be instantly unsubscribed when they are destroyed.
+ *
+ * <p>
+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
+ * to get notified when number of active {@link Observer}s change between 0 and 1.
+ * This allows LiveData to release any heavy resources when it does not have any Observers that
+ * are actively observing.
+ * <p>
+ * This class is designed to hold individual data fields of {@link ViewModel},
+ * but can also be used for sharing data between different modules in your application
+ * in a decoupled fashion.
+ *
+ * @param <T> The type of data hold by this instance
+ * @see ViewModel
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
+// thread.
+public abstract class LiveData<T> {
+ private final Object mDataLock = new Object();
+ static final int START_VERSION = -1;
+ private static final Object NOT_SET = new Object();
+
+ private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
+
+ private LifecycleRegistry mRegistry = init();
+
+ private LifecycleRegistry init() {
+ LifecycleRegistry registry = new LifecycleRegistry(this);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ return registry;
+ }
+
+ @Override
+ public Lifecycle getLifecycle() {
+ return mRegistry;
+ }
+ };
+
+ private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
+ new SafeIterableMap<>();
+
+ // how many observers are in active state
+ private int mActiveCount = 0;
+ private volatile Object mData = NOT_SET;
+ // when setData is called, we set the pending data and actual data swap happens on the main
+ // thread
+ private volatile Object mPendingData = NOT_SET;
+ private int mVersion = START_VERSION;
+
+ private boolean mDispatchingValue;
+ @SuppressWarnings("FieldCanBeLocal")
+ private boolean mDispatchInvalidated;
+ private final Runnable mPostValueRunnable = new Runnable() {
+ @Override
+ public void run() {
+ Object newValue;
+ synchronized (mDataLock) {
+ newValue = mPendingData;
+ mPendingData = NOT_SET;
+ }
+ //noinspection unchecked
+ setValue((T) newValue);
+ }
+ };
+
+ private void considerNotify(LifecycleBoundObserver observer) {
+ if (!observer.active) {
+ return;
+ }
+ // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
+ //
+ // we still first check observer.active to keep it as the entrance for events. So even if
+ // the observer moved to an active state, if we've not received that event, we better not
+ // notify for a more predictable notification order.
+ if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
+ return;
+ }
+ if (observer.lastVersion >= mVersion) {
+ return;
+ }
+ observer.lastVersion = mVersion;
+ //noinspection unchecked
+ observer.observer.onChanged((T) mData);
+ }
+
+ private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
+ if (mDispatchingValue) {
+ mDispatchInvalidated = true;
+ return;
+ }
+ mDispatchingValue = true;
+ do {
+ mDispatchInvalidated = false;
+ if (initiator != null) {
+ considerNotify(initiator);
+ initiator = null;
+ } else {
+ for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
+ mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
+ considerNotify(iterator.next().getValue());
+ if (mDispatchInvalidated) {
+ break;
+ }
+ }
+ }
+ } while (mDispatchInvalidated);
+ mDispatchingValue = false;
+ }
+
+ /**
+ * Adds the given observer to the observers list within the lifespan of the given
+ * owner. The events are dispatched on the main thread. If LiveData already has data
+ * set, it will be delivered to the observer.
+ * <p>
+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
+ * or {@link Lifecycle.State#RESUMED} state (active).
+ * <p>
+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
+ * automatically be removed.
+ * <p>
+ * When data changes while the {@code owner} is not active, it will not receive any updates.
+ * If it becomes active again, it will receive the last available data automatically.
+ * <p>
+ * LiveData keeps a strong reference to the observer and the owner as long as the
+ * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
+ * the observer &amp; the owner.
+ * <p>
+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
+ * ignores the call.
+ * <p>
+ * If the given owner, observer tuple is already in the list, the call is ignored.
+ * If the observer is already in the list with another owner, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param owner The LifecycleOwner which controls the observer
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observe(LifecycleOwner owner, Observer<T> observer) {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ // ignore
+ return;
+ }
+ LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
+ LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
+ if (existing != null && existing.owner != wrapper.owner) {
+ throw new IllegalArgumentException("Cannot add the same observer"
+ + " with different lifecycles");
+ }
+ if (existing != null) {
+ return;
+ }
+ owner.getLifecycle().addObserver(wrapper);
+ wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+ }
+
+ /**
+ * Adds the given observer to the observers list. This call is similar to
+ * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
+ * is always active. This means that the given observer will receive all events and will never
+ * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
+ * observing this LiveData.
+ * While LiveData has one of such observers, it will be considered
+ * as active.
+ * <p>
+ * If the observer was already added with an owner to this LiveData, LiveData throws an
+ * {@link IllegalArgumentException}.
+ *
+ * @param observer The observer that will receive the events
+ */
+ @MainThread
+ public void observeForever(Observer<T> observer) {
+ observe(ALWAYS_ON, observer);
+ }
+
+ /**
+ * Removes the given observer from the observers list.
+ *
+ * @param observer The Observer to receive events.
+ */
+ @MainThread
+ public void removeObserver(final Observer<T> observer) {
+ assertMainThread("removeObserver");
+ LifecycleBoundObserver removed = mObservers.remove(observer);
+ if (removed == null) {
+ return;
+ }
+ removed.owner.getLifecycle().removeObserver(removed);
+ removed.activeStateChanged(false);
+ }
+
+ /**
+ * Removes all observers that are tied to the given {@link LifecycleOwner}.
+ *
+ * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
+ */
+ @MainThread
+ public void removeObservers(final LifecycleOwner owner) {
+ assertMainThread("removeObservers");
+ for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
+ if (entry.getValue().owner == owner) {
+ removeObserver(entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Posts a task to a main thread to set the given value. So if you have a following code
+ * executed in the main thread:
+ * <pre class="prettyprint">
+ * liveData.postValue("a");
+ * liveData.setValue("b");
+ * </pre>
+ * The value "b" would be set at first and later the main thread would override it with
+ * the value "a".
+ * <p>
+ * If you called this method multiple times before a main thread executed a posted task, only
+ * the last value would be dispatched.
+ *
+ * @param value The new value
+ */
+ protected void postValue(T value) {
+ boolean postTask;
+ synchronized (mDataLock) {
+ postTask = mPendingData == NOT_SET;
+ mPendingData = value;
+ }
+ if (!postTask) {
+ return;
+ }
+ AppToolkitTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
+ }
+
+ /**
+ * Sets the value. If there are active observers, the value will be dispatched to them.
+ * <p>
+ * This method must be called from the main thread. If you need set a value from a background
+ * thread, you can use {@link #postValue(Object)}
+ *
+ * @param value The new value
+ */
+ @MainThread
+ protected void setValue(T value) {
+ assertMainThread("setValue");
+ mVersion++;
+ mData = value;
+ dispatchingValue(null);
+ }
+
+ /**
+ * Returns the current value.
+ * Note that calling this method on a background thread does not guarantee that the latest
+ * value set will be received.
+ *
+ * @return the current value
+ */
+ @Nullable
+ public T getValue() {
+ Object data = mData;
+ if (data != NOT_SET) {
+ //noinspection unchecked
+ return (T) data;
+ }
+ return null;
+ }
+
+ int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Called when the number of active observers change to 1 from 0.
+ * <p>
+ * This callback can be used to know that this LiveData is being used thus should be kept
+ * up to date.
+ */
+ protected void onActive() {
+
+ }
+
+ /**
+ * Called when the number of active observers change from 1 to 0.
+ * <p>
+ * This does not mean that there are no observers left, there may still be observers but their
+ * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
+ * (like an Activity in the back stack).
+ * <p>
+ * You can check if there are observers via {@link #hasObservers()}.
+ */
+ protected void onInactive() {
+
+ }
+
+ /**
+ * Returns true if this LiveData has observers.
+ *
+ * @return true if this LiveData has observers
+ */
+ public boolean hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ /**
+ * Returns true if this LiveData has active observers.
+ *
+ * @return true if this LiveData has active observers
+ */
+ public boolean hasActiveObservers() {
+ return mActiveCount > 0;
+ }
+
+ class LifecycleBoundObserver implements LifecycleObserver {
+ public final LifecycleOwner owner;
+ public final Observer<T> observer;
+ public boolean active;
+ public int lastVersion = START_VERSION;
+
+ LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
+ this.owner = owner;
+ this.observer = observer;
+ }
+
+ @SuppressWarnings("unused")
+ @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
+ void onStateChange() {
+ if (owner.getLifecycle().getCurrentState() == DESTROYED) {
+ removeObserver(observer);
+ return;
+ }
+ // immediately set active state, so we'd never dispatch anything to inactive
+ // owner
+ activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
+
+ }
+
+ void activeStateChanged(boolean newActive) {
+ if (newActive == active) {
+ return;
+ }
+ active = newActive;
+ boolean wasInactive = LiveData.this.mActiveCount == 0;
+ LiveData.this.mActiveCount += active ? 1 : -1;
+ if (wasInactive && active) {
+ onActive();
+ }
+ if (LiveData.this.mActiveCount == 0 && !active) {
+ onInactive();
+ }
+ if (active) {
+ dispatchingValue(this);
+ }
+ }
+ }
+
+ static boolean isActiveState(State state) {
+ return state.isAtLeast(STARTED);
+ }
+
+ private void assertMainThread(String methodName) {
+ if (!AppToolkitTaskExecutor.getInstance().isMainThread()) {
+ throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ + " thread");
+ }
+ }
}
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index 821eac20..b7c68dd6 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,112 +16,5 @@
package android.arch.paging;
-import android.arch.core.executor.AppToolkitTaskExecutor;
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-/**
- * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
- * <p>
- * Return type for data-loading system of an application or library to produce a
- * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
- * consumer.
- *
- * @param <Key> Tyep of input valued used to load data from the DataSource. Must be integer if
- * you're using TiledDataSource.
- * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
- *
- * @see PagedListAdapter
- * @see DataSource
- * @see PagedList
- */
-public abstract class LivePagedListProvider<Key, Value> {
-
- /**
- * Construct a new data source to be wrapped in a new PagedList, which will be returned
- * through the LiveData.
- *
- * @return The data source.
- */
- @WorkerThread
- protected abstract DataSource<Key, Value> createDataSource();
-
- /**
- * Creates a LiveData of PagedLists, given the page size.
- * <p>
- * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
- * {@link android.support.v7.widget.RecyclerView}.
- *
- * @param initialLoadKey Initial key used to load initial data from the data source.
- * @param pageSize Page size defining how many items are loaded from a data source at a time.
- * Recommended to be multiple times the size of item displayed at once.
- *
- * @return The LiveData of PagedLists.
- */
- public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
- return create(initialLoadKey,
- new PagedList.Config.Builder()
- .setPageSize(pageSize)
- .build());
- }
-
- /**
- * Creates a LiveData of PagedLists, given the PagedList.Config.
- * <p>
- * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
- * {@link android.support.v7.widget.RecyclerView}.
- *
- * @param initialLoadKey Initial key to pass to the data source to initialize data with.
- * @param config PagedList.Config to use with created PagedLists. This specifies how the
- * lists will load data.
- *
- * @return The LiveData of PagedLists.
- */
- public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
- final PagedList.Config config) {
- return new ComputableLiveData<PagedList<Value>>() {
- @Nullable
- private PagedList<Value> mList;
- @Nullable
- private DataSource<Key, Value> mDataSource;
-
- private final DataSource.InvalidatedCallback mCallback =
- new DataSource.InvalidatedCallback() {
- @Override
- public void onInvalidated() {
- invalidate();
- }
- };
-
- @Override
- protected PagedList<Value> compute() {
- @Nullable Key initializeKey = initialLoadKey;
- if (mList != null) {
- //noinspection unchecked
- initializeKey = (Key) mList.getLastKey();
- }
-
- do {
- if (mDataSource != null) {
- mDataSource.removeInvalidatedCallback(mCallback);
- }
-
- mDataSource = createDataSource();
- mDataSource.addInvalidatedCallback(mCallback);
-
- mList = new PagedList.Builder<Key, Value>()
- .setDataSource(mDataSource)
- .setMainThreadExecutor(AppToolkitTaskExecutor.getMainThreadExecutor())
- .setBackgroundThreadExecutor(
- AppToolkitTaskExecutor.getIOThreadExecutor())
- .setConfig(config)
- .setInitialKey(initializeKey)
- .build();
- } while (mList.isDetached());
- return mList;
- }
- }.getLiveData();
- }
-}
+abstract public class LivePagedListProvider<K, T> {
+} \ No newline at end of file
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 73c1b647..19a0c558 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -44,7 +44,7 @@ import android.support.v7.widget.RecyclerView;
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider&lt;User> usersByLastName();
+ * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
@@ -58,7 +58,7 @@ import android.support.v7.widget.RecyclerView;
* }
* }
*
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 7d7a9222..4bff5fcf 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -50,7 +50,7 @@ import java.util.List;
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider&lt;User> usersByLastName();
+ * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
@@ -64,7 +64,7 @@ import java.util.List;
* }
* }
*
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
diff --git a/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
index f0022186..5d0117d7 100644
--- a/android/arch/paging/integration/testapp/PagedListSampleActivity.java
+++ b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
@@ -17,7 +17,6 @@
package android.arch.paging.integration.testapp;
import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.arch.paging.PagedList;
@@ -31,7 +30,7 @@ import android.widget.Button;
/**
* Sample NullPaddedList activity with artificial data source.
*/
-public class PagedListSampleActivity extends AppCompatActivity implements LifecycleRegistryOwner {
+public class PagedListSampleActivity extends AppCompatActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
diff --git a/android/arch/persistence/db/SimpleSQLiteQuery.java b/android/arch/persistence/db/SimpleSQLiteQuery.java
index b8211760..e2a38294 100644
--- a/android/arch/persistence/db/SimpleSQLiteQuery.java
+++ b/android/arch/persistence/db/SimpleSQLiteQuery.java
@@ -20,7 +20,7 @@ package android.arch.persistence.db;
* A basic implemtation of {@link SupportSQLiteQuery} which receives a query and its args and binds
* args based on the passed in Object type.
*/
-public class SimpleSQLiteQuery implements SupportSQLiteQuery {
+public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
private final String mQuery;
private final Object[] mBindArgs;
diff --git a/android/arch/persistence/db/SupportSQLiteQueryBuilder.java b/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
index 183fb0a3..52957a31 100644
--- a/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
+++ b/android/arch/persistence/db/SupportSQLiteQueryBuilder.java
@@ -22,7 +22,7 @@ import java.util.regex.Pattern;
* A simple query builder to create SQL SELECT queries.
*/
@SuppressWarnings("unused")
-public class SupportSQLiteQueryBuilder {
+public final class SupportSQLiteQueryBuilder {
private static final Pattern sLimitPattern =
Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 7b4245b7..2268f45f 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -23,7 +23,7 @@ import android.arch.persistence.db.SupportSQLiteOpenHelper;
* framework.
*/
@SuppressWarnings("unused")
-public class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
+public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
return new FrameworkSQLiteOpenHelper(
diff --git a/android/arch/persistence/room/ColumnInfo.java b/android/arch/persistence/room/ColumnInfo.java
index 84f5844e..65da379c 100644
--- a/android/arch/persistence/room/ColumnInfo.java
+++ b/android/arch/persistence/room/ColumnInfo.java
@@ -33,6 +33,7 @@ import java.lang.annotation.Target;
public @interface ColumnInfo {
/**
* Name of the column in the database. Defaults to the field name if not set.
+ *
* @return Name of the column in the database.
*/
String name() default INHERIT_FIELD_NAME;
@@ -40,13 +41,14 @@ public @interface ColumnInfo {
/**
* The type affinity for the column, which will be used when constructing the database.
* <p>
- * If it is not specified, Room resolves it based on the field's type and available
- * TypeConverters.
+ * If it is not specified, the value defaults to {@link #UNDEFINED} and Room resolves it based
+ * on the field's type and available TypeConverters.
* <p>
* See <a href="https://www.sqlite.org/datatype3.html">SQLite types documentation</a> for
* details.
*
- * @return The type affinity of the column.
+ * @return The type affinity of the column. This is either {@link #UNDEFINED}, {@link #TEXT},
+ * {@link #INTEGER}, {@link #REAL}, or {@link #BLOB}.
*/
@SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;
@@ -60,6 +62,17 @@ public @interface ColumnInfo {
boolean index() default false;
/**
+ * The collation sequence for the column, which will be used when constructing the database.
+ * <p>
+ * The default value is {@link #UNSPECIFIED}. In that case, Room does not add any
+ * collation sequence to the column, and SQLite treats it like {@link #BINARY}.
+ *
+ * @return The collation sequence of the column. This is either {@link #UNSPECIFIED},
+ * {@link #BINARY}, {@link #NOCASE}, or {@link #RTRIM}.
+ */
+ @Collate int collate() default UNSPECIFIED;
+
+ /**
* Constant to let Room inherit the field name as the column name. If used, Room will use the
* field name as the column name.
*/
@@ -67,22 +80,32 @@ public @interface ColumnInfo {
/**
* Undefined type affinity. Will be resolved based on the type.
+ *
+ * @see #typeAffinity()
*/
int UNDEFINED = 1;
/**
* Column affinity constant for strings.
+ *
+ * @see #typeAffinity()
*/
int TEXT = 2;
/**
* Column affinity constant for integers or booleans.
+ *
+ * @see #typeAffinity()
*/
int INTEGER = 3;
/**
* Column affinity constant for floats or doubles.
+ *
+ * @see #typeAffinity()
*/
int REAL = 4;
/**
* Column affinity constant for binary data.
+ *
+ * @see #typeAffinity()
*/
int BLOB = 5;
@@ -92,4 +115,34 @@ public @interface ColumnInfo {
@IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})
@interface SQLiteTypeAffinity {
}
+
+ /**
+ * Collation sequence is not specified. The match will behave like {@link #BINARY}.
+ *
+ * @see #collate()
+ */
+ int UNSPECIFIED = 1;
+ /**
+ * Collation sequence for case-sensitive match.
+ *
+ * @see #collate()
+ */
+ int BINARY = 2;
+ /**
+ * Collation sequence for case-insensitive match.
+ *
+ * @see #collate()
+ */
+ int NOCASE = 3;
+ /**
+ * Collation sequence for case-sensitive match except that trailing space characters are
+ * ignored.
+ *
+ * @see #collate()
+ */
+ int RTRIM = 4;
+
+ @IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM})
+ @interface Collate {
+ }
}
diff --git a/android/arch/persistence/room/Relation.java b/android/arch/persistence/room/Relation.java
index 0d2f1527..72066992 100644
--- a/android/arch/persistence/room/Relation.java
+++ b/android/arch/persistence/room/Relation.java
@@ -41,7 +41,7 @@ import java.lang.annotation.Target;
*
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT id, name from User WHERE age &gte; ?")
+ * {@literal @}Query("SELECT id, name from User WHERE age &gt; :minAge")
* public List&lt;UserNameAndAllPets&gt; loadUserAndPets(int minAge);
* }
* </pre>
@@ -67,7 +67,7 @@ import java.lang.annotation.Target;
* }
* {@literal @}Dao
* public interface UserPetDao {
- * {@literal @}Query("SELECT * from User WHERE age &gte; ?")
+ * {@literal @}Query("SELECT * from User WHERE age &gt; :minAge")
* public List&lt;UserAllPets&gt; loadUserAndPets(int minAge);
* }
* </pre>
diff --git a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
index 56508292..818c46b4 100644
--- a/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
+++ b/android/arch/persistence/room/integration/testapp/RoomPagedListActivity.java
@@ -17,7 +17,6 @@
package android.arch.persistence.room.integration.testapp;
import android.arch.lifecycle.LifecycleRegistry;
-import android.arch.lifecycle.LifecycleRegistryOwner;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
@@ -35,7 +34,7 @@ import android.widget.Button;
/**
* Sample PagedList activity which uses Room.
*/
-public class RoomPagedListActivity extends AppCompatActivity implements LifecycleRegistryOwner {
+public class RoomPagedListActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private PagedListCustomerAdapter mAdapter;
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 428d87c3..337c233f 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -59,6 +59,9 @@ public abstract class UserDao {
@Query("select * from user where mId IN(:ids)")
public abstract User[] loadByIds(int... ids);
+ @Query("select * from user where custommm = :customField")
+ public abstract List<User> findByCustomField(String customField);
+
@Insert
public abstract void insert(User user);
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java b/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
index 4a95ad85..ef207cf2 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationDb.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.integration.testapp.migration;
import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.Entity;
@@ -75,6 +76,7 @@ public abstract class MigrationDb extends RoomDatabase {
public static final String TABLE_NAME = "Entity4";
@PrimaryKey
public int id;
+ @ColumnInfo(collate = ColumnInfo.NOCASE)
public String name;
}
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index aa297ed4..725d53f8 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -315,7 +315,7 @@ public class MigrationTest {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS " + MigrationDb.Entity4.TABLE_NAME
- + " (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`),"
+ + " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
+ " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
+ " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
}
diff --git a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index 2b4a0e99..8861adbc 100644
--- a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -265,6 +265,16 @@ public class SimpleEntityReadWriteTest {
}
@Test
+ public void findByCollateNoCase() {
+ User user = TestUtil.createUser(3);
+ user.setCustomField("abc");
+ mUserDao.insert(user);
+ List<User> users = mUserDao.findByCustomField("ABC");
+ assertThat(users, hasSize(1));
+ assertThat(users.get(0).getId(), is(3));
+ }
+
+ @Test
public void deleteByAge() {
User user1 = TestUtil.createUser(3);
user1.setAge(30);
diff --git a/android/arch/persistence/room/integration/testapp/vo/User.java b/android/arch/persistence/room/integration/testapp/vo/User.java
index 57cf585e..a5b88394 100644
--- a/android/arch/persistence/room/integration/testapp/vo/User.java
+++ b/android/arch/persistence/room/integration/testapp/vo/User.java
@@ -43,7 +43,7 @@ public class User {
private Date mBirthday;
- @ColumnInfo(name = "custommm")
+ @ColumnInfo(name = "custommm", collate = ColumnInfo.NOCASE)
private String mCustomField;
public int getId() {
diff --git a/android/bluetooth/BluetoothGatt.java b/android/bluetooth/BluetoothGatt.java
index 759d7729..a2af3422 100644
--- a/android/bluetooth/BluetoothGatt.java
+++ b/android/bluetooth/BluetoothGatt.java
@@ -42,7 +42,7 @@ public final class BluetoothGatt implements BluetoothProfile {
private static final boolean VDBG = false;
private IBluetoothGatt mService;
- private BluetoothGattCallback mCallback;
+ private volatile BluetoothGattCallback mCallback;
private Handler mHandler;
private int mClientIf;
private BluetoothDevice mDevice;
@@ -164,8 +164,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onConnectionStateChange(BluetoothGatt.this,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onConnectionStateChange(BluetoothGatt.this,
GATT_FAILURE,
BluetoothProfile.STATE_DISCONNECTED);
}
@@ -203,8 +204,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
}
}
});
@@ -227,8 +229,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
}
}
});
@@ -254,8 +257,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onConnectionStateChange(BluetoothGatt.this, status,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onConnectionStateChange(BluetoothGatt.this, status,
profileState);
}
}
@@ -307,8 +311,7 @@ public final class BluetoothGatt implements BluetoothProfile {
for (BluetoothGattService brokenRef : includedServices) {
BluetoothGattService includedService = getService(mDevice,
- brokenRef.getUuid(), brokenRef.getInstanceId(),
- brokenRef.getType());
+ brokenRef.getUuid(), brokenRef.getInstanceId());
if (includedService != null) {
fixedService.addIncludedService(includedService);
} else {
@@ -320,8 +323,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onServicesDiscovered(BluetoothGatt.this, status);
}
}
});
@@ -371,13 +375,13 @@ public final class BluetoothGatt implements BluetoothProfile {
return;
}
- if (status == 0) characteristic.setValue(value);
-
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ if (status == 0) characteristic.setValue(value);
+ callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
status);
}
}
@@ -429,8 +433,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
status);
}
}
@@ -454,13 +459,13 @@ public final class BluetoothGatt implements BluetoothProfile {
handle);
if (characteristic == null) return;
- characteristic.setValue(value);
-
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onCharacteristicChanged(BluetoothGatt.this,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ characteristic.setValue(value);
+ callback.onCharacteristicChanged(BluetoothGatt.this,
characteristic);
}
}
@@ -489,7 +494,6 @@ public final class BluetoothGatt implements BluetoothProfile {
BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
if (descriptor == null) return;
- if (status == 0) descriptor.setValue(value);
if ((status == GATT_INSUFFICIENT_AUTHENTICATION
|| status == GATT_INSUFFICIENT_ENCRYPTION)
@@ -510,8 +514,10 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ if (status == 0) descriptor.setValue(value);
+ callback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
}
}
});
@@ -559,8 +565,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
}
}
});
@@ -587,8 +594,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onReliableWriteCompleted(BluetoothGatt.this, status);
}
}
});
@@ -610,8 +618,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
}
}
});
@@ -634,8 +643,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onMtuChanged(BluetoothGatt.this, mtu, status);
}
}
});
@@ -660,8 +670,9 @@ public final class BluetoothGatt implements BluetoothProfile {
runOrQueueCallback(new Runnable() {
@Override
public void run() {
- if (mCallback != null) {
- mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
timeout, status);
}
}
@@ -702,10 +713,9 @@ public final class BluetoothGatt implements BluetoothProfile {
* @hide
*/
/*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
- int instanceId, int type) {
+ int instanceId) {
for (BluetoothGattService svc : mServices) {
if (svc.getDevice().equals(device)
- && svc.getType() == type
&& svc.getInstanceId() == instanceId
&& svc.getUuid().equals(uuid)) {
return svc;
@@ -901,7 +911,7 @@ public final class BluetoothGatt implements BluetoothProfile {
/**
* Set the preferred connection PHY for this app. Please note that this is just a
- * recommendation, whether the PHY change will happen depends on other applications peferences,
+ * recommendation, whether the PHY change will happen depends on other applications preferences,
* local and remote controller capabilities. Controller can override these settings.
* <p>
* {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/android/bluetooth/BluetoothGattServerCallback.java b/android/bluetooth/BluetoothGattServerCallback.java
index 22eba351..2c8114be 100644
--- a/android/bluetooth/BluetoothGattServerCallback.java
+++ b/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,7 +184,7 @@ public abstract class BluetoothGattServerCallback {
/**
* Callback indicating the connection parameters were updated.
*
- * @param gatt The remote device involved
+ * @param device The remote device involved
* @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
* 6 (7.5ms) to 3200 (4000ms).
* @param latency Slave latency for the connection in number of connection events. Valid range
@@ -195,7 +195,7 @@ public abstract class BluetoothGattServerCallback {
* successfully
* @hide
*/
- public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+ public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout,
int status) {
}
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
index 664bcbca..d73f8526 100644
--- a/android/content/pm/ApplicationInfo.java
+++ b/android/content/pm/ApplicationInfo.java
@@ -586,24 +586,32 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_VIRTUAL_PRELOAD = 1 << 16;
+ /**
+ * Value for {@linl #privateFlags}: whether this app is pre-installed on the
+ * OEM partition of the system image.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_OEM = 1 << 17;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
- PRIVATE_FLAG_HIDDEN,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
+ PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
+ PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
PRIVATE_FLAG_CANT_SAVE_STATE,
- PRIVATE_FLAG_FORWARD_LOCK,
- PRIVATE_FLAG_PRIVILEGED,
- PRIVATE_FLAG_HAS_DOMAIN_URLS,
PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
PRIVATE_FLAG_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_FORWARD_LOCK,
+ PRIVATE_FLAG_HAS_DOMAIN_URLS,
+ PRIVATE_FLAG_HIDDEN,
PRIVATE_FLAG_INSTANT,
+ PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
+ PRIVATE_FLAG_OEM,
PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE,
+ PRIVATE_FLAG_PRIVILEGED,
PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER,
- PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
- PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE,
- PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION,
- PRIVATE_FLAG_BACKUP_IN_FOREGROUND,
PRIVATE_FLAG_STATIC_SHARED_LIBRARY,
- PRIVATE_FLAG_ISOLATED_SPLIT_LOADING,
PRIVATE_FLAG_VIRTUAL_PRELOAD,
})
@Retention(RetentionPolicy.SOURCE)
@@ -1557,6 +1565,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
/**
* @hide
*/
+ public boolean isOem() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index 8b54b0d8..6c7c8a07 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -96,8 +96,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
-
import libcore.util.EmptyArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -815,21 +815,22 @@ public class PackageParser {
}
}
- public final static int PARSE_IS_SYSTEM = 1<<0;
- public final static int PARSE_CHATTY = 1<<1;
- public final static int PARSE_MUST_BE_APK = 1<<2;
- public final static int PARSE_IGNORE_PROCESSES = 1<<3;
- public final static int PARSE_FORWARD_LOCK = 1<<4;
- public final static int PARSE_EXTERNAL_STORAGE = 1<<5;
- public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
- public final static int PARSE_IS_PRIVILEGED = 1<<7;
- public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
- public final static int PARSE_TRUSTED_OVERLAY = 1<<9;
- public final static int PARSE_ENFORCE_CODE = 1<<10;
+ public static final int PARSE_IS_SYSTEM = 1 << 0;
+ public static final int PARSE_CHATTY = 1 << 1;
+ public static final int PARSE_MUST_BE_APK = 1 << 2;
+ public static final int PARSE_IGNORE_PROCESSES = 1 << 3;
+ public static final int PARSE_FORWARD_LOCK = 1 << 4;
+ public static final int PARSE_EXTERNAL_STORAGE = 1 << 5;
+ public static final int PARSE_IS_SYSTEM_DIR = 1 << 6;
+ public static final int PARSE_IS_PRIVILEGED = 1 << 7;
+ public static final int PARSE_COLLECT_CERTIFICATES = 1 << 8;
+ public static final int PARSE_TRUSTED_OVERLAY = 1 << 9;
+ public static final int PARSE_ENFORCE_CODE = 1 << 10;
/** @deprecated remove when fixing b/34761192 */
@Deprecated
- public final static int PARSE_IS_EPHEMERAL = 1<<11;
- public final static int PARSE_FORCE_SDK = 1<<12;
+ public static final int PARSE_IS_EPHEMERAL = 1 << 11;
+ public static final int PARSE_FORCE_SDK = 1 << 12;
+ public static final int PARSE_IS_OEM = 1 << 13;
private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index b84c1b93..17b4f871 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -17,7 +17,6 @@
package android.content.pm;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -135,6 +134,16 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
public static final int PROTECTION_FLAG_RUNTIME_ONLY = 0x2000;
/**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>oem</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PROTECTION_FLAG_OEM = 0x4000;
+
+ /**
* Mask for {@link #protectionLevel}: the basic protection type.
*/
public static final int PROTECTION_MASK_BASE = 0xf;
@@ -222,7 +231,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
/** @hide */
public static String protectionToString(int level) {
String protLevel = "????";
- switch (level&PROTECTION_MASK_BASE) {
+ switch (level & PROTECTION_MASK_BASE) {
case PermissionInfo.PROTECTION_DANGEROUS:
protLevel = "dangerous";
break;
@@ -236,36 +245,39 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
protLevel = "signatureOrSystem";
break;
}
- if ((level&PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
protLevel += "|privileged";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
protLevel += "|development";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
protLevel += "|appop";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_PRE23) != 0) {
protLevel += "|pre23";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0) {
protLevel += "|installer";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0) {
protLevel += "|verifier";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) {
protLevel += "|preinstalled";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_SETUP) != 0) {
protLevel += "|setup";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
protLevel += "|instant";
}
- if ((level&PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
+ if ((level & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0) {
protLevel += "|runtime";
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_OEM) != 0) {
+ protLevel += "|oem";
+ }
return protLevel;
}
diff --git a/android/graphics/BitmapFactory.java b/android/graphics/BitmapFactory.java
index 3b272c8d..ffb39e33 100644
--- a/android/graphics/BitmapFactory.java
+++ b/android/graphics/BitmapFactory.java
@@ -433,10 +433,15 @@ public class BitmapFactory {
static void validate(Options opts) {
if (opts == null) return;
- if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
+ if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
}
+ if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
+ throw new IllegalArgumentException("Bitmaps with Config.HARDWARE cannot be " +
+ "decoded into - they are immutable");
+ }
+
if (opts.inPreferredColorSpace != null) {
if (!(opts.inPreferredColorSpace instanceof ColorSpace.Rgb)) {
throw new IllegalArgumentException("The destination color space must use the " +
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
index c3ef4508..ceac3253 100644
--- a/android/graphics/drawable/VectorDrawable.java
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -31,6 +31,7 @@ import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Shader;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -605,38 +606,44 @@ public class VectorDrawable extends Drawable {
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
- // This VD has been used to display other VD resource content, clean up.
- if (mVectorState.mRootGroup != null) {
- // Subtract the native allocation for all the nodes.
- VMRuntime.getRuntime().registerNativeFree(mVectorState.mRootGroup.getNativeSize());
- // Remove child nodes' reference to tree
- mVectorState.mRootGroup.setTree(null);
- }
- mVectorState.mRootGroup = new VGroup();
- if (mVectorState.mNativeTree != null) {
- // Subtract the native allocation for the tree wrapper, which contains root node
- // as well as rendering related data.
- VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
- mVectorState.mNativeTree.release();
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "VectorDrawable#inflate");
+ if (mVectorState.mRootGroup != null || mVectorState.mNativeTree != null) {
+ // This VD has been used to display other VD resource content, clean up.
+ if (mVectorState.mRootGroup != null) {
+ // Subtract the native allocation for all the nodes.
+ VMRuntime.getRuntime().registerNativeFree(
+ mVectorState.mRootGroup.getNativeSize());
+ // Remove child nodes' reference to tree
+ mVectorState.mRootGroup.setTree(null);
+ }
+ mVectorState.mRootGroup = new VGroup();
+ if (mVectorState.mNativeTree != null) {
+ // Subtract the native allocation for the tree wrapper, which contains root node
+ // as well as rendering related data.
+ VMRuntime.getRuntime().registerNativeFree(mVectorState.NATIVE_ALLOCATION_SIZE);
+ mVectorState.mNativeTree.release();
+ }
+ mVectorState.createNativeTree(mVectorState.mRootGroup);
}
- mVectorState.createNativeTree(mVectorState.mRootGroup);
- }
- final VectorDrawableState state = mVectorState;
- state.setDensity(Drawable.resolveDensity(r, 0));
+ final VectorDrawableState state = mVectorState;
+ state.setDensity(Drawable.resolveDensity(r, 0));
- final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
+ updateStateFromTypedArray(a);
+ a.recycle();
- mDpiScaledDirty = true;
+ mDpiScaledDirty = true;
- state.mCacheDirty = true;
- inflateChildElements(r, parser, attrs, theme);
+ state.mCacheDirty = true;
+ inflateChildElements(r, parser, attrs, theme);
- state.onTreeConstructionFinished();
- // Update local properties.
- updateLocalState(r);
+ state.onTreeConstructionFinished();
+ // Update local properties.
+ updateLocalState(r);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
}
private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index 15754574..186b2650 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -3058,7 +3058,11 @@ public class AudioManager {
private final IPlaybackConfigDispatcher mPlayCb = new IPlaybackConfigDispatcher.Stub() {
@Override
- public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+ public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+ boolean flush) {
+ if (flush) {
+ Binder.flushPendingCommands();
+ }
synchronized(mPlaybackCallbackLock) {
if (mPlaybackCallbackList != null) {
for (int i=0 ; i < mPlaybackCallbackList.size() ; i++) {
diff --git a/android/media/AudioPlaybackConfiguration.java b/android/media/AudioPlaybackConfiguration.java
index 14bc5551..8a36f91c 100644
--- a/android/media/AudioPlaybackConfiguration.java
+++ b/android/media/AudioPlaybackConfiguration.java
@@ -212,8 +212,10 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* @hide
*/
public void init() {
- if (mIPlayerShell != null) {
- mIPlayerShell.monitorDeath();
+ synchronized (this) {
+ if (mIPlayerShell != null) {
+ mIPlayerShell.monitorDeath();
+ }
}
}
@@ -322,7 +324,11 @@ public final class AudioPlaybackConfiguration implements Parcelable {
*/
@SystemApi
public PlayerProxy getPlayerProxy() {
- return mIPlayerShell == null ? null : new PlayerProxy(this);
+ final IPlayerShell ips;
+ synchronized (this) {
+ ips = mIPlayerShell;
+ }
+ return ips == null ? null : new PlayerProxy(this);
}
/**
@@ -330,7 +336,11 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* @return the IPlayer interface for the associated player
*/
IPlayer getIPlayer() {
- return mIPlayerShell == null ? null : mIPlayerShell.getIPlayer();
+ final IPlayerShell ips;
+ synchronized (this) {
+ ips = mIPlayerShell;
+ }
+ return ips == null ? null : ips.getIPlayer();
}
/**
@@ -351,10 +361,14 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* @return true if the state changed, false otherwise
*/
public boolean handleStateEvent(int event) {
- final boolean changed = (mPlayerState != event);
- mPlayerState = event;
- if ((event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
- mIPlayerShell.release();
+ final boolean changed;
+ synchronized (this) {
+ changed = (mPlayerState != event);
+ mPlayerState = event;
+ if (changed && (event == PLAYER_STATE_RELEASED) && (mIPlayerShell != null)) {
+ mIPlayerShell.release();
+ mIPlayerShell = null;
+ }
}
return changed;
}
@@ -447,7 +461,11 @@ public final class AudioPlaybackConfiguration implements Parcelable {
dest.writeInt(mClientPid);
dest.writeInt(mPlayerState);
mPlayerAttr.writeToParcel(dest, 0);
- dest.writeStrongInterface(mIPlayerShell == null ? null : mIPlayerShell.getIPlayer());
+ final IPlayerShell ips;
+ synchronized (this) {
+ ips = mIPlayerShell;
+ }
+ dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
}
private AudioPlaybackConfiguration(Parcel in) {
@@ -479,14 +497,17 @@ public final class AudioPlaybackConfiguration implements Parcelable {
static final class IPlayerShell implements IBinder.DeathRecipient {
final AudioPlaybackConfiguration mMonitor; // never null
- private IPlayer mIPlayer;
+ private volatile IPlayer mIPlayer;
IPlayerShell(@NonNull AudioPlaybackConfiguration monitor, @NonNull IPlayer iplayer) {
mMonitor = monitor;
mIPlayer = iplayer;
}
- void monitorDeath() {
+ synchronized void monitorDeath() {
+ if (mIPlayer == null) {
+ return;
+ }
try {
mIPlayer.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -509,8 +530,13 @@ public final class AudioPlaybackConfiguration implements Parcelable {
} else if (DEBUG) { Log.i(TAG, "IPlayerShell binderDied"); }
}
- void release() {
+ synchronized void release() {
+ if (mIPlayer == null) {
+ return;
+ }
mIPlayer.asBinder().unlinkToDeath(this, 0);
+ mIPlayer = null;
+ Binder.flushPendingCommands();
}
}
@@ -532,7 +558,7 @@ public final class AudioPlaybackConfiguration implements Parcelable {
case PLAYER_TYPE_HW_SOURCE: return "hardware source";
case PLAYER_TYPE_EXTERNAL_PROXY: return "external proxy";
default:
- return "unknown player type - FIXME";
+ return "unknown player type " + type + " - FIXME";
}
}
diff --git a/android/media/MediaMuxer.java b/android/media/MediaMuxer.java
index 832b2974..91e57ee0 100644
--- a/android/media/MediaMuxer.java
+++ b/android/media/MediaMuxer.java
@@ -70,7 +70,7 @@ import java.util.Map;
<p>
Per-frame metadata is useful in carrying extra information that correlated with video or audio to
facilitate offline processing, e.g. gyro signals from the sensor could help video stabilization when
- doing offline processing. Metaadata track is only supported in MP4 container. When adding a new
+ doing offline processing. Metadata track is only supported in MP4 container. When adding a new
metadata track, track's mime format must start with prefix "application/", e.g. "applicaton/gyro".
Metadata's format/layout will be defined by the application. Writing metadata is nearly the same as
writing video/audio data except that the data will not be from mediacodec. Application just needs
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 0d99473c..7787d4b5 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -2072,6 +2072,20 @@ public class MediaPlayer extends PlayerBase
private native void _reset();
/**
+ * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be
+ * notified when the presentation time reaches (becomes greater than or equal to)
+ * the value specified.
+ *
+ * @param mediaTimeUs presentation time to get timed event callback at
+ * @hide
+ */
+ public void notifyAt(long mediaTimeUs) {
+ _notifyAt(mediaTimeUs);
+ }
+
+ private native void _notifyAt(long mediaTimeUs);
+
+ /**
* Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
* for a list of stream types. Must call this method before prepare() or
* prepareAsync() in order for the target stream type to become effective
@@ -3155,6 +3169,7 @@ public class MediaPlayer extends PlayerBase
private static final int MEDIA_PAUSED = 7;
private static final int MEDIA_STOPPED = 8;
private static final int MEDIA_SKIPPED = 9;
+ private static final int MEDIA_NOTIFY_TIME = 98;
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
private static final int MEDIA_INFO = 200;
@@ -3345,6 +3360,14 @@ public class MediaPlayer extends PlayerBase
}
// No real default action so far.
return;
+
+ case MEDIA_NOTIFY_TIME:
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onNotifyTime();
+ }
+ return;
+
case MEDIA_TIMED_TEXT:
OnTimedTextListener onTimedTextListener = mOnTimedTextListener;
if (onTimedTextListener == null)
@@ -5144,19 +5167,16 @@ public class MediaPlayer extends PlayerBase
private boolean mStopped = true;
private boolean mBuffering;
private long mLastReportedTime;
- private long mTimeAdjustment;
// since we are expecting only a handful listeners per stream, there is
// no need for log(N) search performance
private MediaTimeProvider.OnMediaTimeListener mListeners[];
private long mTimes[];
- private long mLastNanoTime;
private Handler mEventHandler;
private boolean mRefresh = false;
private boolean mPausing = false;
private boolean mSeeking = false;
private static final int NOTIFY = 1;
private static final int NOTIFY_TIME = 0;
- private static final int REFRESH_AND_NOTIFY_TIME = 1;
private static final int NOTIFY_STOP = 2;
private static final int NOTIFY_SEEK = 3;
private static final int NOTIFY_TRACK_DATA = 4;
@@ -5188,13 +5208,11 @@ public class MediaPlayer extends PlayerBase
mListeners = new MediaTimeProvider.OnMediaTimeListener[0];
mTimes = new long[0];
mLastTimeUs = 0;
- mTimeAdjustment = 0;
}
private void scheduleNotification(int type, long delayUs) {
// ignore time notifications until seek is handled
- if (mSeeking &&
- (type == NOTIFY_TIME || type == REFRESH_AND_NOTIFY_TIME)) {
+ if (mSeeking && type == NOTIFY_TIME) {
return;
}
@@ -5221,6 +5239,14 @@ public class MediaPlayer extends PlayerBase
}
/** @hide */
+ public void onNotifyTime() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onNotifyTime: ");
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+
+ /** @hide */
public void onPaused(boolean paused) {
synchronized(this) {
if (DEBUG) Log.d(TAG, "onPaused: " + paused);
@@ -5231,7 +5257,7 @@ public class MediaPlayer extends PlayerBase
} else {
mPausing = paused; // special handling if player disappeared
mSeeking = false;
- scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */);
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
}
}
}
@@ -5241,7 +5267,7 @@ public class MediaPlayer extends PlayerBase
synchronized (this) {
if (DEBUG) Log.d(TAG, "onBuffering: " + buffering);
mBuffering = buffering;
- scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */);
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
}
}
@@ -5438,7 +5464,7 @@ public class MediaPlayer extends PlayerBase
if (nextTimeUs > nowUs && !mPaused) {
// schedule callback at nextTimeUs
if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs);
- scheduleNotification(NOTIFY_TIME, nextTimeUs - nowUs);
+ mPlayer.notifyAt(nextTimeUs);
} else {
mEventHandler.removeMessages(NOTIFY);
// no more callbacks
@@ -5449,25 +5475,6 @@ public class MediaPlayer extends PlayerBase
}
}
- private long getEstimatedTime(long nanoTime, boolean monotonic) {
- if (mPaused) {
- mLastReportedTime = mLastTimeUs + mTimeAdjustment;
- } else {
- long timeSinceRead = (nanoTime - mLastNanoTime) / 1000;
- mLastReportedTime = mLastTimeUs + timeSinceRead;
- if (mTimeAdjustment > 0) {
- long adjustment =
- mTimeAdjustment - timeSinceRead / TIME_ADJUSTMENT_RATE;
- if (adjustment <= 0) {
- mTimeAdjustment = 0;
- } else {
- mLastReportedTime += adjustment;
- }
- }
- }
- return mLastReportedTime;
- }
-
public long getCurrentTimeUs(boolean refreshTime, boolean monotonic)
throws IllegalStateException {
synchronized (this) {
@@ -5477,42 +5484,38 @@ public class MediaPlayer extends PlayerBase
return mLastReportedTime;
}
- long nanoTime = System.nanoTime();
- if (refreshTime ||
- nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) {
- try {
- mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
- mPaused = !mPlayer.isPlaying() || mBuffering;
- if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
- } catch (IllegalStateException e) {
- if (mPausing) {
- // if we were pausing, get last estimated timestamp
- mPausing = false;
- getEstimatedTime(nanoTime, monotonic);
- mPaused = true;
- if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
- return mLastReportedTime;
+ try {
+ mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
+ mPaused = !mPlayer.isPlaying() || mBuffering;
+ if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
+ } catch (IllegalStateException e) {
+ if (mPausing) {
+ // if we were pausing, get last estimated timestamp
+ mPausing = false;
+ if (!monotonic || mLastReportedTime < mLastTimeUs) {
+ mLastReportedTime = mLastTimeUs;
}
- // TODO get time when prepared
- throw e;
+ mPaused = true;
+ if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
+ return mLastReportedTime;
}
- mLastNanoTime = nanoTime;
- if (monotonic && mLastTimeUs < mLastReportedTime) {
- /* have to adjust time */
- mTimeAdjustment = mLastReportedTime - mLastTimeUs;
- if (mTimeAdjustment > 1000000) {
- // schedule seeked event if time jumped significantly
- // TODO: do this properly by introducing an exception
- mStopped = false;
- mSeeking = true;
- scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
- }
- } else {
- mTimeAdjustment = 0;
+ // TODO get time when prepared
+ throw e;
+ }
+ if (monotonic && mLastTimeUs < mLastReportedTime) {
+ /* have to adjust time */
+ if (mLastReportedTime - mLastTimeUs > 1000000) {
+ // schedule seeked event if time jumped significantly
+ // TODO: do this properly by introducing an exception
+ mStopped = false;
+ mSeeking = true;
+ scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
}
+ } else {
+ mLastReportedTime = mLastTimeUs;
}
- return getEstimatedTime(nanoTime, monotonic);
+ return mLastReportedTime;
}
}
@@ -5526,9 +5529,6 @@ public class MediaPlayer extends PlayerBase
if (msg.what == NOTIFY) {
switch (msg.arg1) {
case NOTIFY_TIME:
- notifyTimedEvent(false /* refreshTime */);
- break;
- case REFRESH_AND_NOTIFY_TIME:
notifyTimedEvent(true /* refreshTime */);
break;
case NOTIFY_STOP:
diff --git a/android/media/MediaRouter.java b/android/media/MediaRouter.java
index 2894e895..fe427a73 100644
--- a/android/media/MediaRouter.java
+++ b/android/media/MediaRouter.java
@@ -193,7 +193,9 @@ public class MediaRouter {
} else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
- name = com.android.internal.R.string.default_media_route_name_hdmi;
+ name = com.android.internal.R.string.default_audio_route_name_hdmi;
+ } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_USB) != 0) {
+ name = com.android.internal.R.string.default_audio_route_name_usb;
} else {
name = com.android.internal.R.string.default_audio_route_name;
}
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
index f527f77d..2c9fb23e 100644
--- a/android/net/LinkProperties.java
+++ b/android/net/LinkProperties.java
@@ -70,8 +70,23 @@ public final class LinkProperties implements Parcelable {
* @hide
*/
public static class CompareResult<T> {
- public List<T> removed = new ArrayList<T>();
- public List<T> added = new ArrayList<T>();
+ public final List<T> removed = new ArrayList<T>();
+ public final List<T> added = new ArrayList<T>();
+
+ public CompareResult() {}
+
+ public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
+ if (oldItems != null) {
+ removed.addAll(oldItems);
+ }
+ if (newItems != null) {
+ for (T newItem : newItems) {
+ if (!removed.remove(newItem)) {
+ added.add(newItem);
+ }
+ }
+ }
+ }
@Override
public String toString() {
@@ -1000,17 +1015,8 @@ public final class LinkProperties implements Parcelable {
* are in target but not in mLinkAddresses are placed in the
* addedAddresses.
*/
- CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
- result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
- result.added.clear();
- if (target != null) {
- for (LinkAddress newAddress : target.getLinkAddresses()) {
- if (! result.removed.remove(newAddress)) {
- result.added.add(newAddress);
- }
- }
- }
- return result;
+ return new CompareResult<>(mLinkAddresses,
+ target != null ? target.getLinkAddresses() : null);
}
/**
@@ -1029,18 +1035,7 @@ public final class LinkProperties implements Parcelable {
* are in target but not in mDnses are placed in the
* addedAddresses.
*/
- CompareResult<InetAddress> result = new CompareResult<InetAddress>();
-
- result.removed = new ArrayList<InetAddress>(mDnses);
- result.added.clear();
- if (target != null) {
- for (InetAddress newAddress : target.getDnsServers()) {
- if (! result.removed.remove(newAddress)) {
- result.added.add(newAddress);
- }
- }
- }
- return result;
+ return new CompareResult<>(mDnses, target != null ? target.getDnsServers() : null);
}
/**
@@ -1058,18 +1053,7 @@ public final class LinkProperties implements Parcelable {
* leaving the routes that are different. And route address which
* are in target but not in mRoutes are placed in added.
*/
- CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
-
- result.removed = getAllRoutes();
- result.added.clear();
- if (target != null) {
- for (RouteInfo r : target.getAllRoutes()) {
- if (! result.removed.remove(r)) {
- result.added.add(r);
- }
- }
- }
- return result;
+ return new CompareResult<>(getAllRoutes(), target != null ? target.getAllRoutes() : null);
}
/**
@@ -1087,18 +1071,8 @@ public final class LinkProperties implements Parcelable {
* leaving the interface names that are different. And interface names which
* are in target but not in this are placed in added.
*/
- CompareResult<String> result = new CompareResult<String>();
-
- result.removed = getAllInterfaceNames();
- result.added.clear();
- if (target != null) {
- for (String r : target.getAllInterfaceNames()) {
- if (! result.removed.remove(r)) {
- result.added.add(r);
- }
- }
- }
- return result;
+ return new CompareResult<>(getAllInterfaceNames(),
+ target != null ? target.getAllInterfaceNames() : null);
}
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
new file mode 100644
index 00000000..cbf3fc8c
--- /dev/null
+++ b/android/net/metrics/WakeupEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+/**
+ * An event logged when NFLOG notifies userspace of a wakeup packet for
+ * watched interfaces.
+ * {@hide}
+ */
+public class WakeupEvent {
+ public String iface;
+ public long timestampMs;
+ public int uid;
+
+ @Override
+ public String toString() {
+ return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
+ timestampMs, timestampMs, iface, uid);
+ }
+}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
new file mode 100644
index 00000000..d520b974
--- /dev/null
+++ b/android/net/metrics/WakeupStats.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.os.Process;
+import android.os.SystemClock;
+
+/**
+ * An event logged per interface and that aggregates WakeupEvents for that interface.
+ * {@hide}
+ */
+public class WakeupStats {
+
+ private static final int NO_UID = -1;
+
+ public final long creationTimeMs = SystemClock.elapsedRealtime();
+ public final String iface;
+
+ public long totalWakeups = 0;
+ public long rootWakeups = 0;
+ public long systemWakeups = 0;
+ public long nonApplicationWakeups = 0;
+ public long applicationWakeups = 0;
+ public long unroutedWakeups = 0;
+ public long durationSec = 0;
+
+ public WakeupStats(String iface) {
+ this.iface = iface;
+ }
+
+ /** Update durationSec with current time. */
+ public void updateDuration() {
+ durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000;
+ }
+
+ /** Update wakeup counters for the given WakeupEvent. */
+ public void countEvent(WakeupEvent ev) {
+ totalWakeups++;
+ switch (ev.uid) {
+ case Process.ROOT_UID:
+ rootWakeups++;
+ break;
+ case Process.SYSTEM_UID:
+ systemWakeups++;
+ break;
+ case NO_UID:
+ unroutedWakeups++;
+ break;
+ default:
+ if (ev.uid >= Process.FIRST_APPLICATION_UID) {
+ applicationWakeups++;
+ } else {
+ nonApplicationWakeups++;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public String toString() {
+ updateDuration();
+ return new StringBuilder()
+ .append("WakeupStats(").append(iface)
+ .append(", total: ").append(totalWakeups)
+ .append(", root: ").append(rootWakeups)
+ .append(", system: ").append(systemWakeups)
+ .append(", apps: ").append(applicationWakeups)
+ .append(", non-apps: ").append(nonApplicationWakeups)
+ .append(", unrouted: ").append(unroutedWakeups)
+ .append(", ").append(durationSec).append("s)")
+ .toString();
+ }
+}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index cea5715d..66b6b478 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -53,6 +53,8 @@ public abstract class BatteryStats implements Parcelable {
private static final String TAG = "BatteryStats";
private static final boolean LOCAL_LOGV = false;
+ /** Fetching RPM stats is too slow to do each time screen changes, so disable it. */
+ protected static final boolean SCREEN_OFF_RPM_STATS_ENABLED = false;
/** @hide */
public static final String SERVICE_NAME = "batterystats";
@@ -213,8 +215,10 @@ public abstract class BatteryStats implements Parcelable {
* - Fixed bugs in background timers and BLE scan time
* New in version 25:
* - Package wakeup alarms are now on screen-off timebase
+ * New in version 26:
+ * - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
*/
- static final String CHECKIN_VERSION = "25";
+ static final String CHECKIN_VERSION = "26";
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -233,6 +237,10 @@ public abstract class BatteryStats implements Parcelable {
private static final String CPU_DATA = "cpu";
private static final String GLOBAL_CPU_FREQ_DATA = "gcf";
private static final String CPU_TIMES_AT_FREQ_DATA = "ctf";
+ // rpm line is:
+ // BATTERY_STATS_CHECKIN_VERSION, uid, which, "rpm", state/voter name, total time, total count,
+ // screen-off time, screen-off count
+ private static final String RESOURCE_POWER_MANAGER_DATA = "rpm";
private static final String SENSOR_DATA = "sr";
private static final String VIBRATOR_DATA = "vib";
private static final String FOREGROUND_ACTIVITY_DATA = "fg";
@@ -2648,6 +2656,16 @@ public abstract class BatteryStats implements Parcelable {
public abstract Map<String, ? extends Timer> getKernelWakelockStats();
+ /**
+ * Returns Timers tracking the total time of each Resource Power Manager state and voter.
+ */
+ public abstract Map<String, ? extends Timer> getRpmStats();
+ /**
+ * Returns Timers tracking the screen-off time of each Resource Power Manager state and voter.
+ */
+ public abstract Map<String, ? extends Timer> getScreenOffRpmStats();
+
+
public abstract LongSparseArray<? extends Timer> getKernelMemoryStats();
public abstract void writeToParcelWithoutUids(Parcel out, int flags);
@@ -3309,6 +3327,30 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ final Map<String, ? extends Timer> rpmStats = getRpmStats();
+ final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+ if (rpmStats.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+ sb.setLength(0);
+ Timer totalTimer = ent.getValue();
+ long timeMs = (totalTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ int count = totalTimer.getCountLocked(which);
+ Timer screenOffTimer = screenOffRpmStats.get(ent.getKey());
+ long screenOffTimeMs = screenOffTimer != null
+ ? (screenOffTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : 0;
+ int screenOffCount = screenOffTimer != null
+ ? screenOffTimer.getCountLocked(which) : 0;
+ if (SCREEN_OFF_RPM_STATS_ENABLED) {
+ dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+ "\"" + ent.getKey() + "\"", timeMs, count, screenOffTimeMs,
+ screenOffCount);
+ } else {
+ dumpLine(pw, 0 /* uid */, category, RESOURCE_POWER_MANAGER_DATA,
+ "\"" + ent.getKey() + "\"", timeMs, count);
+ }
+ }
+ }
+
final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
helper.create(this);
helper.refreshStats(which, UserHandle.USER_ALL);
@@ -4570,24 +4612,56 @@ public abstract class BatteryStats implements Parcelable {
}
final LongSparseArray<? extends Timer> mMemoryStats = getKernelMemoryStats();
- pw.println("Memory Stats");
- for (int i = 0; i < mMemoryStats.size(); i++) {
- sb.setLength(0);
- sb.append("Bandwidth ");
- sb.append(mMemoryStats.keyAt(i));
- sb.append(" Time ");
- sb.append(mMemoryStats.valueAt(i).getTotalTimeLocked(rawRealtime, which));
- pw.println(sb.toString());
+ if (mMemoryStats.size() > 0) {
+ pw.println(" Memory Stats");
+ for (int i = 0; i < mMemoryStats.size(); i++) {
+ sb.setLength(0);
+ sb.append(" Bandwidth ");
+ sb.append(mMemoryStats.keyAt(i));
+ sb.append(" Time ");
+ sb.append(mMemoryStats.valueAt(i).getTotalTimeLocked(rawRealtime, which));
+ pw.println(sb.toString());
+ }
+ pw.println();
+ }
+
+ final Map<String, ? extends Timer> rpmStats = getRpmStats();
+ if (rpmStats.size() > 0) {
+ pw.print(prefix); pw.println(" Resource Power Manager Stats");
+ if (rpmStats.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+ final String timerName = ent.getKey();
+ final Timer timer = ent.getValue();
+ printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+ }
+ }
+ pw.println();
+ }
+ if (SCREEN_OFF_RPM_STATS_ENABLED) {
+ final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+ if (screenOffRpmStats.size() > 0) {
+ pw.print(prefix);
+ pw.println(" Resource Power Manager Stats for when screen was off");
+ if (screenOffRpmStats.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : screenOffRpmStats.entrySet()) {
+ final String timerName = ent.getKey();
+ final Timer timer = ent.getValue();
+ printTimer(pw, sb, timer, rawRealtime, which, prefix, timerName);
+ }
+ }
+ pw.println();
+ }
}
final long[] cpuFreqs = getCpuFreqs();
if (cpuFreqs != null) {
sb.setLength(0);
- sb.append("CPU freqs:");
+ sb.append(" CPU freqs:");
for (int i = 0; i < cpuFreqs.length; ++i) {
sb.append(" " + cpuFreqs[i]);
}
pw.println(sb.toString());
+ pw.println();
}
for (int iu=0; iu<NU; iu++) {
diff --git a/android/os/Process.java b/android/os/Process.java
index 93516619..b5d62e55 100644
--- a/android/os/Process.java
+++ b/android/os/Process.java
@@ -19,8 +19,8 @@ package android.os;
import android.annotation.TestApi;
import android.system.Os;
import android.system.OsConstants;
-import android.util.Log;
import android.webkit.WebViewZygote;
+
import dalvik.system.VMRuntime;
/**
@@ -423,7 +423,7 @@ public class Process {
*
* When invokeWith is not null, the process will be started as a fresh app
* and not a zygote fork. Note that this is only allowed for uid 0 or when
- * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+ * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
*
* @param processClass The class to use as the process's main entry
* point.
@@ -431,7 +431,7 @@ public class Process {
* @param uid The user-id under which the process will run.
* @param gid The group-id under which the process will run.
* @param gids Additional group-ids associated with the process.
- * @param debugFlags Additional flags.
+ * @param runtimeFlags Additional flags for the runtime.
* @param targetSdkVersion The target SDK version for the app.
* @param seInfo null-ok SELinux information for the new process.
* @param abi non-null the ABI this app should be started with.
@@ -448,7 +448,7 @@ public class Process {
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
- int debugFlags, int mountExternal,
+ int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
@@ -457,7 +457,7 @@ public class Process {
String invokeWith,
String[] zygoteArgs) {
return zygoteProcess.start(processClass, niceName, uid, gid, gids,
- debugFlags, mountExternal, targetSdkVersion, seInfo,
+ runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}
@@ -465,7 +465,7 @@ public class Process {
public static final ProcessStartResult startWebView(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
- int debugFlags, int mountExternal,
+ int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
@@ -474,7 +474,7 @@ public class Process {
String invokeWith,
String[] zygoteArgs) {
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
- debugFlags, mountExternal, targetSdkVersion, seInfo,
+ runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index e11494d5..34c78455 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,114 +16,44 @@
package android.os;
-import android.util.Log;
-
-import com.android.internal.os.BinderInternal;
-
-import java.util.HashMap;
import java.util.Map;
-/** @hide */
public final class ServiceManager {
- private static final String TAG = "ServiceManager";
-
- private static IServiceManager sServiceManager;
- private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
-
- private static IServiceManager getIServiceManager() {
- if (sServiceManager != null) {
- return sServiceManager;
- }
-
- // Find the service manager
- sServiceManager = ServiceManagerNative
- .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
- return sServiceManager;
- }
/**
* Returns a reference to a service with the given name.
- *
+ *
* @param name the name of the service to get
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- return Binder.allowBlocking(getIServiceManager().getService(name));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in getService", e);
- }
return null;
}
/**
- * Returns a reference to a service with the given name, or throws
- * {@link NullPointerException} if none is found.
- *
- * @hide
+ * Is not supposed to return null, but that is fine for layoutlib.
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- final IBinder binder = getService(name);
- if (binder != null) {
- return binder;
- } else {
- throw new ServiceNotFoundException(name);
- }
+ throw new ServiceNotFoundException(name);
}
/**
* Place a new @a service called @a name into the service
* manager.
- *
+ *
* @param name the name of the new service
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- try {
- getIServiceManager().addService(name, service, false);
- } catch (RemoteException e) {
- Log.e(TAG, "error in addService", e);
- }
+ // pass
}
/**
- * Place a new @a service called @a name into the service
- * manager.
- *
- * @param name the name of the new service
- * @param service the service object
- * @param allowIsolated set to true to allow isolated sandboxed processes
- * to access this service
- */
- public static void addService(String name, IBinder service, boolean allowIsolated) {
- try {
- getIServiceManager().addService(name, service, allowIsolated);
- } catch (RemoteException e) {
- Log.e(TAG, "error in addService", e);
- }
- }
-
- /**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- try {
- IBinder service = sCache.get(name);
- if (service != null) {
- return service;
- } else {
- return Binder.allowBlocking(getIServiceManager().checkService(name));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in checkService", e);
- return null;
- }
+ return null;
}
/**
@@ -132,27 +62,21 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- try {
- return getIServiceManager().listServices();
- } catch (RemoteException e) {
- Log.e(TAG, "error in listServices", e);
- return null;
- }
+ // actual implementation returns null sometimes, so it's ok
+ // to return null instead of an empty list.
+ return null;
}
/**
* This is only intended to be called when the process is first being brought
* up and bound by the activity manager. There is only one thread in the process
* at that time, so no locking is done.
- *
+ *
* @param cache the cache of service references
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- if (sCache.size() != 0) {
- throw new IllegalStateException("setServiceCache may only be called once");
- }
- sCache.putAll(cache);
+ // pass
}
/**
@@ -163,6 +87,7 @@ public final class ServiceManager {
* @hide
*/
public static class ServiceNotFoundException extends Exception {
+ // identical to the original implementation
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
}
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index 615d3c4c..f02631c7 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -722,7 +722,7 @@ public final class StrictMode {
}
/**
- * Detect when an {@link java.io.Closeable} or other object with a explict termination
+ * Detect when an {@link java.io.Closeable} or other object with an explicit termination
* method is finalized without having been closed.
*
* <p>You always want to explicitly close such objects to avoid unnecessary resources
diff --git a/android/os/ZygoteProcess.java b/android/os/ZygoteProcess.java
index 7a13ee89..670f7949 100644
--- a/android/os/ZygoteProcess.java
+++ b/android/os/ZygoteProcess.java
@@ -20,9 +20,11 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
import android.util.Slog;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import com.android.internal.util.Preconditions;
+
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
@@ -173,7 +175,7 @@ public class ZygoteProcess {
*
* When invokeWith is not null, the process will be started as a fresh app
* and not a zygote fork. Note that this is only allowed for uid 0 or when
- * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+ * runtimeFlags contains DEBUG_ENABLE_DEBUGGER.
*
* @param processClass The class to use as the process's main entry
* point.
@@ -181,7 +183,7 @@ public class ZygoteProcess {
* @param uid The user-id under which the process will run.
* @param gid The group-id under which the process will run.
* @param gids Additional group-ids associated with the process.
- * @param debugFlags Additional flags.
+ * @param runtimeFlags Additional flags.
* @param targetSdkVersion The target SDK version for the app.
* @param seInfo null-ok SELinux information for the new process.
* @param abi non-null the ABI this app should be started with.
@@ -196,7 +198,7 @@ public class ZygoteProcess {
public final Process.ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
- int debugFlags, int mountExternal,
+ int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
@@ -206,7 +208,7 @@ public class ZygoteProcess {
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
- debugFlags, mountExternal, targetSdkVersion, seInfo,
+ runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
@@ -317,7 +319,7 @@ public class ZygoteProcess {
* @param gid a POSIX gid that the new process shuold setgid() to
* @param gids null-ok; a list of supplementary group IDs that the
* new process should setgroup() to.
- * @param debugFlags Additional flags.
+ * @param runtimeFlags Additional flags for the runtime.
* @param targetSdkVersion The target SDK version for the app.
* @param seInfo null-ok SELinux information for the new process.
* @param abi the ABI the process should use.
@@ -331,7 +333,7 @@ public class ZygoteProcess {
final String niceName,
final int uid, final int gid,
final int[] gids,
- int debugFlags, int mountExternal,
+ int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
@@ -347,33 +349,7 @@ public class ZygoteProcess {
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
- if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
- argsForZygote.add("--enable-jni-logging");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
- argsForZygote.add("--enable-safemode");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) {
- argsForZygote.add("--enable-jdwp");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
- argsForZygote.add("--enable-checkjni");
- }
- if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
- argsForZygote.add("--generate-debug-info");
- }
- if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
- argsForZygote.add("--always-jit");
- }
- if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
- argsForZygote.add("--native-debuggable");
- }
- if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
- argsForZygote.add("--java-debuggable");
- }
- if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
- argsForZygote.add("--enable-assert");
- }
+ argsForZygote.add("--runtime-flags=" + runtimeFlags);
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
argsForZygote.add("--mount-external-default");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 70363468..6594cd07 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -221,8 +221,6 @@ public class StorageManager {
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
- /** {@hide} */
- public static final int FSTRIM_FLAG_BENCHMARK = IVold.FSTRIM_FLAG_BENCHMARK_AFTER;
/** @hide The volume is not encrypted. */
public static final int ENCRYPTION_STATE_NONE =
diff --git a/android/os/storage/VolumeInfo.java b/android/os/storage/VolumeInfo.java
index b8353d7a..76f79f13 100644
--- a/android/os/storage/VolumeInfo.java
+++ b/android/os/storage/VolumeInfo.java
@@ -76,21 +76,21 @@ public class VolumeInfo implements Parcelable {
/** Real volume representing internal emulated storage */
public static final String ID_EMULATED_INTERNAL = "emulated";
- public static final int TYPE_PUBLIC = IVold.TYPE_PUBLIC;
- public static final int TYPE_PRIVATE = IVold.TYPE_PRIVATE;
- public static final int TYPE_EMULATED = IVold.TYPE_EMULATED;
- public static final int TYPE_ASEC = IVold.TYPE_ASEC;
- public static final int TYPE_OBB = IVold.TYPE_OBB;
-
- public static final int STATE_UNMOUNTED = IVold.STATE_UNMOUNTED;
- public static final int STATE_CHECKING = IVold.STATE_CHECKING;
- public static final int STATE_MOUNTED = IVold.STATE_MOUNTED;
- public static final int STATE_MOUNTED_READ_ONLY = IVold.STATE_MOUNTED_READ_ONLY;
- public static final int STATE_FORMATTING = IVold.STATE_FORMATTING;
- public static final int STATE_EJECTING = IVold.STATE_EJECTING;
- public static final int STATE_UNMOUNTABLE = IVold.STATE_UNMOUNTABLE;
- public static final int STATE_REMOVED = IVold.STATE_REMOVED;
- public static final int STATE_BAD_REMOVAL = IVold.STATE_BAD_REMOVAL;
+ public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
+ public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
+ public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
+ public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
+ public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
+
+ public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED;
+ public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING;
+ public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED;
+ public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY;
+ public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING;
+ public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING;
+ public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE;
+ public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED;
+ public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 2cb3864f..40ced6ce 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -5194,17 +5194,39 @@ public final class Settings {
public static final String ALLOW_MOCK_LOCATION = "mock_location";
/**
- * A 64-bit number (as a hex string) that is randomly
+ * On Android 8.0 (API level 26) and higher versions of the platform,
+ * a 64-bit number (expressed as a hexadecimal string), unique to
+ * each combination of app-signing key, user, and device.
+ * Values of {@code ANDROID_ID} are scoped by signing key and user.
+ * The value may change if a factory reset is performed on the
+ * device or if an APK signing key changes.
+ *
+ * For more information about how the platform handles {@code ANDROID_ID}
+ * in Android 8.0 (API level 26) and higher, see <a
+ * href="{@docRoot}preview/behavior-changes.html#privacy-all">
+ * Android 8.0 Behavior Changes</a>.
+ *
+ * <p class="note"><strong>Note:</strong> For apps that were installed
+ * prior to updating the device to a version of Android 8.0
+ * (API level 26) or higher, the value of {@code ANDROID_ID} changes
+ * if the app is uninstalled and then reinstalled after the OTA.
+ * To preserve values across uninstalls after an OTA to Android 8.0
+ * or higher, developers can use
+ * <a href="{@docRoot}guide/topics/data/keyvaluebackup.html">
+ * Key/Value Backup</a>.</p>
+ *
+ * <p>In versions of the platform lower than Android 8.0 (API level 26),
+ * a 64-bit number (expressed as a hexadecimal string) that is randomly
* generated when the user first sets up the device and should remain
- * constant for the lifetime of the user's device. The value may
- * change if a factory reset is performed on the device.
- * <p class="note"><strong>Note:</strong> When a device has <a
- * href="{@docRoot}about/versions/android-4.2.html#MultipleUsers">multiple users</a>
- * (available on certain devices running Android 4.2 or higher), each user appears as a
- * completely separate device, so the {@code ANDROID_ID} value is unique to each
- * user.</p>
- *
- * <p class="note"><strong>Note:</strong> If the caller is an Instant App the id is scoped
+ * constant for the lifetime of the user's device.
+ *
+ * On devices that have
+ * <a href="{@docRoot}about/versions/android-4.2.html#MultipleUsers">
+ * multiple users</a>, each user appears as a
+ * completely separate device, so the {@code ANDROID_ID} value is
+ * unique to each user.</p>
+ *
+ * <p class="note"><strong>Note:</strong> If the caller is an Instant App the ID is scoped
* to the Instant App, it is generated when the Instant App is first installed and reset if
* the user clears the Instant App.
*/
@@ -7124,6 +7146,31 @@ public final class Settings {
* @hide
*/
public static final String LOCKDOWN_IN_POWER_MENU = "lockdown_in_power_menu";
+
+ /**
+ * Backup manager behavioral parameters.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "key_value_backup_interval_milliseconds=14400000,key_value_backup_require_charging=true"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * key_value_backup_interval_milliseconds (long)
+ * key_value_backup_fuzz_milliseconds (long)
+ * key_value_backup_require_charging (boolean)
+ * key_value_backup_required_network_type (int)
+ * full_backup_interval_milliseconds (long)
+ * full_backup_require_charging (boolean)
+ * full_backup_required_network_type (int)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * @hide
+ */
+ public static final String BACKUP_MANAGER_CONSTANTS = "backup_manager_constants";
+
/**
* This are the settings to be backed up.
*
diff --git a/android/service/settings/suggestions/Suggestion.java b/android/service/settings/suggestions/Suggestion.java
new file mode 100644
index 00000000..f27cc2eb
--- /dev/null
+++ b/android/service/settings/suggestions/Suggestion.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.settings.suggestions;
+
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Data object that has information about a device suggestion.
+ *
+ * @hide
+ */
+@SystemApi
+public final class Suggestion implements Parcelable {
+
+ private final String mId;
+ private final CharSequence mTitle;
+ private final CharSequence mSummary;
+ private final PendingIntent mPendingIntent;
+
+ /**
+ * Gets the id for the suggestion object.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Title of the suggestion that is shown to the user.
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Optional summary describing what this suggestion controls.
+ */
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * The Intent to launch when the suggestion is activated.
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ private Suggestion(Builder builder) {
+ mId = builder.mId;
+ mTitle = builder.mTitle;
+ mSummary = builder.mSummary;
+ mPendingIntent = builder.mPendingIntent;
+ }
+
+ private Suggestion(Parcel in) {
+ mId = in.readString();
+ mTitle = in.readCharSequence();
+ mSummary = in.readCharSequence();
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+ }
+
+ public static final Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
+ @Override
+ public Suggestion createFromParcel(Parcel in) {
+ return new Suggestion(in);
+ }
+
+ @Override
+ public Suggestion[] newArray(int size) {
+ return new Suggestion[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeCharSequence(mTitle);
+ dest.writeCharSequence(mSummary);
+ dest.writeParcelable(mPendingIntent, flags);
+ }
+
+ /**
+ * Builder class for {@link Suggestion}.
+ */
+ public static class Builder {
+ private final String mId;
+ private CharSequence mTitle;
+ private CharSequence mSummary;
+ private PendingIntent mPendingIntent;
+
+ public Builder(String id) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("Suggestion id cannot be empty");
+ }
+ mId = id;
+ }
+
+ /**
+ * Sets suggestion title
+ */
+ public Builder setTitle(CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets suggestion summary
+ */
+ public Builder setSummary(CharSequence summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets suggestion intent
+ */
+ public Builder setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Builds an immutable {@link Suggestion} object.
+ */
+ public Suggestion build() {
+ return new Suggestion(this /* builder */);
+ }
+ }
+}
diff --git a/android/service/settings/suggestions/SuggestionService.java b/android/service/settings/suggestions/SuggestionService.java
new file mode 100644
index 00000000..2a4c84c2
--- /dev/null
+++ b/android/service/settings/suggestions/SuggestionService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.settings.suggestions;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This is the base class for implementing suggestion service. A suggestion service is responsible
+ * to provide a collection of {@link Suggestion}s for the current user when queried.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SuggestionService extends Service {
+
+ private static final String TAG = "SuggestionService";
+ private static final boolean DEBUG = false;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new ISuggestionService.Stub() {
+ @Override
+ public List<Suggestion> getSuggestions() {
+ if (DEBUG) {
+ Log.d(TAG, "getSuggestions() " + getPackageName());
+ }
+ return onGetSuggestions();
+ }
+
+ @Override
+ public void dismissSuggestion(Suggestion suggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "dismissSuggestion() " + getPackageName());
+ }
+ onSuggestionDismissed(suggestion);
+ }
+ };
+ }
+
+ /**
+ * Return all available suggestions.
+ */
+ public abstract List<Suggestion> onGetSuggestions();
+
+ /**
+ * Dismiss a suggestion. The suggestion will not be included in future
+ * {@link #onGetSuggestions()} calls.
+ * @param suggestion
+ */
+ public abstract void onSuggestionDismissed(Suggestion suggestion);
+}
diff --git a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
index d0496e44..61ea52ba 100644
--- a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
@@ -254,9 +254,7 @@ public class PlaybackTransportControlGlue<T extends PlayerAdapter>
// playing paused paused
// paused playing playing
// ff/rw playing playing paused
- if (canPause
- && (canPlay ? mIsPlaying :
- !mIsPlaying)) {
+ if (canPause && mIsPlaying) {
mIsPlaying = false;
pause();
} else if (canPlay && !mIsPlaying) {
diff --git a/android/support/v4/app/FragmentManager.java b/android/support/v4/app/FragmentManager.java
index 20e8d9e4..6e6caa04 100644
--- a/android/support/v4/app/FragmentManager.java
+++ b/android/support/v4/app/FragmentManager.java
@@ -1594,6 +1594,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
private void animateRemoveFragment(@NonNull final Fragment fragment,
@NonNull AnimationOrAnimator anim, final int newState) {
final View viewToAnimate = fragment.mView;
+ final ViewGroup container = fragment.mContainer;
+ container.startViewTransition(viewToAnimate);
fragment.setStateAfterAnimating(newState);
if (anim.animation != null) {
Animation animation = anim.animation;
@@ -1603,6 +1605,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
@Override
public void onAnimationEnd(Animation animation) {
super.onAnimationEnd(animation);
+ container.endViewTransition(viewToAnimate);
+
if (fragment.getAnimatingAway() != null) {
fragment.setAnimatingAway(null);
moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
@@ -1612,25 +1616,18 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
setHWLayerAnimListenerIfAlpha(viewToAnimate, anim);
fragment.mView.startAnimation(animation);
} else {
- final Animator animator = anim.animator;
+ Animator animator = anim.animator;
fragment.setAnimator(anim.animator);
- final ViewGroup container = fragment.mContainer;
- if (container != null) {
- container.startViewTransition(viewToAnimate);
- }
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
- // AnimatorSet in API 26 (only) can end() during start(), so delay by posting
- if (container == null || container.indexOfChild(viewToAnimate) < 0) {
- finishAnimatedFragmentRemoval(fragment, container, viewToAnimate);
- } else {
- viewToAnimate.post(new Runnable() {
- @Override
- public void run() {
- finishAnimatedFragmentRemoval(fragment, container, viewToAnimate);
- }
- });
+ container.endViewTransition(viewToAnimate);
+ // If an animator ends immediately, we can just pretend there is no animation.
+ // When that happens the the fragment's view won't have been removed yet.
+ Animator animator = fragment.getAnimator();
+ fragment.setAnimator(null);
+ if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
+ moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
}
}
});
@@ -1640,17 +1637,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
}
}
- void finishAnimatedFragmentRemoval(Fragment fragment, ViewGroup container, View view) {
- if (container != null) {
- container.endViewTransition(view);
- }
- if (fragment.getAnimator() != null) {
- fragment.setAnimator(null);
- moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
- false);
- }
- }
-
void moveToState(Fragment f) {
moveToState(f, mCurState, 0, 0, false);
}
@@ -2646,7 +2632,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
// Give up waiting for the animation and just end it.
final int stateAfterAnimating = fragment.getStateAfterAnimating();
final View animatingAway = fragment.getAnimatingAway();
- fragment.setAnimatingAway(null);
Animation animation = animatingAway.getAnimation();
if (animation != null) {
animation.cancel();
@@ -2654,6 +2639,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
// and will instead cause the animation to infinitely loop
animatingAway.clearAnimation();
}
+ fragment.setAnimatingAway(null);
moveToState(fragment, stateAfterAnimating, 0, 0, false);
} else if (fragment.getAnimator() != null) {
fragment.getAnimator().end();
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index 8175aaee..53b111ab 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -550,7 +550,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
/**
* All the info about a connection.
*/
- private static class ConnectionRecord {
+ private class ConnectionRecord implements IBinder.DeathRecipient {
String pkg;
Bundle rootHints;
ServiceCallbacks callbacks;
@@ -559,6 +559,16 @@ public abstract class MediaBrowserServiceCompat extends Service {
ConnectionRecord() {
}
+
+ @Override
+ public void binderDied() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mConnections.remove(callbacks.asBinder());
+ }
+ });
+ }
}
/**
@@ -747,6 +757,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
} else {
try {
mConnections.put(b, connection);
+ b.linkToDeath(connection, 0);
if (mSession != null) {
callbacks.onConnect(connection.root.getRootId(),
mSession, connection.root.getExtras());
@@ -771,6 +782,7 @@ public abstract class MediaBrowserServiceCompat extends Service {
final ConnectionRecord old = mConnections.remove(b);
if (old != null) {
// TODO
+ old.callbacks.asBinder().unlinkToDeath(old, 0);
}
}
});
@@ -852,6 +864,11 @@ public abstract class MediaBrowserServiceCompat extends Service {
connection.callbacks = callbacks;
connection.rootHints = rootHints;
mConnections.put(b, connection);
+ try {
+ b.linkToDeath(connection, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IBinder is already dead.");
+ }
}
});
}
@@ -862,7 +879,10 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
- mConnections.remove(b);
+ ConnectionRecord old = mConnections.remove(b);
+ if (old != null) {
+ b.unlinkToDeath(old, 0);
+ }
}
});
}
diff --git a/android/support/v4/os/BuildCompat.java b/android/support/v4/os/BuildCompat.java
index 9a48c5f1..586557d2 100644
--- a/android/support/v4/os/BuildCompat.java
+++ b/android/support/v4/os/BuildCompat.java
@@ -60,6 +60,7 @@ public class BuildCompat {
* be removed in a future release of the Support Library. Instead use
* {@code Build.SDK_INT >= Build.VERSION_CODES#O}.
*/
+ @Deprecated
public static boolean isAtLeastO() {
return VERSION.SDK_INT >= 26;
}
@@ -68,7 +69,11 @@ public class BuildCompat {
* Checks if the device is running on a pre-release version of Android O MR1 or newer.
* <p>
* @return {@code true} if O MR1 APIs are available for use, {@code false} otherwise
+ * @deprecated Android O MR1 is a finalized release and this method is no longer necessary. It
+ * will be removed in a future release of the Support Library. Instead, use
+ * {@code Build.SDK_INT >= Build.VERSION_CODES#O_MR1}.
*/
+ @Deprecated
public static boolean isAtLeastOMR1() {
return VERSION.SDK_INT >= 27;
}
diff --git a/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
new file mode 100644
index 00000000..e15ca18f
--- /dev/null
+++ b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to handle advanced children display logic with collapsible functionality.
+ */
+final class CollapsiblePreferenceGroupController
+ implements PreferenceGroup.PreferenceInstanceStateCallback {
+
+ private final PreferenceGroupAdapter mPreferenceGroupAdapter;
+ private int mMaxPreferenceToShow;
+ private final Context mContext;
+
+ CollapsiblePreferenceGroupController(PreferenceGroup preferenceGroup,
+ PreferenceGroupAdapter preferenceGroupAdapter) {
+ mPreferenceGroupAdapter = preferenceGroupAdapter;
+ mMaxPreferenceToShow = preferenceGroup.getInitialExpandedChildrenCount();
+ mContext = preferenceGroup.getContext();
+ preferenceGroup.setPreferenceInstanceStateCallback(this);
+ }
+
+ /**
+ * Creates the visible portion of the flattened preferences.
+ *
+ * @param flattenedPreferenceList the flattened children of the preference group
+ * @return the visible portion of the flattened preferences
+ */
+ public List<Preference> createVisiblePreferencesList(List<Preference> flattenedPreferenceList) {
+ int visiblePreferenceCount = 0;
+ final List<Preference> visiblePreferenceList =
+ new ArrayList<>(flattenedPreferenceList.size());
+ // Copy only the visible preferences to the active list up to the maximum specified
+ for (final Preference preference : flattenedPreferenceList) {
+ if (preference.isVisible()) {
+ if (visiblePreferenceCount < mMaxPreferenceToShow) {
+ visiblePreferenceList.add(preference);
+ }
+ // Do no count PreferenceGroup as expanded preference because the list of its child
+ // is already contained in the flattenedPreferenceList
+ if (!(preference instanceof PreferenceGroup)) {
+ visiblePreferenceCount++;
+ }
+ }
+ }
+ // If there are any visible preferences being hidden, add an expand button to show the rest
+ // of the preferences. Clicking the expand button will show all the visible preferences and
+ // reset mMaxPreferenceToShow
+ if (showLimitedChildren() && visiblePreferenceCount > mMaxPreferenceToShow) {
+ final ExpandButton expandButton = createExpandButton(visiblePreferenceList,
+ flattenedPreferenceList);
+ visiblePreferenceList.add(expandButton);
+ }
+ return visiblePreferenceList;
+ }
+
+ /**
+ * Called when a preference has changed its visibility.
+ *
+ * @param preference The preference whose visibility has changed.
+ * @return {@code true} if view update has been handled by this controller.
+ */
+ public boolean onPreferenceVisibilityChange(Preference preference) {
+ if (showLimitedChildren()) {
+ // We only want to show up to the max number of preferences. Preference visibility
+ // change can result in the expand button being added/removed, as well as expand button
+ // summary change. Rebulid the data to ensure the correct data is shown.
+ mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Parcelable saveInstanceState(Parcelable state) {
+ final SavedState myState = new SavedState(state);
+ myState.mMaxPreferenceToShow = mMaxPreferenceToShow;
+ return myState;
+ }
+
+ @Override
+ public Parcelable restoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in saveInstanceState
+ return state;
+ }
+ SavedState myState = (SavedState) state;
+ final int restoredMaxToShow = myState.mMaxPreferenceToShow;
+ if (mMaxPreferenceToShow != restoredMaxToShow) {
+ mMaxPreferenceToShow = restoredMaxToShow;
+ mPreferenceGroupAdapter.onPreferenceHierarchyChange(null);
+ }
+ return myState.getSuperState();
+ }
+
+ private ExpandButton createExpandButton(List<Preference> visiblePreferenceList,
+ List<Preference> flattenedPreferenceList) {
+ final ExpandButton preference = new ExpandButton(mContext, visiblePreferenceList,
+ flattenedPreferenceList);
+ preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ mMaxPreferenceToShow = Integer.MAX_VALUE;
+ mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+ return true;
+ }
+ });
+ return preference;
+ }
+
+ private boolean showLimitedChildren() {
+ return mMaxPreferenceToShow != Integer.MAX_VALUE;
+ }
+
+ /**
+ * A {@link Preference} that provides capability to expand the collapsed items in the
+ * {@link PreferenceGroup}.
+ */
+ static class ExpandButton extends Preference {
+ ExpandButton(Context context, List<Preference> visiblePreferenceList,
+ List<Preference> flattenedPreferenceList) {
+ super(context);
+ initLayout();
+ setSummary(visiblePreferenceList, flattenedPreferenceList);
+ }
+
+ private void initLayout() {
+ setLayoutResource(R.layout.expand_button);
+ setIcon(R.drawable.ic_arrow_down_24dp);
+ setTitle(R.string.expand_button_title);
+ // Sets a high order so that the expand button will be placed at the bottom of the group
+ setOrder(999);
+ }
+
+ /*
+ * The summary of this will be the list of title for collapsed preferences. Iterate through
+ * the preferences not in the visible list and add its title to the summary text.
+ */
+ private void setSummary(List<Preference> visiblePreferenceList,
+ List<Preference> flattenedPreferenceList) {
+ final Preference lastVisiblePreference =
+ visiblePreferenceList.get(visiblePreferenceList.size() - 1);
+ final int collapsedIndex = flattenedPreferenceList.indexOf(lastVisiblePreference) + 1;
+ CharSequence summary = null;
+ for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) {
+ final Preference preference = flattenedPreferenceList.get(i);
+ if (preference instanceof PreferenceGroup) {
+ continue;
+ }
+ final CharSequence title = preference.getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ if (summary == null) {
+ summary = title;
+ } else {
+ summary = getContext().getString(
+ R.string.summary_collapsed_preference_list, summary, title);
+ }
+ }
+ }
+ setSummary(summary);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ holder.setDividerAllowedAbove(false);
+ }
+ }
+
+ /**
+ * A class for managing the instance state of a {@link PreferenceGroup}.
+ */
+ static class SavedState extends Preference.BaseSavedState {
+ int mMaxPreferenceToShow;
+
+ SavedState(Parcel source) {
+ super(source);
+ mMaxPreferenceToShow = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mMaxPreferenceToShow);
+ }
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/android/support/v7/preference/PreferenceGroup.java b/android/support/v7/preference/PreferenceGroup.java
index d285ee67..a951e708 100644
--- a/android/support/v7/preference/PreferenceGroup.java
+++ b/android/support/v7/preference/PreferenceGroup.java
@@ -22,7 +22,9 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Parcelable;
import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.content.res.TypedArrayUtils;
import android.support.v4.util.SimpleArrayMap;
import android.text.TextUtils;
@@ -45,6 +47,7 @@ import java.util.List;
* </div>
*
* @attr name android:orderingFromXml
+ * @attr name initialExpandedChildrenCount
*/
public abstract class PreferenceGroup extends Preference {
/**
@@ -60,6 +63,9 @@ public abstract class PreferenceGroup extends Preference {
private boolean mAttachedToHierarchy = false;
+ private int mInitialExpandedChildrenCount = Integer.MAX_VALUE;
+ private PreferenceInstanceStateCallback mPreferenceInstanceStateCallback;
+
private final SimpleArrayMap<String, Long> mIdRecycleCache = new SimpleArrayMap<>();
private final Handler mHandler = new Handler();
private final Runnable mClearRecycleCacheRunnable = new Runnable() {
@@ -83,6 +89,11 @@ public abstract class PreferenceGroup extends Preference {
TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml,
R.styleable.PreferenceGroup_orderingFromXml, true);
+ if (a.hasValue(R.styleable.PreferenceGroup_initialExpandedChildrenCount)) {
+ mInitialExpandedChildrenCount = TypedArrayUtils.getInt(
+ a, R.styleable.PreferenceGroup_initialExpandedChildrenCount,
+ R.styleable.PreferenceGroup_initialExpandedChildrenCount, -1);
+ }
a.recycle();
}
@@ -120,6 +131,35 @@ public abstract class PreferenceGroup extends Preference {
}
/**
+ * Sets the maximal number of children that are shown when the preference group is launched
+ * where the rest of the children will be hidden.
+ * If some children are hidden an expand button will be provided to show all the hidden
+ * children. Any child in any level of the hierarchy that is also a preference group (e.g.
+ * preference category) will not be counted towards the limit. But instead the children of such
+ * group will be counted.
+ * By default, all children will be shown, so the default value of this attribute is equal to
+ * Integer.MAX_VALUE.
+ *
+ * @param expandedCount the number of children that is initially shown.
+ *
+ * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+ */
+ public void setInitialExpandedChildrenCount(int expandedCount) {
+ mInitialExpandedChildrenCount = expandedCount;
+ }
+
+ /**
+ * Gets the maximal number of children that is initially shown.
+ *
+ * @return the maximal number of children that is initially shown.
+ *
+ * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+ */
+ public int getInitialExpandedChildrenCount() {
+ return mInitialExpandedChildrenCount;
+ }
+
+ /**
* Called by the inflater to add an item to this group.
*/
public void addItemFromInflater(Preference preference) {
@@ -400,6 +440,44 @@ public abstract class PreferenceGroup extends Preference {
}
}
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (mPreferenceInstanceStateCallback != null) {
+ return mPreferenceInstanceStateCallback.saveInstanceState(superState);
+ }
+ return superState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (mPreferenceInstanceStateCallback != null) {
+ state = mPreferenceInstanceStateCallback.restoreInstanceState(state);
+ }
+ super.onRestoreInstanceState(state);
+ }
+
+ /**
+ * Sets the instance state callback.
+ *
+ * @param callback The callback.
+ * @see #onSaveInstanceState()
+ * @see #onRestoreInstanceState()
+ */
+ final void setPreferenceInstanceStateCallback(PreferenceInstanceStateCallback callback) {
+ mPreferenceInstanceStateCallback = callback;
+ }
+
+ /**
+ * Gets the instance state callback.
+ *
+ * @return the instance state callback.
+ */
+ @VisibleForTesting
+ final PreferenceInstanceStateCallback getPreferenceInstanceStateCallback() {
+ return mPreferenceInstanceStateCallback;
+ }
+
/**
* Interface for PreferenceGroup Adapters to implement so that
* {@link android.support.v14.preference.PreferenceFragment#scrollToPreference(String)} and
@@ -426,4 +504,29 @@ public abstract class PreferenceGroup extends Preference {
*/
int getPreferenceAdapterPosition(Preference preference);
}
+
+ /**
+ * Interface for callback to implement so that they can save and restore the preference group's
+ * instance state.
+ */
+ interface PreferenceInstanceStateCallback {
+
+ /**
+ * Save the internal state that can later be used to create a new instance with that
+ * same state.
+ *
+ * @param state The Parcelable to save the current dynamic state.
+ */
+ Parcelable saveInstanceState(Parcelable state);
+
+ /**
+ * Restore the previously saved state from the given parcelable.
+ *
+ * @param state The Parcelable that holds the previously saved state.
+ * @return the super state if data has been saved in the state in {@link saveInstanceState}
+ * or state otherwise
+ */
+ Parcelable restoreInstanceState(Parcelable state);
+ }
+
}
diff --git a/android/support/v7/preference/PreferenceGroupAdapter.java b/android/support/v7/preference/PreferenceGroupAdapter.java
index d1c630fe..00a0c5ba 100644
--- a/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -22,6 +22,7 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.util.DiffUtil;
@@ -73,7 +74,9 @@ public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewH
private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
- private Handler mHandler = new Handler();
+ private Handler mHandler;
+
+ private CollapsiblePreferenceGroupController mPreferenceGroupController;
private Runnable mSyncRunnable = new Runnable() {
@Override
@@ -117,7 +120,14 @@ public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewH
}
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
+ this(preferenceGroup, new Handler());
+ }
+
+ private PreferenceGroupAdapter(PreferenceGroup preferenceGroup, Handler handler) {
mPreferenceGroup = preferenceGroup;
+ mHandler = handler;
+ mPreferenceGroupController =
+ new CollapsiblePreferenceGroupController(preferenceGroup, this);
// If this group gets or loses any children, let us know
mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
@@ -134,6 +144,12 @@ public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewH
syncMyPreferences();
}
+ @VisibleForTesting
+ static PreferenceGroupAdapter createInstanceWithCustomHandler(PreferenceGroup preferenceGroup,
+ Handler handler) {
+ return new PreferenceGroupAdapter(preferenceGroup, handler);
+ }
+
private void syncMyPreferences() {
for (final Preference preference : mPreferenceListInternal) {
// Clear out the listeners in anticipation of some items being removed. This listener
@@ -143,13 +159,8 @@ public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewH
final List<Preference> fullPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
flattenPreferenceGroup(fullPreferenceList, mPreferenceGroup);
- final List<Preference> visiblePreferenceList = new ArrayList<>(fullPreferenceList.size());
- // Copy only the visible preferences to the active list
- for (final Preference preference : fullPreferenceList) {
- if (preference.isVisible()) {
- visiblePreferenceList.add(preference);
- }
- }
+ final List<Preference> visiblePreferenceList =
+ mPreferenceGroupController.createVisiblePreferencesList(fullPreferenceList);
final List<Preference> oldVisibleList = mPreferenceList;
mPreferenceList = visiblePreferenceList;
@@ -277,6 +288,9 @@ public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewH
if (!mPreferenceListInternal.contains(preference)) {
return;
}
+ if (mPreferenceGroupController.onPreferenceVisibilityChange(preference)) {
+ return;
+ }
if (preference.isVisible()) {
// The preference has become visible, we need to add it in the correct location.
diff --git a/android/support/v7/recyclerview/extensions/ListAdapter.java b/android/support/v7/recyclerview/extensions/ListAdapter.java
index 8035a271..e08cb53c 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapter.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapter.java
@@ -46,7 +46,7 @@ import java.util.List;
* }
* }
*
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
index e4a53fd9..b47b833a 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
@@ -55,7 +55,7 @@ import java.util.List;
* }
* }
*
- * class MyActivity extends Activity implements LifecycleRegistryOwner {
+ * class MyActivity extends AppCompatActivity {
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
diff --git a/android/support/v7/view/menu/CascadingMenuPopup.java b/android/support/v7/view/menu/CascadingMenuPopup.java
index 73499cff..564bbfca 100644
--- a/android/support/v7/view/menu/CascadingMenuPopup.java
+++ b/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -404,14 +404,14 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
mLastPosition = nextMenuPosition;
- final int parentOffsetLeft;
- final int parentOffsetTop;
+ final int parentOffsetX;
+ final int parentOffsetY;
if (Build.VERSION.SDK_INT >= 26) {
// Anchor the submenu directly to the parent menu item view. This allows for
// accurate submenu positioning when the parent menu is being moved.
popupWindow.setAnchorView(parentView);
- parentOffsetLeft = 0;
- parentOffsetTop = 0;
+ parentOffsetX = 0;
+ parentOffsetY = 0;
} else {
// Framework does not allow anchoring to a view in another popup window. Use the
// same top-level anchor as the parent menu is using, with appropriate offsets.
@@ -428,10 +428,19 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
final int[] parentViewScreenLocation = new int[2];
parentView.getLocationOnScreen(parentViewScreenLocation);
+ // For Gravity.LEFT case, the baseline is just the left border of the view. So we
+ // can use the X of the location directly. But for Gravity.RIGHT case, the baseline
+ // is the right border. So we need add view's width with the location to make the
+ // baseline as the right border correctly.
+ if ((mDropDownGravity & (Gravity.RIGHT | Gravity.LEFT)) == Gravity.RIGHT) {
+ anchorScreenLocation[0] += mAnchorView.getWidth();
+ parentViewScreenLocation[0] += parentView.getWidth();
+ }
+
// If used as horizontal/vertical offsets, these values would position the submenu
// at the exact same position as the parent item.
- parentOffsetLeft = parentViewScreenLocation[0] - anchorScreenLocation[0];
- parentOffsetTop = parentViewScreenLocation[1] - anchorScreenLocation[1];
+ parentOffsetX = parentViewScreenLocation[0] - anchorScreenLocation[0];
+ parentOffsetY = parentViewScreenLocation[1] - anchorScreenLocation[1];
}
// Adjust the horizontal offset to display the submenu to the right or to the left
@@ -441,22 +450,22 @@ final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKey
final int x;
if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
if (showOnRight) {
- x = parentOffsetLeft + menuWidth;
+ x = parentOffsetX + menuWidth;
} else {
- x = parentOffsetLeft - parentView.getWidth();
+ x = parentOffsetX - parentView.getWidth();
}
} else {
if (showOnRight) {
- x = parentOffsetLeft + parentView.getWidth();
+ x = parentOffsetX + parentView.getWidth();
} else {
- x = parentOffsetLeft - menuWidth;
+ x = parentOffsetX - menuWidth;
}
}
popupWindow.setHorizontalOffset(x);
// Vertically align with the parent item.
popupWindow.setOverlapAnchor(true);
- popupWindow.setVerticalOffset(parentOffsetTop);
+ popupWindow.setVerticalOffset(parentOffsetY);
} else {
if (mHasXOffset) {
popupWindow.setHorizontalOffset(mXOffset);
diff --git a/android/support/v7/widget/AppCompatImageButton.java b/android/support/v7/widget/AppCompatImageButton.java
index 90e6aa9f..b2b1f106 100644
--- a/android/support/v7/widget/AppCompatImageButton.java
+++ b/android/support/v7/widget/AppCompatImageButton.java
@@ -23,7 +23,6 @@ import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
@@ -102,14 +101,6 @@ public class AppCompatImageButton extends ImageButton implements TintableBackgro
}
@Override
- public void setImageIcon(@Nullable Icon icon) {
- super.setImageIcon(icon);
- if (mImageHelper != null) {
- mImageHelper.applySupportImageTint();
- }
- }
-
- @Override
public void setImageURI(@Nullable Uri uri) {
super.setImageURI(uri);
if (mImageHelper != null) {
diff --git a/android/support/v7/widget/AppCompatImageView.java b/android/support/v7/widget/AppCompatImageView.java
index 0844f9a5..f50799ee 100644
--- a/android/support/v7/widget/AppCompatImageView.java
+++ b/android/support/v7/widget/AppCompatImageView.java
@@ -23,7 +23,6 @@ import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
@@ -112,14 +111,6 @@ public class AppCompatImageView extends ImageView implements TintableBackgroundV
}
@Override
- public void setImageIcon(@Nullable Icon icon) {
- super.setImageIcon(icon);
- if (mImageHelper != null) {
- mImageHelper.applySupportImageTint();
- }
- }
-
- @Override
public void setImageURI(@Nullable Uri uri) {
super.setImageURI(uri);
if (mImageHelper != null) {
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index dea8546f..7009733c 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -44,6 +44,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import android.support.v4.os.TraceCompat;
+import android.support.v4.util.Preconditions;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.InputDeviceCompat;
import android.support.v4.view.MotionEventCompat;
@@ -417,6 +418,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
*/
private int mDispatchScrollCounter = 0;
+ @NonNull
+ private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory();
private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
ItemAnimator mItemAnimator = new DefaultItemAnimator();
@@ -2306,7 +2309,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (mLeftGlow != null) {
return;
}
- mLeftGlow = new EdgeEffect(getContext());
+ mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT);
if (mClipToPadding) {
mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2319,7 +2322,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (mRightGlow != null) {
return;
}
- mRightGlow = new EdgeEffect(getContext());
+ mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT);
if (mClipToPadding) {
mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2332,7 +2335,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (mTopGlow != null) {
return;
}
- mTopGlow = new EdgeEffect(getContext());
+ mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP);
if (mClipToPadding) {
mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2346,7 +2349,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (mBottomGlow != null) {
return;
}
- mBottomGlow = new EdgeEffect(getContext());
+ mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM);
if (mClipToPadding) {
mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2360,6 +2363,32 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
+ * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}.
+ * <p>
+ * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared
+ * and new effects are created as needed using
+ * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)}
+ *
+ * @param edgeEffectFactory The {@link EdgeEffectFactory} instance.
+ */
+ public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) {
+ Preconditions.checkNotNull(edgeEffectFactory);
+ mEdgeEffectFactory = edgeEffectFactory;
+ invalidateGlows();
+ }
+
+ /**
+ * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing
+ * was set.
+ *
+ * @return The previously set {@link EdgeEffectFactory}
+ * @see #setEdgeEffectFactory(EdgeEffectFactory)
+ */
+ public EdgeEffectFactory getEdgeEffectFactory() {
+ return mEdgeEffectFactory;
+ }
+
+ /**
* Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
* in the Adapter but not visible in the UI), it employs a more involved focus search strategy
* that differs from other ViewGroups.
@@ -5130,6 +5159,46 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
+ * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews.
+ *
+ * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory)
+ */
+ public static class EdgeEffectFactory {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM})
+ public @interface EdgeDirection {}
+
+ /**
+ * Direction constant for the left edge
+ */
+ public static final int DIRECTION_LEFT = 0;
+
+ /**
+ * Direction constant for the top edge
+ */
+ public static final int DIRECTION_TOP = 1;
+
+ /**
+ * Direction constant for the right edge
+ */
+ public static final int DIRECTION_RIGHT = 2;
+
+ /**
+ * Direction constant for the bottom edge
+ */
+ public static final int DIRECTION_BOTTOM = 3;
+
+ /**
+ * Create a new EdgeEffect for the provided direction.
+ */
+ protected @NonNull EdgeEffect createEdgeEffect(RecyclerView view,
+ @EdgeDirection int direction) {
+ return new EdgeEffect(view.getContext());
+ }
+ }
+
+ /**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* <p>
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
diff --git a/android/telecom/DefaultDialerManager.java b/android/telecom/DefaultDialerManager.java
index 2a707c91..1806aee2 100644
--- a/android/telecom/DefaultDialerManager.java
+++ b/android/telecom/DefaultDialerManager.java
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Process;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -163,7 +164,10 @@ public class DefaultDialerManager {
for (ResolveInfo resolveInfo : resolveInfoList) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
- if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) {
+ if (activityInfo != null
+ && !packageNames.contains(activityInfo.packageName)
+ // ignore cross profile intent handler
+ && resolveInfo.targetUserId == UserHandle.USER_CURRENT) {
packageNames.add(activityInfo.packageName);
}
}
diff --git a/android/telephony/MbmsDownloadManager.java b/android/telephony/MbmsDownloadManager.java
deleted file mode 100644
index 1e8cf185..00000000
--- a/android/telephony/MbmsDownloadManager.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2016 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.telephony;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.telephony.mbms.DownloadProgressListener;
-import android.telephony.mbms.FileInfo;
-import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadReceiver;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsTempFileProvider;
-import android.telephony.mbms.MbmsUtils;
-import android.telephony.mbms.vendor.IMbmsDownloadService;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-/** @hide */
-public class MbmsDownloadManager {
- private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
-
- /** @hide */
- // TODO: systemapi
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
- "android.telephony.action.EmbmsDownload";
-
- /**
- * Integer extra indicating the result code of the download. One of
- * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
- */
- public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
-
- /**
- * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
- * is for. Must not be null.
- */
- public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
-
- /**
- * Extra containing a single {@link Uri} indicating the location of the successfully
- * downloaded file. Set on the intent provided via
- * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
- * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
- * {@link #RESULT_SUCCESSFUL}.
- */
- public static final String EXTRA_COMPLETED_FILE_URI =
- "android.telephony.mbms.extra.COMPLETED_FILE_URI";
-
- public static final int RESULT_SUCCESSFUL = 1;
- public static final int RESULT_CANCELLED = 2;
- public static final int RESULT_EXPIRED = 3;
- public static final int RESULT_IO_ERROR = 4;
- // TODO - more results!
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
- STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
- public @interface DownloadStatus {}
-
- public static final int STATUS_UNKNOWN = 0;
- public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
- public static final int STATUS_PENDING_DOWNLOAD = 2;
- public static final int STATUS_PENDING_REPAIR = 3;
- public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
-
- private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
-
- private final Context mContext;
- private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
- }
- };
-
- private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
- private final MbmsDownloadManagerCallback mCallback;
-
- private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, int subId) {
- mContext = context;
- mCallback = callback;
- mSubscriptionId = subId;
- }
-
- /**
- * Create a new MbmsDownloadManager using the system default data subscription ID.
- * See {@link #create(Context, MbmsDownloadManagerCallback, int)}
- *
- * @hide
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback listener)
- throws MbmsException {
- return create(context, listener, SubscriptionManager.getDefaultSubscriptionId());
- }
-
- /**
- * Create a new MbmsDownloadManager using the given subscription ID.
- *
- * Note that this call will bind a remote service and that may take a bit. The instance of
- * {@link MbmsDownloadManager} that is returned will not be ready for use until
- * {@link MbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
- * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
- *
- * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
- *
- * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this
- * method while there is an active instance of {@link MbmsDownloadManager} in your process
- * (in other words, one that has not had {@link #dispose()} called on it), this method will
- * throw an {@link MbmsException}. If you call this method in a different process
- * running under the same UID, an error will be indicated via
- * {@link MbmsDownloadManagerCallback#error(int, String)}.
- *
- * Note that initialization may fail asynchronously. If you wish to try again after you
- * receive such an asynchronous error, you must call dispose() on the instance of
- * {@link MbmsDownloadManager} that you received before calling this method again.
- *
- * @param context The instance of {@link Context} to use
- * @param listener A callback to get asynchronous error messages and file service updates.
- * @param subscriptionId The data subscription ID to use
- * @hide
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback listener, int subscriptionId)
- throws MbmsException {
- if (!sIsInitialized.compareAndSet(false, true)) {
- throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
- }
- MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId);
- try {
- mdm.bindAndInitialize();
- } catch (MbmsException e) {
- sIsInitialized.set(false);
- throw e;
- }
- return mdm;
- }
-
- private void bindAndInitialize() throws MbmsException {
- MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- IMbmsDownloadService downloadService =
- IMbmsDownloadService.Stub.asInterface(service);
- int result;
- try {
- result = downloadService.initialize(mSubscriptionId, mCallback);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Service died before initialization");
- sIsInitialized.set(false);
- return;
- } catch (RuntimeException e) {
- Log.e(LOG_TAG, "Runtime exception during initialization");
- sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
- e.toString());
- sIsInitialized.set(false);
- return;
- }
- if (result != MbmsException.SUCCESS) {
- sendErrorToApp(result, "Error returned during initialization");
- sIsInitialized.set(false);
- return;
- }
- try {
- downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException e) {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
- "Middleware lost during initialization");
- sIsInitialized.set(false);
- return;
- }
- mService.set(downloadService);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- sIsInitialized.set(false);
- mService.set(null);
- }
- });
- }
-
- /**
- * An inspection API to retrieve the list of available
- * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
- * The results are returned asynchronously via a call to
- * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)}
- *
- * The serviceClasses argument lets the app filter on types of programming and is opaque data
- * negotiated beforehand between the app and the carrier.
- *
- * This may throw an {@link MbmsException} containing one of the following errors:
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
- *
- * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)}
- * callback can include any of the errors except:
- * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
- *
- * @param classList A list of service classes which the app wishes to receive
- * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks
- * about. Subsequent calls to this method will replace this list of service
- * classes (i.e. the middleware will no longer send updates for services
- * matching classes only in the old list).
- */
- public void getFileServices(List<String> classList) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
- try {
- int returnCode = downloadService.getFileServices(mSubscriptionId, classList);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Sets the temp file root for downloads.
- * All temp files created for the middleware to write to will be contained in the specified
- * directory. Applications that wish to specify a location only need to call this method once
- * as long their data is persisted in storage -- the argument will be stored both in a
- * local instance of {@link android.content.SharedPreferences} and by the middleware.
- *
- * If this method is not called at least once before calling
- * {@link #download(DownloadRequest, DownloadProgressListener)}, the framework
- * will default to a directory formed by the concatenation of the app's files directory and
- * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
- *
- * Before calling this method, the app must cancel all of its pending
- * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
- * an {@link MbmsException} will be thrown with code
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
- * provided directory is the same as what has been previously configured.
- *
- * The {@link File} supplied as a root temp file directory must already exist. If not, an
- * {@link IllegalArgumentException} will be thrown.
- * @param tempFileRootDirectory A directory to place temp files in.
- */
- public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
- if (!tempFileRootDirectory.exists()) {
- throw new IllegalArgumentException("Provided directory does not exist");
- }
- if (!tempFileRootDirectory.isDirectory()) {
- throw new IllegalArgumentException("Provided File is not a directory");
- }
- String filePath;
- try {
- filePath = tempFileRootDirectory.getCanonicalPath();
- } catch (IOException e) {
- throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
- }
-
- try {
- int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
-
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
- }
-
- /**
- * Retrieves the currently configured temp file root directory. Returns the file that was
- * configured via {@link #setTempFileRootDirectory(File)} or the default directory
- * {@link #download(DownloadRequest, DownloadProgressListener)} was called without ever setting
- * the temp file root. If neither method has been called since the last time the app's shared
- * preferences were reset, returns null.
- *
- * @return A {@link File} pointing to the configured temp file directory, or null if not yet
- * configured.
- */
- public @Nullable File getTempFileRootDirectory() {
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
- if (path != null) {
- return new File(path);
- }
- return null;
- }
-
- /**
- * Requests a download of a file that is available via multicast.
- *
- * downloadListener is an optional callback object which can be used to get progress reports
- * of a currently occuring download. Note this can only run while the calling app
- * is running, so future downloads will simply result in resultIntents being sent
- * for completed or errored-out downloads. A NULL indicates no callbacks are needed.
- *
- * May throw an {@link IllegalArgumentException}
- *
- * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
- * this method will create a directory at the default location defined at
- * {@link MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
- * file root directory.
- *
- * Asynchronous errors through the listener include any of the errors
- *
- * @param request The request that specifies what should be downloaded
- * @param progressListener Optional listener that will be provided progress updates
- * if the app is running.
- */
- public void download(DownloadRequest request, DownloadProgressListener progressListener)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- // Check to see whether the app's set a temp root dir yet, and set it if not.
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
- File tempRootDirectory = new File(mContext.getFilesDir(),
- MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
- tempRootDirectory.mkdirs();
- setTempFileRootDirectory(tempRootDirectory);
- }
-
- checkValidDownloadDestination(request);
- writeDownloadRequestToken(request);
- try {
- downloadService.download(request, progressListener);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Returns a list of pending {@link DownloadRequest}s that originated from this application.
- * A pending request is one that was issued via
- * {@link #download(DownloadRequest, DownloadProgressListener)} but not cancelled through
- * {@link #cancelDownload(DownloadRequest)}.
- * @return A list, possibly empty, of {@link DownloadRequest}s
- */
- public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- return downloadService.listPendingDownloads(mSubscriptionId);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Attempts to cancel the specified {@link DownloadRequest}.
- *
- * If the middleware is not aware of the specified download request, an MbmsException will be
- * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
- *
- * If this method returns without throwing an exception, you may assume that cancellation
- * was successful.
- * @param downloadRequest The download request that you wish to cancel.
- */
- public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- int result = downloadService.cancelDownload(downloadRequest);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- deleteDownloadRequestToken(downloadRequest);
- }
-
- /**
- * Gets information about the status of a file pending download.
- *
- * If the middleware has not yet been properly initialized or if it has no records of the
- * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
- * {@link #STATUS_UNKNOWN} will be returned.
- *
- * @param downloadRequest The download request to query.
- * @param fileInfo The particular file within the request to get information on.
- * @return The status of the download.
- */
- @DownloadStatus
- public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- return downloadService.getDownloadStatus(downloadRequest, fileInfo);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Resets the middleware's knowledge of previously-downloaded files in this download request.
- *
- * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
- * files whose server-reported hash matches one of the already-downloaded files. This means
- * that if the file is accidentally deleted by the user or by the app, the middleware will
- * not try to download it again.
- * This method will reset the middleware's cache of hashes for the provided
- * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
- * when available.
- * This will not interrupt in-progress downloads.
- *
- * If the middleware is not aware of the specified download request, an MbmsException will be
- * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
- *
- * May throw a {@link MbmsException} with error code
- * @param downloadRequest The request to re-download files for.
- */
- public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- int result = downloadService.resetDownloadKnowledge(downloadRequest);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- public void dispose() {
- try {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- Log.i(LOG_TAG, "Service already dead");
- return;
- }
- downloadService.dispose(mSubscriptionId);
- } catch (RemoteException e) {
- // Ignore
- Log.i(LOG_TAG, "Remote exception while disposing of service");
- } finally {
- mService.set(null);
- sIsInitialized.set(false);
- }
- }
-
- private void writeDownloadRequestToken(DownloadRequest request) {
- File token = getDownloadRequestTokenPath(request);
- if (!token.getParentFile().exists()) {
- token.getParentFile().mkdirs();
- }
- if (token.exists()) {
- Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
- return;
- }
- try {
- if (!token.createNewFile()) {
- throw new RuntimeException("Failed to create download token for request "
- + request);
- }
- } catch (IOException e) {
- throw new RuntimeException("Failed to create download token for request " + request
- + " due to IOException " + e);
- }
- }
-
- private void deleteDownloadRequestToken(DownloadRequest request) {
- File token = getDownloadRequestTokenPath(request);
- if (!token.isFile()) {
- Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
- return;
- }
- if (!token.delete()) {
- Log.w(LOG_TAG, "Couldn't delete download token at " + token);
- }
- }
-
- private File getDownloadRequestTokenPath(DownloadRequest request) {
- File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
- request.getFileServiceId());
- String downloadTokenFileName = request.getHash()
- + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
- return new File(tempFileLocation, downloadTokenFileName);
- }
-
- /**
- * Verifies the following:
- * If a request is multi-part,
- * 1. Destination Uri must exist and be a directory
- * 2. Directory specified must contain no files.
- * Otherwise
- * 1. The file specified by the destination Uri must not exist.
- */
- private void checkValidDownloadDestination(DownloadRequest request) {
- File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
- if (request.isMultipartDownload()) {
- if (!toFile.isDirectory()) {
- throw new IllegalArgumentException("Multipart download must specify valid " +
- "destination directory.");
- }
- if (toFile.listFiles().length > 0) {
- throw new IllegalArgumentException("Destination directory must be clear of all " +
- "files.");
- }
- } else {
- if (toFile.exists()) {
- throw new IllegalArgumentException("Destination file must not exist.");
- }
- }
- }
-
- private void sendErrorToApp(int errorCode, String message) {
- try {
- mCallback.error(errorCode, message);
- } catch (RemoteException e) {
- // Ignore, should not happen locally.
- }
- }
-}
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
new file mode 100644
index 00000000..ebac0419
--- /dev/null
+++ b/android/telephony/MbmsDownloadSession.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2016 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadStateCallback;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.InternalDownloadSessionCallback;
+import android.telephony.mbms.InternalDownloadStateCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadReceiver;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsTempFileProvider;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/**
+ * This class provides functionality for file download over MBMS.
+ */
+public class MbmsDownloadSession implements AutoCloseable {
+ private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();
+
+ /**
+ * Service action which must be handled by the middleware implementing the MBMS file download
+ * interface.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
+ "android.telephony.action.EmbmsDownload";
+
+ /**
+ * Integer extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the result code of the download. One of
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
+ * {@link #RESULT_IO_ERROR}.
+ *
+ * This extra may also be used by the middleware when it is sending intents to the app.
+ */
+ public static final String EXTRA_MBMS_DOWNLOAD_RESULT =
+ "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
+
+ /**
+ * {@link FileInfo} extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the file for which the download result is for. Never null.
+ *
+ * This extra may also be used by the middleware when it is sending intents to the app.
+ */
+ public static final String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
+
+ /**
+ * {@link Uri} extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the location of the successfully downloaded file within the temp file root set
+ * via {@link #setTempFileRootDirectory(File)}.
+ * While you may use this file in-place, it is highly encouraged that you move
+ * this file to a different location after receiving the download completion intent, as this
+ * file resides within the temp file directory.
+ *
+ * Will always be set to a non-null value if
+ * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
+ */
+ public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
+ "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+
+ /**
+ * Extra containing the {@link DownloadRequest} for which the download result or file
+ * descriptor request is for. Must not be null.
+ */
+ public static final String EXTRA_MBMS_DOWNLOAD_REQUEST =
+ "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
+
+ /**
+ * The default directory name for all MBMS temp files. If you call
+ * {@link #download(DownloadRequest)} without first calling
+ * {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
+ * path returned by {@link Context#getFilesDir()}.
+ */
+ public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
+
+ /**
+ * Indicates that the download was successful.
+ */
+ public static final int RESULT_SUCCESSFUL = 1;
+
+ /**
+ * Indicates that the download was cancelled via {@link #cancelDownload(DownloadRequest)}.
+ */
+ public static final int RESULT_CANCELLED = 2;
+
+ /**
+ * Indicates that the download will not be completed due to the expiration of its download
+ * window on the carrier's network.
+ */
+ public static final int RESULT_EXPIRED = 3;
+
+ /**
+ * Indicates that the download will not be completed due to an I/O error incurred while
+ * writing to temp files. This commonly indicates that the device is out of storage space,
+ * but may indicate other conditions as well (such as an SD card being removed).
+ */
+ public static final int RESULT_IO_ERROR = 4;
+ // TODO - more results!
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
+ STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
+ public @interface DownloadStatus {}
+
+ /**
+ * Indicates that the middleware has no information on the file.
+ */
+ public static final int STATUS_UNKNOWN = 0;
+
+ /**
+ * Indicates that the file is actively downloading.
+ */
+ public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
+
+ /**
+ * TODO: I don't know...
+ */
+ public static final int STATUS_PENDING_DOWNLOAD = 2;
+
+ /**
+ * Indicates that the file is being repaired after the download being interrupted.
+ */
+ public static final int STATUS_PENDING_REPAIR = 3;
+
+ /**
+ * Indicates that the file is waiting to download because its download window has not yet
+ * started.
+ */
+ public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
+
+ private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+ private final Context mContext;
+ private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
+ }
+ };
+
+ private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
+ private final InternalDownloadSessionCallback mInternalCallback;
+ private final Map<DownloadStateCallback, InternalDownloadStateCallback>
+ mInternalDownloadCallbacks = new HashMap<>();
+
+ private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,
+ int subscriptionId, Handler handler) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mInternalCallback = new InternalDownloadSessionCallback(callback, handler);
+ }
+
+ /**
+ * Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
+ * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}
+ */
+ public static MbmsDownloadSession create(@NonNull Context context,
+ @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {
+ return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+ }
+
+ /**
+ * Create a new MbmsDownloadManager using the given subscription ID.
+ *
+ * Note that this call will bind a remote service and that may take a bit. The instance of
+ * {@link MbmsDownloadSession} that is returned will not be ready for use until
+ * {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback.
+ * If you attempt to use the instance before it is ready, an {@link IllegalStateException}
+ * will be thrown or an error will be delivered through
+ * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+ *
+ * This also may throw an {@link IllegalArgumentException}.
+ *
+ * You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this
+ * method while there is an active instance of {@link MbmsDownloadSession} in your process
+ * (in other words, one that has not had {@link #close()} called on it), this method will
+ * throw an {@link IllegalStateException}. If you call this method in a different process
+ * running under the same UID, an error will be indicated via
+ * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+ *
+ * Note that initialization may fail asynchronously. If you wish to try again after you
+ * receive such an asynchronous error, you must call {@link #close()} on the instance of
+ * {@link MbmsDownloadSession} that you received before calling this method again.
+ *
+ * @param context The instance of {@link Context} to use
+ * @param callback A callback to get asynchronous error messages and file service updates.
+ * @param subscriptionId The data subscription ID to use
+ * @param handler The {@link Handler} on which callbacks should be enqueued.
+ * @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
+ * setup.
+ */
+ public static @Nullable MbmsDownloadSession create(@NonNull Context context,
+ final @NonNull MbmsDownloadSessionCallback callback,
+ int subscriptionId, @NonNull Handler handler) {
+ if (!sIsInitialized.compareAndSet(false, true)) {
+ throw new IllegalStateException("Cannot have two active instances");
+ }
+ MbmsDownloadSession session =
+ new MbmsDownloadSession(context, callback, subscriptionId, handler);
+ final int result = session.bindAndInitialize();
+ if (result != MbmsErrors.SUCCESS) {
+ sIsInitialized.set(false);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(result, null);
+ }
+ });
+ return null;
+ }
+ return session;
+ }
+
+ private int bindAndInitialize() {
+ return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IMbmsDownloadService downloadService =
+ IMbmsDownloadService.Stub.asInterface(service);
+ int result;
+ try {
+ result = downloadService.initialize(mSubscriptionId, mInternalCallback);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Service died before initialization");
+ sIsInitialized.set(false);
+ return;
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "Runtime exception during initialization");
+ sendErrorToApp(
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ e.toString());
+ sIsInitialized.set(false);
+ return;
+ }
+ if (result != MbmsErrors.SUCCESS) {
+ sendErrorToApp(result, "Error returned during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ try {
+ downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+ "Middleware lost during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ mService.set(downloadService);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ sIsInitialized.set(false);
+ mService.set(null);
+ }
+ });
+ }
+
+ /**
+ * An inspection API to retrieve the list of available
+ * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
+ * The results are returned asynchronously via a call to
+ * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)}
+ *
+ * Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
+ * callback may include any of the errors that are not specific to the streaming use-case.
+ *
+ * May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.
+ *
+ * @param classList A list of service classes which the app wishes to receive
+ * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks
+ * about. Subsequent calls to this method will replace this list of service
+ * classes (i.e. the middleware will no longer send updates for services
+ * matching classes only in the old list).
+ * Values in this list should be negotiated with the wireless carrier prior
+ * to using this API.
+ */
+ public void requestUpdateFileServices(@NonNull List<String> classList) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+ try {
+ int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Sets the temp file root for downloads.
+ * All temp files created for the middleware to write to will be contained in the specified
+ * directory. Applications that wish to specify a location only need to call this method once
+ * as long their data is persisted in storage -- the argument will be stored both in a
+ * local instance of {@link android.content.SharedPreferences} and by the middleware.
+ *
+ * If this method is not called at least once before calling
+ * {@link #download(DownloadRequest)}, the framework
+ * will default to a directory formed by the concatenation of the app's files directory and
+ * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
+ *
+ * Before calling this method, the app must cancel all of its pending
+ * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
+ * you will receive an asynchronous error with code
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
+ * provided directory is the same as what has been previously configured.
+ *
+ * The {@link File} supplied as a root temp file directory must already exist. If not, an
+ * {@link IllegalArgumentException} will be thrown. In addition, as an additional sanity
+ * check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp
+ * file root directory to one of your data roots (the value of {@link Context#getDataDir()},
+ * {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).
+ * @param tempFileRootDirectory A directory to place temp files in.
+ */
+ public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+ try {
+ validateTempFileRootSanity(tempFileRootDirectory);
+ } catch (IOException e) {
+ throw new IllegalStateException("Got IOException checking directory sanity");
+ }
+ String filePath;
+ try {
+ filePath = tempFileRootDirectory.getCanonicalPath();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
+ }
+
+ try {
+ int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
+ if (result != MbmsErrors.SUCCESS) {
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
+ }
+
+ private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException {
+ if (!tempFileRootDirectory.exists()) {
+ throw new IllegalArgumentException("Provided directory does not exist");
+ }
+ if (!tempFileRootDirectory.isDirectory()) {
+ throw new IllegalArgumentException("Provided File is not a directory");
+ }
+ String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath();
+ if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your data dir");
+ }
+ if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your cache dir");
+ }
+ if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your files dir");
+ }
+ }
+ /**
+ * Retrieves the currently configured temp file root directory. Returns the file that was
+ * configured via {@link #setTempFileRootDirectory(File)} or the default directory
+ * {@link #download(DownloadRequest)} was called without ever
+ * setting the temp file root. If neither method has been called since the last time the app's
+ * shared preferences were reset, returns {@code null}.
+ *
+ * @return A {@link File} pointing to the configured temp file directory, or null if not yet
+ * configured.
+ */
+ public @Nullable File getTempFileRootDirectory() {
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
+ if (path != null) {
+ return new File(path);
+ }
+ return null;
+ }
+
+ /**
+ * Requests the download of a file or set of files that the carrier has indicated to be
+ * available.
+ *
+ * May throw an {@link IllegalArgumentException}
+ *
+ * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
+ * this method will create a directory at the default location defined at
+ * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
+ * file root directory.
+ *
+ * Asynchronous errors through the callback may include any error not specific to the
+ * streaming use-case.
+ * @param request The request that specifies what should be downloaded.
+ */
+ public void download(@NonNull DownloadRequest request) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ // Check to see whether the app's set a temp root dir yet, and set it if not.
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
+ File tempRootDirectory = new File(mContext.getFilesDir(),
+ DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
+ tempRootDirectory.mkdirs();
+ setTempFileRootDirectory(tempRootDirectory);
+ }
+
+ writeDownloadRequestToken(request);
+ try {
+ downloadService.download(request);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Returns a list of pending {@link DownloadRequest}s that originated from this application.
+ * A pending request is one that was issued via
+ * {@link #download(DownloadRequest)} but not cancelled through
+ * {@link #cancelDownload(DownloadRequest)}.
+ * @return A list, possibly empty, of {@link DownloadRequest}s
+ */
+ public @NonNull List<DownloadRequest> listPendingDownloads() {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ return downloadService.listPendingDownloads(mSubscriptionId);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Registers a callback for a {@link DownloadRequest} previously requested via
+ * {@link #download(DownloadRequest)}. This callback will only be called as long as both this
+ * app and the middleware are both running -- if either one stops, no further calls on the
+ * provided {@link DownloadStateCallback} will be enqueued.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param request The {@link DownloadRequest} that you want updates on.
+ * @param callback The callback that should be called when the middleware has information to
+ * share on the download.
+ * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+ */
+ public void registerStateCallback(@NonNull DownloadRequest request,
+ @NonNull DownloadStateCallback callback,
+ @NonNull Handler handler) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ InternalDownloadStateCallback internalCallback =
+ new InternalDownloadStateCallback(callback, handler);
+
+ try {
+ int result = downloadService.registerStateCallback(request, internalCallback);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+ mInternalDownloadCallbacks.put(callback, internalCallback);
+ }
+
+ /**
+ * Un-register a callback previously registered via
+ * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After
+ * this method is called, no further callbacks will be enqueued on the {@link Handler}
+ * provided upon registration, even if this method throws an exception.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param request The {@link DownloadRequest} provided during registration
+ * @param callback The callback provided during registration.
+ */
+ public void unregisterStateCallback(@NonNull DownloadRequest request,
+ @NonNull DownloadStateCallback callback) {
+ try {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ InternalDownloadStateCallback internalCallback =
+ mInternalDownloadCallbacks.get(callback);
+
+ try {
+ int result = downloadService.unregisterStateCallback(request, internalCallback);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ } finally {
+ InternalDownloadStateCallback internalCallback =
+ mInternalDownloadCallbacks.remove(callback);
+ if (internalCallback != null) {
+ internalCallback.stop();
+ }
+ }
+ }
+
+ /**
+ * Attempts to cancel the specified {@link DownloadRequest}.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param downloadRequest The download request that you wish to cancel.
+ */
+ public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ int result = downloadService.cancelDownload(downloadRequest);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+ deleteDownloadRequestToken(downloadRequest);
+ }
+
+ /**
+ * Gets information about the status of a file pending download.
+ *
+ * If there was a problem communicating with the middleware or if it has no records of the
+ * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
+ * {@link #STATUS_UNKNOWN} will be returned.
+ *
+ * @param downloadRequest The download request to query.
+ * @param fileInfo The particular file within the request to get information on.
+ * @return The status of the download.
+ */
+ @DownloadStatus
+ public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ return downloadService.getDownloadStatus(downloadRequest, fileInfo);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return STATUS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Resets the middleware's knowledge of previously-downloaded files in this download request.
+ *
+ * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
+ * files whose server-reported hash matches one of the already-downloaded files. This means
+ * that if the file is accidentally deleted by the user or by the app, the middleware will
+ * not try to download it again.
+ * This method will reset the middleware's cache of hashes for the provided
+ * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
+ * when available.
+ * This will not interrupt in-progress downloads.
+ *
+ * This is distinct from cancelling and re-issuing the download request -- if you cancel and
+ * re-issue, the middleware will not clear its cache of download state information.
+ *
+ * If the middleware is not aware of the specified download request, an
+ * {@link IllegalArgumentException} will be thrown.
+ *
+ * @param downloadRequest The request to re-download files for.
+ */
+ public void resetDownloadKnowledge(DownloadRequest downloadRequest) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ int result = downloadService.resetDownloadKnowledge(downloadRequest);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Terminates this instance.
+ *
+ * After this method returns,
+ * no further callbacks originating from the middleware will be enqueued on the provided
+ * instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been
+ * enqueued will still be delivered.
+ *
+ * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to
+ * obtain another instance of {@link MbmsDownloadSession} immediately after this method
+ * returns.
+ *
+ * May throw an {@link IllegalStateException}
+ */
+ @Override
+ public void close() {
+ try {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ Log.i(LOG_TAG, "Service already dead");
+ return;
+ }
+ downloadService.dispose(mSubscriptionId);
+ } catch (RemoteException e) {
+ // Ignore
+ Log.i(LOG_TAG, "Remote exception while disposing of service");
+ } finally {
+ mService.set(null);
+ sIsInitialized.set(false);
+ mInternalCallback.stop();
+ }
+ }
+
+ private void writeDownloadRequestToken(DownloadRequest request) {
+ File token = getDownloadRequestTokenPath(request);
+ if (!token.getParentFile().exists()) {
+ token.getParentFile().mkdirs();
+ }
+ if (token.exists()) {
+ Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
+ return;
+ }
+ try {
+ if (!token.createNewFile()) {
+ throw new RuntimeException("Failed to create download token for request "
+ + request);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create download token for request " + request
+ + " due to IOException " + e);
+ }
+ }
+
+ private void deleteDownloadRequestToken(DownloadRequest request) {
+ File token = getDownloadRequestTokenPath(request);
+ if (!token.isFile()) {
+ Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
+ return;
+ }
+ if (!token.delete()) {
+ Log.w(LOG_TAG, "Couldn't delete download token at " + token);
+ }
+ }
+
+ private File getDownloadRequestTokenPath(DownloadRequest request) {
+ File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
+ request.getFileServiceId());
+ String downloadTokenFileName = request.getHash()
+ + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+ return new File(tempFileLocation, downloadTokenFileName);
+ }
+
+ private void sendErrorToApp(int errorCode, String message) {
+ try {
+ mInternalCallback.onError(errorCode, message);
+ } catch (RemoteException e) {
+ // Ignore, should not happen locally.
+ }
+ }
+}
diff --git a/android/telephony/MbmsStreamingManager.java b/android/telephony/MbmsStreamingSession.java
index b6b253ec..a8c46079 100644
--- a/android/telephony/MbmsStreamingManager.java
+++ b/android/telephony/MbmsStreamingSession.java
@@ -16,6 +16,8 @@
package android.telephony;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.content.ComponentName;
@@ -25,18 +27,20 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
-import android.telephony.mbms.InternalStreamingManagerCallback;
+import android.telephony.mbms.InternalStreamingSessionCallback;
import android.telephony.mbms.InternalStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.ArraySet;
import android.util.Log;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -45,8 +49,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
/**
* This class provides functionality for streaming media over MBMS.
*/
-public class MbmsStreamingManager {
- private static final String LOG_TAG = "MbmsStreamingManager";
+public class MbmsStreamingSession implements AutoCloseable {
+ private static final String LOG_TAG = "MbmsStreamingSession";
/**
* Service action which must be handled by the middleware implementing the MBMS streaming
@@ -65,98 +69,98 @@ public class MbmsStreamingManager {
@Override
public void binderDied() {
sIsInitialized.set(false);
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
}
};
- private InternalStreamingManagerCallback mInternalCallback;
+ private InternalStreamingSessionCallback mInternalCallback;
+ private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
private final Context mContext;
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
/** @hide */
- private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
+ private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
int subscriptionId, Handler handler) {
mContext = context;
mSubscriptionId = subscriptionId;
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
- mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
+ mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
}
/**
- * Create a new MbmsStreamingManager using the given subscription ID.
+ * Create a new {@link MbmsStreamingSession} using the given subscription ID.
*
* Note that this call will bind a remote service. You may not call this method on your app's
- * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
- * during the initialization or binding process.
+ * main thread.
*
- *
- * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
- * method while there is an active instance of {@link MbmsStreamingManager} in your process
- * (in other words, one that has not had {@link #dispose()} called on it), this method will
- * throw an {@link MbmsException}. If you call this method in a different process
+ * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
+ * method while there is an active instance of {@link MbmsStreamingSession} in your process
+ * (in other words, one that has not had {@link #close()} called on it), this method will
+ * throw an {@link IllegalStateException}. If you call this method in a different process
* running under the same UID, an error will be indicated via
- * {@link MbmsStreamingManagerCallback#onError(int, String)}.
+ * {@link MbmsStreamingSessionCallback#onError(int, String)}.
*
* Note that initialization may fail asynchronously. If you wish to try again after you
- * receive such an asynchronous error, you must call dispose() on the instance of
- * {@link MbmsStreamingManager} that you received before calling this method again.
+ * receive such an asynchronous error, you must call {@link #close()} on the instance of
+ * {@link MbmsStreamingSession} that you received before calling this method again.
*
* @param context The {@link Context} to use.
* @param callback A callback object on which you wish to receive results of asynchronous
* operations.
* @param subscriptionId The subscription ID to use.
- * @param handler The handler you wish to receive callbacks on. If null, callbacks will be
- * processed on the main looper (in other words, the looper returned from
- * {@link Looper#getMainLooper()}).
+ * @param handler The handler you wish to receive callbacks on.
+ * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
*/
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
- throws MbmsException {
+ public static @Nullable MbmsStreamingSession create(@NonNull Context context,
+ final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
+ @NonNull Handler handler) {
if (!sIsInitialized.compareAndSet(false, true)) {
- throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
+ throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
}
- MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
+ MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
subscriptionId, handler);
- try {
- manager.bindAndInitialize();
- } catch (MbmsException e) {
+
+ final int result = session.bindAndInitialize();
+ if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
- throw e;
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(result, null);
+ }
+ });
+ return null;
}
- return manager;
+ return session;
}
/**
- * Create a new MbmsStreamingManager using the system default data subscription ID.
- * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
+ * Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
+ * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
*/
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback, Handler handler)
- throws MbmsException {
+ public static MbmsStreamingSession create(@NonNull Context context,
+ @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
}
/**
- * Create a new MbmsStreamingManager using the system default data subscription ID and
- * default {@link Handler}.
- * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
- */
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback)
- throws MbmsException {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
- }
-
- /**
- * Terminates this instance, ending calls to the registered listener. Also terminates
- * any streaming services spawned from this instance.
+ * Terminates this instance. Also terminates
+ * any streaming services spawned from this instance as if
+ * {@link StreamingService#stopStreaming()} had been called on them. After this method returns,
+ * no further callbacks originating from the middleware will be enqueued on the provided
+ * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
+ * enqueued will still be delivered.
+ *
+ * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to
+ * obtain another instance of {@link MbmsStreamingSession} immediately after this method
+ * returns.
*
* May throw an {@link IllegalStateException}
*/
- public void dispose() {
+ public void close() {
try {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
@@ -164,47 +168,49 @@ public class MbmsStreamingManager {
return;
}
streamingService.dispose(mSubscriptionId);
+ for (StreamingService s : mKnownActiveStreamingServices) {
+ s.getCallback().stop();
+ }
+ mKnownActiveStreamingServices.clear();
} catch (RemoteException e) {
// Ignore for now
} finally {
mService.set(null);
sIsInitialized.set(false);
+ mInternalCallback.stop();
}
}
/**
* An inspection API to retrieve the list of streaming media currently be advertised.
- * The results are returned asynchronously through the previously registered callback.
- * serviceClasses lets the app filter on types of programming and is opaque data between
- * the app and the carrier.
+ * The results are returned asynchronously via
+ * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
+ * provided upon creation.
*
- * Multiple calls replace the list of serviceClasses of interest.
+ * Multiple calls replace the list of service classes of interest.
*
- * This may throw an {@link MbmsException} containing any error in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors},
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
*
- * May also throw an unchecked {@link IllegalArgumentException} or an
- * {@link IllegalStateException}
- *
- * @param classList A list of streaming service classes that the app would like updates on.
+ * @param serviceClassList A list of streaming service classes that the app would like updates
+ * on. The exact names of these classes should be negotiated with the
+ * wireless carrier separately.
*/
- public void getStreamingServices(List<String> classList) throws MbmsException {
+ public void requestUpdateStreamingServices(List<String> serviceClassList) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+ throw new IllegalStateException("Middleware not yet bound");
}
try {
- int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
+ int returnCode = streamingService.requestUpdateStreamingServices(
+ mSubscriptionId, serviceClassList);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -215,56 +221,57 @@ public class MbmsStreamingManager {
* reported via
* {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
*
- * May throw an
- * {@link MbmsException} containing any of the error codes in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors},
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
- *
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* Asynchronous errors through the callback include any of the errors in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
- * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
+ * {@link MbmsErrors.GeneralErrors} or
+ * {@link MbmsErrors.StreamingErrors}.
*
* @param serviceInfo The information about the service to stream.
* @param callback A callback that'll be called when something about the stream changes.
- * @param handler A handler that calls to {@code callback} should be called on. If null,
- * defaults to the handler provided via
- * {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
+ * @param handler A handler that calls to {@code callback} should be called on.
* @return An instance of {@link StreamingService} through which the stream can be controlled.
+ * May be {@code null} if an error occurred.
*/
- public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
- StreamingServiceCallback callback, Handler handler) throws MbmsException {
+ public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+ StreamingServiceCallback callback, @NonNull Handler handler) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
+ throw new IllegalStateException("Middleware not yet bound");
}
InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
- callback, handler == null ? mInternalCallback.getHandler() : handler);
+ callback, handler);
StreamingService serviceForApp = new StreamingService(
- mSubscriptionId, streamingService, serviceInfo, serviceCallback);
+ mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
+ mKnownActiveStreamingServices.add(serviceForApp);
try {
int returnCode = streamingService.startStreaming(
mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
+ return null;
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return null;
}
return serviceForApp;
}
- private void bindAndInitialize() throws MbmsException {
- MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
+ /** @hide */
+ public void onStreamingServiceStopped(StreamingService service) {
+ mKnownActiveStreamingServices.remove(service);
+ }
+
+ private int bindAndInitialize() {
+ return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -277,19 +284,19 @@ public class MbmsStreamingManager {
} catch (RemoteException e) {
Log.e(LOG_TAG, "Service died before initialization");
sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
e.toString());
sIsInitialized.set(false);
return;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "Runtime exception during initialization");
sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
e.toString());
sIsInitialized.set(false);
return;
}
- if (result != MbmsException.SUCCESS) {
+ if (result != MbmsErrors.SUCCESS) {
sendErrorToApp(result, "Error returned during initialization");
sIsInitialized.set(false);
return;
@@ -297,7 +304,7 @@ public class MbmsStreamingManager {
try {
streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
"Middleware lost during initialization");
sIsInitialized.set(false);
return;
@@ -315,7 +322,7 @@ public class MbmsStreamingManager {
private void sendErrorToApp(int errorCode, String message) {
try {
- mInternalCallback.error(errorCode, message);
+ mInternalCallback.onError(errorCode, message);
} catch (RemoteException e) {
// Ignore, should not happen locally.
}
diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java
index eae9011e..5a57f322 100644
--- a/android/telephony/mbms/DownloadRequest.java
+++ b/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
package android.telephony.mbms;
+import android.annotation.SystemApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
@@ -37,34 +38,27 @@ import java.security.NoSuchAlgorithmException;
import java.util.Objects;
/**
- * A Parcelable class describing a pending Cell-Broadcast download request
- * @hide
+ * Describes a request to download files over cell-broadcast. Instances of this class should be
+ * created by the app when requesting a download, and instances of this class will be passed back
+ * to the app when the middleware updates the status of the download.
*/
-public class DownloadRequest implements Parcelable {
+public final class DownloadRequest implements Parcelable {
// Version code used to keep token calculation consistent.
private static final int CURRENT_VERSION = 1;
private static final String LOG_TAG = "MbmsDownloadRequest";
- /**
- * Maximum permissible length for the app's download-completion intent, when serialized via
- * {@link Intent#toUri(int)}.
- */
+ /** @hide */
public static final int MAX_APP_INTENT_SIZE = 50000;
- /**
- * Maximum permissible length for the app's destination path, when serialized via
- * {@link Uri#toString()}.
- */
+ /** @hide */
public static final int MAX_DESTINATION_URI_SIZE = 50000;
/** @hide */
private static class OpaqueDataContainer implements Serializable {
- private final String destinationUri;
private final String appIntent;
private final int version;
- public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
- this.destinationUri = destinationUri;
+ public OpaqueDataContainer(String appIntent, int version) {
this.appIntent = appIntent;
this.version = version;
}
@@ -73,7 +67,6 @@ public class DownloadRequest implements Parcelable {
public static class Builder {
private String fileServiceId;
private Uri source;
- private Uri dest;
private int subscriptionId;
private String appIntent;
private int version = CURRENT_VERSION;
@@ -91,8 +84,8 @@ public class DownloadRequest implements Parcelable {
/**
* Set the service ID for the download request. For use by the middleware only.
* @hide
- * TODO: systemapi
*/
+ @SystemApi
public Builder setServiceId(String serviceId) {
fileServiceId = serviceId;
return this;
@@ -101,7 +94,6 @@ public class DownloadRequest implements Parcelable {
/**
* Sets the source URI for the download request to be built.
* @param source
- * @return
*/
public Builder setSource(Uri source) {
this.source = source;
@@ -109,25 +101,8 @@ public class DownloadRequest implements Parcelable {
}
/**
- * Sets the destination URI for the download request to be built. The middleware should
- * not set this directly.
- * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
- * final destination of the download.
- * @return
- */
- public Builder setDest(Uri dest) {
- if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
- throw new IllegalArgumentException("Destination uri must not exceed length " +
- MAX_DESTINATION_URI_SIZE);
- }
- this.dest = dest;
- return this;
- }
-
- /**
* Set the subscription ID on which the file(s) should be downloaded.
* @param subscriptionId
- * @return
*/
public Builder setSubscriptionId(int subscriptionId) {
this.subscriptionId = subscriptionId;
@@ -141,7 +116,6 @@ public class DownloadRequest implements Parcelable {
*
* The middleware should not use this method.
* @param intent
- * @return
*/
public Builder setAppIntent(Intent intent) {
this.appIntent = intent.toUri(0);
@@ -158,17 +132,15 @@ public class DownloadRequest implements Parcelable {
* manager code, but is irrelevant to the middleware.
* @param data A byte array, the contents of which should have been originally obtained
* from {@link DownloadRequest#getOpaqueData()}.
- * @return
- * TODO: systemapi
* @hide
*/
+ @SystemApi
public Builder setOpaqueData(byte[] data) {
try {
ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
version = dataContainer.version;
appIntent = dataContainer.appIntent;
- dest = Uri.parse(dataContainer.destinationUri);
} catch (IOException e) {
// Really should never happen
Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
@@ -181,24 +153,21 @@ public class DownloadRequest implements Parcelable {
}
public DownloadRequest build() {
- return new DownloadRequest(fileServiceId, source, dest,
- subscriptionId, appIntent, version);
+ return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);
}
}
private final String fileServiceId;
private final Uri sourceUri;
- private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
private final int version;
private DownloadRequest(String fileServiceId,
- Uri source, Uri dest,
- int sub, String appIntent, int version) {
+ Uri source, int sub,
+ String appIntent, int version) {
this.fileServiceId = fileServiceId;
sourceUri = source;
- destinationUri = dest;
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
this.version = version;
@@ -211,7 +180,6 @@ public class DownloadRequest implements Parcelable {
private DownloadRequest(DownloadRequest dr) {
fileServiceId = dr.fileServiceId;
sourceUri = dr.sourceUri;
- destinationUri = dr.destinationUri;
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
version = dr.version;
@@ -220,7 +188,6 @@ public class DownloadRequest implements Parcelable {
private DownloadRequest(Parcel in) {
fileServiceId = in.readString();
sourceUri = in.readParcelable(getClass().getClassLoader());
- destinationUri = in.readParcelable(getClass().getClassLoader());
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
version = in.readInt();
@@ -233,7 +200,6 @@ public class DownloadRequest implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(fileServiceId);
out.writeParcelable(sourceUri, flags);
- out.writeParcelable(destinationUri, flags);
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeInt(version);
@@ -254,14 +220,6 @@ public class DownloadRequest implements Parcelable {
}
/**
- * For use by the client app only.
- * @return The URI of the final destination of the download.
- */
- public Uri getDestinationUri() {
- return destinationUri;
- }
-
- /**
* @return The subscription ID on which to perform MBMS operations.
*/
public int getSubscriptionId() {
@@ -287,14 +245,14 @@ public class DownloadRequest implements Parcelable {
* {@link Builder#setOpaqueData(byte[])}.
* @return A byte array of opaque data to persist.
* @hide
- * TODO: systemapi
*/
+ @SystemApi
public byte[] getOpaqueData() {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
OpaqueDataContainer container = new OpaqueDataContainer(
- destinationUri.toString(), serializedResultIntentForApp, version);
+ serializedResultIntentForApp, version);
stream.writeObject(container);
stream.flush();
return byteArrayOutputStream.toByteArray();
@@ -321,6 +279,22 @@ public class DownloadRequest implements Parcelable {
};
/**
+ * Maximum permissible length for the app's destination path, when serialized via
+ * {@link Uri#toString()}.
+ */
+ public static int getMaxAppIntentSize() {
+ return MAX_APP_INTENT_SIZE;
+ }
+
+ /**
+ * Maximum permissible length for the app's download-completion intent, when serialized via
+ * {@link Intent#toUri(int)}.
+ */
+ public static int getMaxDestinationUriSize() {
+ return MAX_DESTINATION_URI_SIZE;
+ }
+
+ /**
* @hide
*/
public boolean isMultipartDownload() {
@@ -344,7 +318,6 @@ public class DownloadRequest implements Parcelable {
if (version >= 1) {
// Hash the source URI, destination URI, and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
- digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
}
// Add updates for future versions here
@@ -365,13 +338,12 @@ public class DownloadRequest implements Parcelable {
version == request.version &&
Objects.equals(fileServiceId, request.fileServiceId) &&
Objects.equals(sourceUri, request.sourceUri) &&
- Objects.equals(destinationUri, request.destinationUri) &&
Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
}
@Override
public int hashCode() {
- return Objects.hash(fileServiceId, sourceUri, destinationUri,
+ return Objects.hash(fileServiceId, sourceUri,
subscriptionId, serializedResultIntentForApp, version);
}
}
diff --git a/android/telephony/mbms/DownloadProgressListener.java b/android/telephony/mbms/DownloadStateCallback.java
index d91e9ad2..86920bd3 100644
--- a/android/telephony/mbms/DownloadProgressListener.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -16,18 +16,20 @@
package android.telephony.mbms;
-import android.os.RemoteException;
+import android.telephony.MbmsDownloadSession;
/**
- * A optional listener class used by download clients to track progress.
- * @hide
+ * A optional listener class used by download clients to track progress. Apps should extend this
+ * class and pass an instance into
+ * {@link MbmsDownloadSession#download(DownloadRequest)}
+ *
+ * This is optionally specified when requesting a download and will only be called while the app
+ * is running.
*/
-public class DownloadProgressListener extends IDownloadProgressListener.Stub {
+public class DownloadStateCallback {
+
/**
- * Gives process callbacks for a given DownloadRequest.
- * This is optionally specified when requesting a download and
- * only lives while the app is running - it's unlikely to be useful for
- * downloads far in the future.
+ * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
*
* @param request a {@link DownloadRequest}, indicating which download is being referenced.
* @param fileInfo a {@link FileInfo} specifying the file to report progress on. Note that
@@ -40,9 +42,21 @@ public class DownloadProgressListener extends IDownloadProgressListener.Stub {
* @param currentDecodedSize is the number of bytes that have been decoded.
* @param fullDecodedSize is the total number of bytes that make up the final decoded content.
*/
- @Override
- public void progress(DownloadRequest request, FileInfo fileInfo,
+ public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
int currentDownloadSize, int fullDownloadSize,
- int currentDecodedSize, int fullDecodedSize) throws RemoteException {
+ int currentDecodedSize, int fullDecodedSize) {
+ }
+
+ /**
+ * Gives download state callbacks for a file in a {@link DownloadRequest}.
+ *
+ * @param request a {@link DownloadRequest}, indicating which download is being referenced.
+ * @param fileInfo a {@link FileInfo} specifying the file to report progress on. Note that
+ * the request may result in many files being downloaded and the client
+ * may not have been able to get a list of them in advance.
+ * @param state The current state of the download.
+ */
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
}
}
diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java
index f97131dd..0d737b58 100644
--- a/android/telephony/mbms/FileInfo.java
+++ b/android/telephony/mbms/FileInfo.java
@@ -16,26 +16,18 @@
package android.telephony.mbms;
+import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
/**
- * A Parcelable class Cell-Broadcast downloadable file information.
- * @hide
+ * Describes a single file that is available over MBMS.
*/
-public class FileInfo implements Parcelable {
+public final class FileInfo implements Parcelable {
- /**
- * The URI into the carriers infrastructure which points to this file.
- * This is used internally but is also one of the few pieces of data about the content that is
- * exposed and may be needed for disambiguation by the application.
- */
private final Uri uri;
- /**
- * The mime type of the content.
- */
private final String mimeType;
public static final Parcelable.Creator<FileInfo> CREATOR =
@@ -53,8 +45,8 @@ public class FileInfo implements Parcelable {
/**
* @hide
- * TODO: systemapi
*/
+ @SystemApi
public FileInfo(Uri uri, String mimeType) {
this.uri = uri;
this.mimeType = mimeType;
@@ -76,10 +68,17 @@ public class FileInfo implements Parcelable {
return 0;
}
+ /**
+ * @return The URI in the carrier's infrastructure which points to this file. Apps should
+ * negotiate the contents of this URI separately with the carrier.
+ */
public Uri getUri() {
return uri;
}
+ /**
+ * @return The MIME type of the file.
+ */
public String getMimeType() {
return mimeType;
}
diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java
index 8afe4d3c..d8d7f48a 100644
--- a/android/telephony/mbms/FileServiceInfo.java
+++ b/android/telephony/mbms/FileServiceInfo.java
@@ -16,6 +16,7 @@
package android.telephony.mbms;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,13 +27,14 @@ import java.util.Locale;
import java.util.Map;
/**
- * A Parcelable class Cell-Broadcast downloadable file information.
- * @hide
+ * Describes a file service available from the carrier from which files can be downloaded via
+ * cell-broadcast.
*/
-public class FileServiceInfo extends ServiceInfo implements Parcelable {
+public final class FileServiceInfo extends ServiceInfo implements Parcelable {
private final List<FileInfo> files;
- /** @hide TODO: systemapi */
+ /** @hide */
+ @SystemApi
public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
List<Locale> newLocales, String newServiceId, Date start, Date end,
List<FileInfo> newFiles) {
@@ -56,7 +58,7 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable {
FileServiceInfo(Parcel in) {
super(in);
files = new ArrayList<FileInfo>();
- in.readList(files, null);
+ in.readList(files, FileInfo.class.getClassLoader());
}
@Override
@@ -70,8 +72,12 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable {
return 0;
}
+ /**
+ * @return A list of files available from this service. Note that this list may not be
+ * exhaustive -- the middleware may not have information on all files that are available.
+ * Consult the carrier for an authoritative and exhaustive list.
+ */
public List<FileInfo> getFiles() {
return files;
}
-
}
diff --git a/android/telephony/mbms/InternalDownloadSessionCallback.java b/android/telephony/mbms/InternalDownloadSessionCallback.java
new file mode 100644
index 00000000..a7a5958f
--- /dev/null
+++ b/android/telephony/mbms/InternalDownloadSessionCallback.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+import android.os.Handler;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/** @hide */
+public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {
+
+ private final Handler mHandler;
+ private final MbmsDownloadSessionCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
+
+ public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,
+ Handler handler) {
+ mAppCallback = appCallback;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAppCallback.onError(errorCode, message);
+ }
+ });
+ }
+
+ @Override
+ public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAppCallback.onFileServicesUpdated(services);
+ }
+ });
+ }
+
+ @Override
+ public void onMiddlewareReady() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAppCallback.onMiddlewareReady();
+ }
+ });
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ public void stop() {
+ mIsStopped = true;
+ }
+}
diff --git a/android/telephony/mbms/InternalDownloadStateCallback.java b/android/telephony/mbms/InternalDownloadStateCallback.java
new file mode 100644
index 00000000..8702952c
--- /dev/null
+++ b/android/telephony/mbms/InternalDownloadStateCallback.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+import android.os.Handler;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ */
+public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
+ private final Handler mHandler;
+ private final DownloadStateCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
+
+ public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {
+ mAppCallback = appCallback;
+ mHandler = handler;
+ }
+
+ @Override
+ public void onProgressUpdated(final DownloadRequest request, final FileInfo fileInfo,
+ final int currentDownloadSize, final int fullDownloadSize, final int currentDecodedSize,
+ final int fullDecodedSize) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize, currentDecodedSize, fullDecodedSize);
+ }
+ });
+ }
+
+ @Override
+ public void onStateUpdated(final DownloadRequest request, final FileInfo fileInfo,
+ final int state) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAppCallback.onStateUpdated(request, fileInfo, state);
+ }
+ });
+ }
+
+ public void stop() {
+ mIsStopped = true;
+ }
+}
diff --git a/android/telephony/mbms/InternalStreamingServiceCallback.java b/android/telephony/mbms/InternalStreamingServiceCallback.java
index bb337b27..eb6579ce 100644
--- a/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -23,6 +23,7 @@ import android.os.RemoteException;
public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
private final StreamingServiceCallback mAppCallback;
private final Handler mHandler;
+ private volatile boolean mIsStopped = false;
public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
mAppCallback = appCallback;
@@ -30,7 +31,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
- public void error(int errorCode, String message) throws RemoteException {
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -40,7 +45,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
- public void streamStateUpdated(int state, int reason) throws RemoteException {
+ public void onStreamStateUpdated(final int state, final int reason) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -50,7 +59,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
- public void mediaDescriptionUpdated() throws RemoteException {
+ public void onMediaDescriptionUpdated() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -60,7 +73,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
- public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+ public void onBroadcastSignalStrengthUpdated(final int signalStrength) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -70,7 +87,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
- public void streamMethodUpdated(int methodType) throws RemoteException {
+ public void onStreamMethodUpdated(final int methodType) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -78,4 +99,8 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
});
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/android/telephony/mbms/InternalStreamingManagerCallback.java b/android/telephony/mbms/InternalStreamingSessionCallback.java
index b52df8c0..d782d12c 100644
--- a/android/telephony/mbms/InternalStreamingManagerCallback.java
+++ b/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -18,25 +18,27 @@ package android.telephony.mbms;
import android.os.Handler;
import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.StreamingServiceInfo;
import java.util.List;
/** @hide */
-public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
private final Handler mHandler;
- private final MbmsStreamingManagerCallback mAppCallback;
+ private final MbmsStreamingSessionCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
- public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+ public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
Handler handler) {
mAppCallback = appCallback;
mHandler = handler;
}
@Override
- public void error(int errorCode, String message) throws RemoteException {
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -46,8 +48,12 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
}
@Override
- public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+ public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services)
throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -57,7 +63,11 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
}
@Override
- public void middlewareReady() throws RemoteException {
+ public void onMiddlewareReady() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -69,4 +79,8 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
public Handler getHandler() {
return mHandler;
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/android/telephony/mbms/MbmsDownloadManagerCallback.java b/android/telephony/mbms/MbmsDownloadManagerCallback.java
deleted file mode 100644
index 17291d09..00000000
--- a/android/telephony/mbms/MbmsDownloadManagerCallback.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2016 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.telephony.mbms;
-
-import android.os.RemoteException;
-import android.telephony.MbmsDownloadManager;
-
-import java.util.List;
-
-/**
- * A Parcelable class with Cell-Broadcast service information.
- * @hide
- */
-public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
-
- @Override
- public void error(int errorCode, String message) throws RemoteException {
- // default implementation empty
- }
-
- /**
- * Called to indicate published File Services have changed.
- *
- * This will only be called after the application has requested
- * a list of file services and specified a service class list
- * of interest AND the results of a subsequent getFileServices
- * call with the same service class list would return different
- * results.
- *
- * @param services a List of FileServiceInfos
- *
- */
- @Override
- public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
- // default implementation empty
- }
-
- /**
- * Called to indicate that the middleware has been initialized and is ready.
- *
- * Before this method is called, calling any method on an instance of
- * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException}
- * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
- */
- @Override
- public void middlewareReady() throws RemoteException {
- // default implementation empty
- }
-}
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index ba7d120a..61415b50 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -16,6 +16,7 @@
package android.telephony.mbms;
+import android.annotation.SystemApi;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -24,8 +25,8 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
-import android.telephony.MbmsDownloadManager;
-import android.telephony.mbms.vendor.VendorIntents;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.mbms.vendor.VendorUtils;
import android.util.Log;
import java.io.File;
@@ -35,52 +36,74 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
- * @hide
+ * The {@link BroadcastReceiver} responsible for handling intents sent from the middleware. Apps
+ * that wish to download using MBMS APIs should declare this class in their AndroidManifest.xml as
+ * follows:
+<pre>{@code
+<receiver
+ android:name="android.telephony.mbms.MbmsDownloadReceiver"
+ android:permission="android.permission.SEND_EMBMS_INTENTS"
+ android:enabled="true"
+ android:exported="true">
+</receiver>}</pre>
*/
public class MbmsDownloadReceiver extends BroadcastReceiver {
+ /** @hide */
public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
+ /** @hide */
public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
/**
- * TODO: @SystemApi all these result codes
* Indicates that the requested operation completed without error.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_OK = 0;
/**
* Indicates that the intent sent had an invalid action. This will be the result if
* {@link Intent#getAction()} returns anything other than
- * {@link VendorIntents#ACTION_DOWNLOAD_RESULT_INTERNAL},
- * {@link VendorIntents#ACTION_FILE_DESCRIPTOR_REQUEST}, or
- * {@link VendorIntents#ACTION_CLEANUP}.
+ * {@link VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL},
+ * {@link VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST}, or
+ * {@link VendorUtils#ACTION_CLEANUP}.
* This is a fatal result code and no result extras should be expected.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_INVALID_ACTION = 1;
/**
* Indicates that the intent was missing some required extras.
* This is a fatal result code and no result extras should be expected.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_MALFORMED_INTENT = 2;
/**
- * Indicates that the supplied value for {@link VendorIntents#EXTRA_TEMP_FILE_ROOT}
+ * Indicates that the supplied value for {@link VendorUtils#EXTRA_TEMP_FILE_ROOT}
* does not match what the app has stored.
* This is a fatal result code and no result extras should be expected.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_BAD_TEMP_FILE_ROOT = 3;
/**
* Indicates that the manager was unable to move the completed download to its final location.
* This is a fatal result code and no result extras should be expected.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
/**
@@ -88,35 +111,48 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
* descriptors.
* This is a non-fatal result code -- some file descriptors may still be generated, but there
* is no guarantee that they will be the same number as requested.
+ * @hide
*/
+ @SystemApi
public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
+ /**
+ * Indicates that the manager was unable to notify the app of the completed download.
+ * This is a fatal result code and no result extras should be expected.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_APP_NOTIFICATION_ERROR = 6;
+
+
private static final String LOG_TAG = "MbmsDownloadReceiver";
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
- private static final int MAX_TEMP_FILE_RETRIES = 5;
+ private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files";
+ private static final int MAX_TEMP_FILE_RETRIES = 5;
private String mFileProviderAuthorityCache = null;
private String mMiddlewarePackageNameCache = null;
+ /** @hide */
@Override
public void onReceive(Context context, Intent intent) {
if (!verifyIntentContents(context, intent)) {
setResultCode(RESULT_MALFORMED_INTENT);
return;
}
- if (!Objects.equals(intent.getStringExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT),
+ if (!Objects.equals(intent.getStringExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT),
MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
return;
}
- if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+ if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
moveDownloadedFile(context, intent);
cleanupPostMove(context, intent);
- } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+ } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
generateTempFiles(context, intent);
- } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
+ } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
cleanupTempFiles(context, intent);
} else {
setResultCode(RESULT_INVALID_ACTION);
@@ -124,30 +160,31 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private boolean verifyIntentContents(Context context, Intent intent) {
- if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
- if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
+ if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_REQUEST)) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
- if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FILE_INFO)) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {
Log.w(LOG_TAG, "Download result did not include the associated file info. " +
"Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_FINAL_URI)) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_FINAL_URI)) {
Log.w(LOG_TAG, "Download result did not include the path to the final " +
"temp file. Ignoring.");
return false;
}
- DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
File expectedTokenFile = new File(
MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
@@ -157,27 +194,27 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
"Expected " + expectedTokenFile);
return false;
}
- } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
- if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
- Log.w(LOG_TAG, "Temp file request did not include the associated service info." +
+ } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
+ Log.w(LOG_TAG, "Temp file request did not include the associated service id." +
" Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
- } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
- if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
- Log.w(LOG_TAG, "Cleanup request did not include the associated service info." +
+ } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
+ Log.w(LOG_TAG, "Cleanup request did not include the associated service id." +
" Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE)) {
+ if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE)) {
Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
"Ignoring.");
return false;
@@ -187,21 +224,26 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private void moveDownloadedFile(Context context, Intent intent) {
- DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
Intent intentForApp = request.getIntentForApp();
+ if (intentForApp == null) {
+ Log.i(LOG_TAG, "Malformed app notification intent");
+ setResultCode(RESULT_APP_NOTIFICATION_ERROR);
+ return;
+ }
- int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
- MbmsDownloadManager.RESULT_CANCELLED);
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
+ int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+ MbmsDownloadSession.RESULT_CANCELLED);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
- if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
+ if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
context.sendBroadcast(intentForApp);
return;
}
- Uri destinationUri = request.getDestinationUri();
- Uri finalTempFile = intent.getParcelableExtra(VendorIntents.EXTRA_FINAL_URI);
+ Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);
if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
@@ -209,30 +251,37 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
FileInfo completedFileInfo =
- (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO);
- String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
+ (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
+ Path stagingDirectory = FileSystems.getDefault().getPath(
+ MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(),
+ TEMP_FILE_STAGING_LOCATION);
- Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
- if (finalFileLocation == null) {
+ Uri stagedFileLocation;
+ try {
+ stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory);
+ } catch (IOException e) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
+ stagedFileLocation);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
}
private void cleanupPostMove(Context context, Intent intent) {
- DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
if (request == null) {
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
return;
}
- List<Uri> tempFiles = intent.getParcelableExtra(VendorIntents.EXTRA_TEMP_LIST);
+ List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
if (tempFiles == null) {
return;
}
@@ -246,16 +295,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private void generateTempFiles(Context context, Intent intent) {
- FileServiceInfo serviceInfo =
- intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
- if (serviceInfo == null) {
- Log.w(LOG_TAG, "Temp file request did not include the associated service info. " +
+ String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
+ if (serviceId == null) {
+ Log.w(LOG_TAG, "Temp file request did not include the associated service id. " +
"Ignoring.");
setResultCode(RESULT_MALFORMED_INTENT);
return;
}
- int fdCount = intent.getIntExtra(VendorIntents.EXTRA_FD_COUNT, 0);
- List<Uri> pausedList = intent.getParcelableExtra(VendorIntents.EXTRA_PAUSED_LIST);
+ int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
+ List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -265,22 +313,20 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
ArrayList<UriPathPair> freshTempFiles =
- generateFreshTempFiles(context, serviceInfo, fdCount);
+ generateFreshTempFiles(context, serviceId, fdCount);
ArrayList<UriPathPair> pausedFiles =
- generateUrisForPausedFiles(context, serviceInfo, pausedList);
+ generateUrisForPausedFiles(context, serviceId, pausedList);
Bundle result = new Bundle();
- result.putParcelableArrayList(VendorIntents.EXTRA_FREE_URI_LIST, freshTempFiles);
- result.putParcelableArrayList(VendorIntents.EXTRA_PAUSED_URI_LIST, pausedFiles);
+ result.putParcelableArrayList(VendorUtils.EXTRA_FREE_URI_LIST, freshTempFiles);
+ result.putParcelableArrayList(VendorUtils.EXTRA_PAUSED_URI_LIST, pausedFiles);
setResultCode(RESULT_OK);
setResultExtras(result);
}
- private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
- FileServiceInfo serviceInfo,
+ private ArrayList<UriPathPair> generateFreshTempFiles(Context context, String serviceId,
int freshFdCount) {
- File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
- serviceInfo.getServiceId());
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
if (!tempFileDir.exists()) {
tempFileDir.mkdirs();
}
@@ -324,14 +370,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
- FileServiceInfo serviceInfo, List<Uri> pausedFiles) {
+ String serviceId, List<Uri> pausedFiles) {
if (pausedFiles == null) {
return new ArrayList<>(0);
}
ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
for (Uri fileUri : pausedFiles) {
- if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) {
+ if (!verifyTempFilePath(context, serviceId, fileUri)) {
Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
continue;
@@ -353,12 +399,10 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private void cleanupTempFiles(Context context, Intent intent) {
- FileServiceInfo serviceInfo =
- intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
- File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
- serviceInfo.getServiceId());
+ String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
final List<Uri> filesInUse =
- intent.getParcelableArrayListExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE);
+ intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE);
File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
@@ -385,63 +429,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
}
- private static String calculateDestinationFileRelativePath(DownloadRequest request,
- FileInfo info) {
- List<String> filePathComponents = info.getUri().getPathSegments();
- List<String> requestPathComponents = request.getSourceUri().getPathSegments();
- Iterator<String> filePathIter = filePathComponents.iterator();
- Iterator<String> requestPathIter = requestPathComponents.iterator();
-
- StringBuilder pathBuilder = new StringBuilder();
- // Iterate through the segments of the carrier's URI to the file, along with the segments
- // of the source URI specified in the download request. The relative path is calculated
- // as the tail of the file's URI that does not match the path segments in the source URI.
- while (filePathIter.hasNext()) {
- String currFilePathComponent = filePathIter.next();
- if (requestPathIter.hasNext()) {
- String requestFilePathComponent = requestPathIter.next();
- if (requestFilePathComponent.equals(currFilePathComponent)) {
- continue;
- }
- }
- pathBuilder.append(currFilePathComponent);
- pathBuilder.append('/');
- }
- // remove the trailing slash
- if (pathBuilder.length() > 0) {
- pathBuilder.deleteCharAt(pathBuilder.length() - 1);
- }
- return pathBuilder.toString();
- }
-
/*
- * Moves a tempfile located at fromPath to a new location at toPath. If
- * toPath is a directory, the destination file will be located at relativePath
- * underneath toPath.
+ * Moves a tempfile located at fromPath to a new location in the staging directory.
*/
- private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
+ private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
return null;
}
- if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
- Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
- return null;
- }
- File fromFile = new File(fromPath.getSchemeSpecificPart());
- File toFile = new File(toPath.getSchemeSpecificPart());
- if (toFile.isDirectory()) {
- toFile = new File(toFile, relativePath);
+ Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
+ if (!Files.isDirectory(stagingDirectory)) {
+ Files.createDirectory(stagingDirectory);
}
- toFile.getParentFile().mkdirs();
+ Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName()));
- if (fromFile.renameTo(toFile)) {
- return Uri.fromFile(toFile);
- } else if (manualMove(fromFile, toFile)) {
- return Uri.fromFile(toFile);
- }
- return null;
+ return Uri.fromFile(result.toFile());
}
private static boolean verifyTempFilePath(Context context, String serviceId,
@@ -493,7 +496,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
private String getMiddlewarePackageCached(Context context) {
if (mMiddlewarePackageNameCache == null) {
mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
- MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
+ MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
}
return mMiddlewarePackageNameCache;
}
diff --git a/android/telephony/mbms/MbmsDownloadSessionCallback.java b/android/telephony/mbms/MbmsDownloadSessionCallback.java
new file mode 100644
index 00000000..77dea6f3
--- /dev/null
+++ b/android/telephony/mbms/MbmsDownloadSessionCallback.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.telephony.mbms;
+
+import android.telephony.MbmsDownloadSession;
+
+import java.util.List;
+
+/**
+ * A callback class that apps should use to receive information on file downloads over
+ * cell-broadcast.
+ */
+public class MbmsDownloadSessionCallback {
+
+ /**
+ * Indicates that the middleware has encountered an asynchronous error.
+ * @param errorCode Any error code listed in {@link MbmsErrors}
+ * @param message A message, intended for debugging purposes, describing the error in further
+ * detail.
+ */
+ public void onError(int errorCode, String message) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate published File Services have changed.
+ *
+ * This will only be called after the application has requested a list of file services and
+ * specified a service class list of interest via
+ * {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
+ * {@link MbmsDownloadSession#requestUpdateFileServices(List)},
+ * this method may not be called again if
+ * the list of service classes would remain the same.
+ *
+ * @param services The most recently updated list of available file services.
+ */
+ public void onFileServicesUpdated(List<FileServiceInfo> services) {
+ // default implementation empty
+ }
+
+ /**
+ * Called to indicate that the middleware has been initialized and is ready.
+ *
+ * Before this method is called, calling any method on an instance of
+ * {@link MbmsDownloadSession} will result in an {@link IllegalStateException}
+ * being thrown or {@link #onError(int, String)} being called with error code
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ */
+ public void onMiddlewareReady() {
+ // default implementation empty
+ }
+}
diff --git a/android/telephony/mbms/MbmsException.java b/android/telephony/mbms/MbmsErrors.java
index 6de5a18e..af0af24c 100644
--- a/android/telephony/mbms/MbmsException.java
+++ b/android/telephony/mbms/MbmsErrors.java
@@ -16,7 +16,9 @@
package android.telephony.mbms;
-public class MbmsException extends Exception {
+import android.telephony.MbmsStreamingSession;
+
+public class MbmsErrors {
/** Indicates that the operation was successful. */
public static final int SUCCESS = 0;
@@ -30,8 +32,8 @@ public class MbmsException extends Exception {
/**
* Indicates that the app attempted to perform an operation on an instance of
- * TODO: link android.telephony.MbmsDownloadManager or
- * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
+ * {@link android.telephony.MbmsDownloadSession} or
+ * {@link MbmsStreamingSession} without being bound to the middleware.
*/
public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
@@ -46,8 +48,7 @@ public class MbmsException extends Exception {
private InitializationErrors() {}
/**
* Indicates that the app tried to create more than one instance each of
- * {@link android.telephony.MbmsStreamingManager} or
- * TODO: link android.telephony.MbmsDownloadManager
+ * {@link MbmsStreamingSession} or {@link android.telephony.MbmsDownloadSession}.
*/
public static final int ERROR_DUPLICATE_INITIALIZE = 101;
/** Indicates that the app is not authorized to access media via MBMS.*/
@@ -64,8 +65,8 @@ public class MbmsException extends Exception {
private GeneralErrors() {}
/**
* Indicates that the app attempted to perform an operation before receiving notification
- * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
- * or TODO: link MbmsDownloadManagerCallback#middlewareReady
+ * that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()}
+ * or {@link MbmsDownloadSessionCallback#onMiddlewareReady()}.
*/
public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
/**
@@ -107,7 +108,7 @@ public class MbmsException extends Exception {
/**
* Indicates that the app called
- * {@link android.telephony.MbmsStreamingManager#startStreaming(
+ * {@link MbmsStreamingSession#startStreaming(
* StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
* more than once for the same {@link StreamingServiceInfo}.
*/
@@ -116,10 +117,9 @@ public class MbmsException extends Exception {
/**
* Indicates the errors that are applicable only to the file-download use-case
- * TODO: unhide
- * @hide
*/
public static class DownloadErrors {
+ private DownloadErrors() { }
/**
* Indicates that the app is not allowed to change the temp file root at this time due to
* outstanding download requests.
@@ -130,15 +130,5 @@ public class MbmsException extends Exception {
public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;
}
- private final int mErrorCode;
-
- /** @hide */
- public MbmsException(int errorCode) {
- super();
- mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
+ private MbmsErrors() {}
}
diff --git a/android/telephony/mbms/MbmsStreamingManagerCallback.java b/android/telephony/mbms/MbmsStreamingSessionCallback.java
index b31ffa72..5c130a09 100644
--- a/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/android/telephony/mbms/MbmsStreamingSessionCallback.java
@@ -16,25 +16,26 @@
package android.telephony.mbms;
+import android.annotation.Nullable;
import android.content.Context;
-import android.os.RemoteException;
-import android.telephony.MbmsStreamingManager;
+import android.os.Handler;
+import android.telephony.MbmsStreamingSession;
import java.util.List;
/**
* A callback class that is used to receive information from the middleware on MBMS streaming
* services. An instance of this object should be passed into
- * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
+ * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
*/
-public class MbmsStreamingManagerCallback {
+public class MbmsStreamingSessionCallback {
/**
* Called by the middleware when it has detected an error condition. The possible error codes
- * are listed in {@link MbmsException}.
+ * are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
- public void onError(int errorCode, String message) {
+ public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}
@@ -47,8 +48,7 @@ public class MbmsStreamingManagerCallback {
* call with the same service class list would return different
* results.
*
- * @param services a List of StreamingServiceInfos
- *
+ * @param services The list of available services.
*/
public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
// default implementation empty
@@ -58,9 +58,9 @@ public class MbmsStreamingManagerCallback {
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
- * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException}
- * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ * {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
+ * delivered via {@link #onError(int, String)} with error code
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
*/
public void onMiddlewareReady() {
// default implementation empty
diff --git a/android/telephony/mbms/MbmsTempFileProvider.java b/android/telephony/mbms/MbmsTempFileProvider.java
index c4d033bf..689becd7 100644
--- a/android/telephony/mbms/MbmsTempFileProvider.java
+++ b/android/telephony/mbms/MbmsTempFileProvider.java
@@ -23,12 +23,11 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.telephony.MbmsDownloadSession;
import java.io.File;
import java.io.FileNotFoundException;
@@ -39,7 +38,6 @@ import java.util.Objects;
* @hide
*/
public class MbmsTempFileProvider extends ContentProvider {
- public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs";
public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root";
@@ -182,8 +180,8 @@ public class MbmsTempFileProvider extends ContentProvider {
if (storedTempFileRoot != null) {
return new File(storedTempFileRoot).getCanonicalFile();
} else {
- return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY)
- .getCanonicalFile();
+ return new File(context.getFilesDir(),
+ MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
}
} catch (IOException e) {
throw new RuntimeException("Unable to canonicalize temp file root path " + e);
diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java
index 4b913f82..d38d8a71 100644
--- a/android/telephony/mbms/MbmsUtils.java
+++ b/android/telephony/mbms/MbmsUtils.java
@@ -68,19 +68,20 @@ public class MbmsUtils {
return downloadServices.get(0).serviceInfo;
}
- public static void startBinding(Context context, String serviceAction,
- ServiceConnection serviceConnection) throws MbmsException {
+ public static int startBinding(Context context, String serviceAction,
+ ServiceConnection serviceConnection) {
Intent bindIntent = new Intent();
ServiceInfo mbmsServiceInfo =
MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
if (mbmsServiceInfo == null) {
- throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
+ return MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE;
}
bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
+ return MbmsErrors.SUCCESS;
}
/**
diff --git a/android/telephony/mbms/ServiceInfo.java b/android/telephony/mbms/ServiceInfo.java
index c01604b0..9a01ed00 100644
--- a/android/telephony/mbms/ServiceInfo.java
+++ b/android/telephony/mbms/ServiceInfo.java
@@ -16,6 +16,8 @@
package android.telephony.mbms;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -26,12 +28,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
/**
* Describes a cell-broadcast service. This class should not be instantiated directly -- use
- * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden
+ * {@link StreamingServiceInfo} or {@link FileServiceInfo}
*/
public class ServiceInfo {
// arbitrary limit on the number of locale -> name pairs we support
@@ -58,6 +61,13 @@ public class ServiceInfo {
if (newLocales.size() > MAP_LIMIT) {
throw new RuntimeException("bad locales length " + newLocales.size());
}
+
+ for (Locale l : newLocales) {
+ if (!newNames.containsKey(l)) {
+ throw new IllegalArgumentException("A name must be provided for each locale");
+ }
+ }
+
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -114,16 +124,25 @@ public class ServiceInfo {
}
/**
- * User displayable names listed by language. Do not modify the map returned from this method.
+ * Get the user-displayable name for this cell-broadcast service corresponding to the
+ * provided {@link Locale}.
+ * @param locale The {@link Locale} in which you want the name of the service. This must be a
+ * value from the list returned by {@link #getLocales()} -- an
+ * {@link java.util.NoSuchElementException} may be thrown otherwise.
+ * @return The {@link CharSequence} providing the name of the service in the given
+ * {@link Locale}
*/
- public Map<Locale, String> getNames() {
- return names;
+ public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) {
+ if (!names.containsKey(locale)) {
+ throw new NoSuchElementException("Locale not supported");
+ }
+ return names.get(locale);
}
/**
* The class name for this service - used to categorize and filter
*/
- public String getClassName() {
+ public String getServiceClassName() {
return className;
}
diff --git a/android/telephony/mbms/StreamingService.java b/android/telephony/mbms/StreamingService.java
index 1d66bac0..ec9134a4 100644
--- a/android/telephony/mbms/StreamingService.java
+++ b/android/telephony/mbms/StreamingService.java
@@ -17,8 +17,10 @@
package android.telephony.mbms;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.net.Uri;
import android.os.RemoteException;
+import android.telephony.MbmsStreamingSession;
import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.Log;
@@ -27,7 +29,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Class used to represent a single MBMS stream. After a stream has been started with
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)},
* this class is used to hold information about the stream and control it.
*/
@@ -63,7 +65,7 @@ public class StreamingService {
/**
* State changed due to a call to {@link #stopStreaming()} or
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)}
*/
public static final int REASON_BY_USER_REQUEST = 1;
@@ -101,6 +103,7 @@ public class StreamingService {
public final static int UNICAST_METHOD = 2;
private final int mSubscriptionId;
+ private final MbmsStreamingSession mParentSession;
private final StreamingServiceInfo mServiceInfo;
private final InternalStreamingServiceCallback mCallback;
@@ -111,25 +114,25 @@ public class StreamingService {
*/
public StreamingService(int subscriptionId,
IMbmsStreamingService service,
+ MbmsStreamingSession session,
StreamingServiceInfo streamingServiceInfo,
InternalStreamingServiceCallback callback) {
mSubscriptionId = subscriptionId;
+ mParentSession = session;
mService = service;
mServiceInfo = streamingServiceInfo;
mCallback = callback;
}
/**
- * Retreive the Uri used to play this stream.
+ * Retrieve the Uri used to play this stream.
*
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
*
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
- *
- * @return The {@link Uri} to pass to the streaming client.
+ * @return The {@link Uri} to pass to the streaming client, or {@code null} if an error
+ * occurred.
*/
- public Uri getPlaybackUri() throws MbmsException {
+ public @Nullable Uri getPlaybackUri() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -139,25 +142,26 @@ public class StreamingService {
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ mParentSession.onStreamingServiceStopped(this);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return null;
}
}
/**
- * Retreive the info for this StreamingService.
+ * Retrieve the {@link StreamingServiceInfo} corresponding to this stream.
*/
public StreamingServiceInfo getInfo() {
return mServiceInfo;
}
/**
- * Stop streaming this service.
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+ * Stop streaming this service. Further operations on this object will fail with an
+ * {@link IllegalStateException}.
*
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*/
- public void stopStreaming() throws MbmsException {
+ public void stopStreaming() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -167,32 +171,22 @@ public class StreamingService {
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ } finally {
+ mParentSession.onStreamingServiceStopped(this);
}
}
- /**
- * Disposes of this stream. Further operations on this object will fail with an
- * {@link IllegalStateException}.
- *
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
- * May also throw an {@link IllegalStateException}
- */
- public void dispose() throws MbmsException {
- if (mService == null) {
- throw new IllegalStateException("No streaming service attached");
- }
+ /** @hide */
+ public InternalStreamingServiceCallback getCallback() {
+ return mCallback;
+ }
+ private void sendErrorToApp(int errorCode, String message) {
try {
- mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
+ mCallback.onError(errorCode, message);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- } catch (IllegalArgumentException e) {
- throw new IllegalStateException("StreamingService state inconsistent with middleware");
- } finally {
- mService = null;
+ // Ignore, should not happen locally.
}
}
}
diff --git a/android/telephony/mbms/StreamingServiceCallback.java b/android/telephony/mbms/StreamingServiceCallback.java
index b72c7157..09038244 100644
--- a/android/telephony/mbms/StreamingServiceCallback.java
+++ b/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,6 +16,8 @@
package android.telephony.mbms;
+import android.annotation.Nullable;
+
/**
* A callback class for use when the application is actively streaming content. The middleware
* will provide updates on the status of the stream via this callback.
@@ -33,11 +35,11 @@ public class StreamingServiceCallback {
/**
* Called by the middleware when it has detected an error condition in this stream. The
- * possible error codes are listed in {@link MbmsException}.
+ * possible error codes are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
- public void onError(int errorCode, String message) {
+ public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}
diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java
index 7acc270e..187e9eed 100644
--- a/android/telephony/mbms/UriPathPair.java
+++ b/android/telephony/mbms/UriPathPair.java
@@ -16,13 +16,20 @@
package android.telephony.mbms;
+import android.annotation.SystemApi;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.mbms.vendor.VendorUtils;
-/** @hide */
-public class UriPathPair implements Parcelable {
+/**
+ * Wrapper for a pair of {@link Uri}s that describe a temp file used by the middleware to
+ * download files via cell-broadcast.
+ * @hide
+ */
+@SystemApi
+public final class UriPathPair implements Parcelable {
private final Uri mFilePathUri;
private final Uri mContentUri;
@@ -40,7 +47,7 @@ public class UriPathPair implements Parcelable {
}
/** @hide */
- protected UriPathPair(Parcel in) {
+ private UriPathPair(Parcel in) {
mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
mContentUri = in.readParcelable(Uri.class.getClassLoader());
}
@@ -57,12 +64,23 @@ public class UriPathPair implements Parcelable {
}
};
- /** future systemapi */
+ /**
+ * Returns the file-path {@link Uri}. This has scheme {@code file} and points to the actual
+ * location on disk where the temp file resides. Use this when sending {@link Uri}s back to the
+ * app in the intents in {@link VendorUtils}.
+ * @return A {@code file} {@link Uri}.
+ */
public Uri getFilePathUri() {
return mFilePathUri;
}
- /** future systemapi */
+ /**
+ * Returns the content {@link Uri} that may be used with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)} to obtain a
+ * {@link android.os.ParcelFileDescriptor} to a temp file to write to. This {@link Uri} will
+ * expire if the middleware process dies.
+ * @return A {@code content} {@link Uri}
+ */
public Uri getContentUri() {
return mContentUri;
}
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 71713d01..d845a57b 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -17,40 +17,50 @@
package android.telephony.mbms.vendor;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
-import android.telephony.mbms.DownloadProgressListener;
+import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.FileServiceInfo;
-import android.telephony.mbms.IDownloadProgressListener;
-import android.telephony.mbms.IMbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.IDownloadStateCallback;
+import android.telephony.mbms.IMbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsErrors;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
- * Base class for MbmsDownloadService. The middleware should extend this base class rather than
- * the aidl stub for compatibility
+ * Base class for MbmsDownloadService. The middleware should return an instance of this object from
+ * its {@link android.app.Service#onBind(Intent)} method.
* @hide
- * TODO: future systemapi
*/
+@SystemApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+ private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
+ private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
+
/**
* Initialize the download service for this app and subId, registering the listener.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
* will be intercepted and passed to the app as
- * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+ * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
- * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
- * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
- * {@link IMbmsDownloadManagerCallback#error(int, String)}.
+ * May return any value from {@link MbmsErrors.InitializationErrors}
+ * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+ * {@link IMbmsDownloadSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
- public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback)
+ public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
throws RemoteException {
return 0;
}
@@ -60,22 +70,42 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @hide
*/
@Override
- public final int initialize(int subscriptionId,
- final IMbmsDownloadManagerCallback callback) throws RemoteException {
- return initialize(subscriptionId, new MbmsDownloadManagerCallback() {
+ public final int initialize(final int subscriptionId,
+ final IMbmsDownloadSessionCallback callback) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ callback.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, subscriptionId);
+ }
+ }, 0);
+
+ return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
@Override
- public void error(int errorCode, String message) throws RemoteException {
- callback.error(errorCode, message);
+ public void onError(int errorCode, String message) {
+ try {
+ callback.onError(errorCode, message);
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, subscriptionId);
+ }
}
@Override
- public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
- callback.fileServicesUpdated(services);
+ public void onFileServicesUpdated(List<FileServiceInfo> services) {
+ try {
+ callback.onFileServicesUpdated(services);
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, subscriptionId);
+ }
}
@Override
- public void middlewareReady() throws RemoteException {
- callback.middlewareReady();
+ public void onMiddlewareReady() {
+ try {
+ callback.onMiddlewareReady();
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, subscriptionId);
+ }
}
});
}
@@ -83,7 +113,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
- * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
+ * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -94,11 +124,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
- * @return One of {@link MbmsException#SUCCESS} or
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
+ * @return One of {@link MbmsErrors#SUCCESS} or
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
*/
@Override
- public int getFileServices(int subscriptionId, List<String> serviceClasses)
+ public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
throws RemoteException {
return 0;
}
@@ -110,13 +140,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* If the calling app (as identified by the calling UID) currently has any pending download
* requests that have not been canceled, the middleware must return
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
*
* @param subscriptionId The subscription id the download is operating under.
* @param rootDirectoryPath The path to the app's temp file root directory.
- * @return {@link MbmsException#SUCCESS},
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+ * @return {@link MbmsErrors#SUCCESS},
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
*/
@Override
public int setTempFileRootDirectory(int subscriptionId,
@@ -132,12 +162,32 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* this is not the case, an {@link IllegalStateException} may be thrown.
*
* @param downloadRequest An object describing the set of files to be downloaded.
- * @param listener A listener through which the middleware can provide progress updates to
- * the app while both are still running.
- * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors}
- * or {@link MbmsException#SUCCESS}
+ * @return Any error from {@link MbmsErrors.GeneralErrors}
+ * or {@link MbmsErrors#SUCCESS}
*/
- public int download(DownloadRequest downloadRequest, DownloadProgressListener listener) {
+ @Override
+ public int download(DownloadRequest downloadRequest) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Registers a download state callbacks for the provided {@link DownloadRequest}.
+ *
+ * This method is called by the app when it wants to request updates on the progress or
+ * status of the download.
+ *
+ * If the middleware is not aware of a download having been requested with the provided
+ *
+ * {@link DownloadRequest} in the past,
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+ * must be returned.
+ *
+ * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
+ * for which progress updates are being requested.
+ * @param callback The callback object to use.
+ */
+ public int registerStateCallback(DownloadRequest downloadRequest,
+ DownloadStateCallback callback) throws RemoteException {
return 0;
}
@@ -146,24 +196,101 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @hide
*/
@Override
- public final int download(DownloadRequest downloadRequest, IDownloadProgressListener listener)
+ public final int registerStateCallback(
+ final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
- return download(downloadRequest, new DownloadProgressListener() {
+ final int uid = Binder.getCallingUid();
+ DeathRecipient deathRecipient = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+ }
+ };
+ mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+ callback.asBinder().linkToDeath(deathRecipient, 0);
+
+ DownloadStateCallback exposedCallback = new DownloadStateCallback() {
@Override
- public void progress(DownloadRequest request, FileInfo fileInfo, int
+ public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
- fullDecodedSize) throws RemoteException {
- listener.progress(request, fileInfo, currentDownloadSize, fullDownloadSize,
- currentDecodedSize, fullDecodedSize);
+ fullDecodedSize) {
+ try {
+ callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize,
+ currentDecodedSize, fullDecodedSize);
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ }
}
- });
+
+ @Override
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
+ try {
+ callback.onStateUpdated(request, fileInfo, state);
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ }
+ }
+ };
+
+ mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+
+ return registerStateCallback(downloadRequest, exposedCallback);
}
+ /**
+ * Un-registers a download state callbacks for the provided {@link DownloadRequest}.
+ *
+ * This method is called by the app when it no longer wants to request updates on the
+ * download.
+ *
+ * If the middleware is not aware of a download having been requested with the provided
+ * {@link DownloadRequest} in the past,
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+ * must be returned.
+ *
+ * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
+ * @param callback The callback object that
+ * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback)}
+ * was called with.
+ */
+ public int unregisterStateCallback(DownloadRequest downloadRequest,
+ DownloadStateCallback callback) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Actual AIDL implementation -- hides the callback AIDL from the API.
+ * @hide
+ */
+ @Override
+ public final int unregisterStateCallback(
+ final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
+ throws RemoteException {
+ DeathRecipient deathRecipient =
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+ if (deathRecipient == null) {
+ throw new IllegalArgumentException("Unknown callback");
+ }
+
+ callback.asBinder().unlinkToDeath(deathRecipient, 0);
+
+ DownloadStateCallback exposedCallback =
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ if (exposedCallback == null) {
+ throw new IllegalArgumentException("Unknown callback");
+ }
+
+ return unregisterStateCallback(downloadRequest, exposedCallback);
+ }
/**
* Returns a list of pending {@link DownloadRequest}s that originated from the calling
* application, identified by its uid. A pending request is one that was issued via
- * {@link #download(DownloadRequest, IDownloadProgressListener)} but not cancelled through
+ * {@link #download(DownloadRequest)} but not cancelled through
* {@link #cancelDownload(DownloadRequest)}.
* The middleware must return a non-null result synchronously or throw an exception
* inheriting from {@link RuntimeException}.
@@ -179,13 +306,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* Issues a request to cancel the specified download request.
*
* If the middleware is unable to cancel the request for whatever reason, it should return
- * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app
+ * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
* will no longer be expecting any more file-completed intents from the middleware for this
* {@link DownloadRequest}.
* @param downloadRequest The request to cancel
- * @return {@link MbmsException#SUCCESS},
- * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ * @return {@link MbmsErrors#SUCCESS},
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
@Override
public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
@@ -197,7 +324,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* If the middleware has not yet been properly initialized or if it has no records of the
* file indicated by {@code fileInfo} being associated with {@code downloadRequest},
- * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned.
+ * {@link MbmsDownloadSession#STATUS_UNKNOWN} must be returned.
*
* @param downloadRequest The download request to query.
* @param fileInfo The particular file within the request to get information on.
@@ -217,7 +344,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* In addition, current in-progress downloads must not be interrupted.
*
* If the middleware is not aware of the specified download request, return
- * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
*
* @param downloadRequest The request to re-download files for.
*/
@@ -231,7 +358,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
- * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used
+ * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
* after this method has been called by the app.
*
* Any download requests issued by the app should remain in effect until the app calls
@@ -244,4 +371,12 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public void dispose(int subscriptionId) throws RemoteException {
}
+
+ /**
+ * Indicates that the app identified by the given UID and subscription ID has died.
+ * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
+ * @param subscriptionId The subscription ID the app is using.
+ */
+ public void onAppCallbackDied(int uid, int subscriptionId) {
+ }
}
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 843e0482..f8f370a5 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,13 +18,14 @@ package android.telephony.mbms.vendor;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -32,6 +33,8 @@ import android.telephony.mbms.StreamingServiceInfo;
import java.util.List;
/**
+ * Base class for MBMS streaming services. The middleware should return an instance of this
+ * object from its {@link android.app.Service#onBind(Intent)} method.
* @hide
*/
@SystemApi
@@ -41,16 +44,16 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
*
* May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
* will be intercepted and passed to the app as
- * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+ * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
- * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
- * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
- * {@link IMbmsStreamingManagerCallback#error(int, String)}.
+ * May return any value from {@link MbmsErrors.InitializationErrors}
+ * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+ * {@link IMbmsStreamingSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
- public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
+ public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)
throws RemoteException {
return 0;
}
@@ -60,7 +63,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @hide
*/
@Override
- public final int initialize(final IMbmsStreamingManagerCallback callback,
+ public final int initialize(final IMbmsStreamingSessionCallback callback,
final int subscriptionId) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@@ -70,20 +73,20 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
}
}, 0);
- return initialize(new MbmsStreamingManagerCallback() {
+ return initialize(new MbmsStreamingSessionCallback() {
@Override
- public void onError(int errorCode, String message) {
+ public void onError(final int errorCode, final String message) {
try {
- callback.error(errorCode, message);
+ callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+ public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services) {
try {
- callback.streamingServicesUpdated(services);
+ callback.onStreamingServicesUpdated(services);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -92,7 +95,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public void onMiddlewareReady() {
try {
- callback.middlewareReady();
+ callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -104,7 +107,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
- * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
+ * later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -115,11 +118,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
- * @return {@link MbmsException#SUCCESS} or any of the errors in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors}
+ * @return {@link MbmsErrors#SUCCESS} or any of the errors in
+ * {@link MbmsErrors.GeneralErrors}
*/
@Override
- public int getStreamingServices(int subscriptionId,
+ public int requestUpdateStreamingServices(int subscriptionId,
List<String> serviceClasses) throws RemoteException {
return 0;
}
@@ -127,14 +130,14 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Starts streaming on a particular service. This method may perform asynchronous work. When
* the middleware is ready to send bits to the frontend, it should inform the app via
- * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+ * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param serviceId The ID of the streaming service that the app has requested.
* @param callback The callback object on which the app wishes to receive updates.
- * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
+ * @return Any error in {@link MbmsErrors.GeneralErrors}
*/
public int startStreaming(int subscriptionId, String serviceId,
StreamingServiceCallback callback) throws RemoteException {
@@ -147,8 +150,8 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @hide
*/
@Override
- public int startStreaming(int subscriptionId, String serviceId,
- IStreamingServiceCallback callback) throws RemoteException {
+ public int startStreaming(final int subscriptionId, String serviceId,
+ final IStreamingServiceCallback callback) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -159,19 +162,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
@Override
- public void onError(int errorCode, String message) {
+ public void onError(final int errorCode, final String message) {
try {
- callback.error(errorCode, message);
+ callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamStateUpdated(@StreamingService.StreamingState int state,
- @StreamingService.StreamingStateChangeReason int reason) {
+ public void onStreamStateUpdated(@StreamingService.StreamingState final int state,
+ @StreamingService.StreamingStateChangeReason final int reason) {
try {
- callback.streamStateUpdated(state, reason);
+ callback.onStreamStateUpdated(state, reason);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -180,25 +183,25 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public void onMediaDescriptionUpdated() {
try {
- callback.mediaDescriptionUpdated();
+ callback.onMediaDescriptionUpdated();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+ public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
try {
- callback.broadcastSignalStrengthUpdated(signalStrength);
+ callback.onBroadcastSignalStrengthUpdated(signalStrength);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamMethodUpdated(int methodType) {
+ public void onStreamMethodUpdated(final int methodType) {
try {
- callback.streamMethodUpdated(methodType);
+ callback.onStreamMethodUpdated(methodType);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -225,32 +228,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
* stream state change should be reported to the app via
- * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
- *
- * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
*
- * @param subscriptionId The subscription id to use.
- * @param serviceId The ID of the streaming service that the app wishes to stop.
- */
- @Override
- public void stopStreaming(int subscriptionId, String serviceId)
- throws RemoteException {
- }
-
- /**
- * Dispose of the stream identified by {@code serviceId} for the app identified by the
- * {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
- * No notification back to the app is required for this operation, and the callback provided via
+ * In addition, the callback provided via
* {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
* used after this method has called by the app.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
- * @param serviceId The ID of the streaming service that the app wishes to dispose of.
+ * @param serviceId The ID of the streaming service that the app wishes to stop.
*/
@Override
- public void disposeStream(int subscriptionId, String serviceId)
+ public void stopStreaming(int subscriptionId, String serviceId)
throws RemoteException {
}
@@ -258,7 +248,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
- * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
+ * {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used
* after this method has been called by the app.
*
* May throw an {@link IllegalStateException}
diff --git a/android/telephony/mbms/vendor/VendorIntents.java b/android/telephony/mbms/vendor/VendorUtils.java
index 367c995c..8fb27b29 100644
--- a/android/telephony/mbms/vendor/VendorIntents.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -16,29 +16,32 @@
package android.telephony.mbms.vendor;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
-import android.telephony.mbms.DownloadRequest;
+import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.MbmsDownloadReceiver;
import java.io.File;
import java.util.List;
/**
+ * Contains constants and utility methods for MBMS Download middleware apps to communicate with
+ * frontend apps.
* @hide
- * TODO: future systemapi
*/
-public class VendorIntents {
+@SystemApi
+public class VendorUtils {
/**
* The MBMS middleware should send this when a download of single file has completed or
* failed. Mandatory extras are
- * {@link android.telephony.MbmsDownloadManager#EXTRA_RESULT}
- * {@link android.telephony.MbmsDownloadManager#EXTRA_FILE_INFO}
- * {@link #EXTRA_REQUEST}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
* {@link #EXTRA_FINAL_URI}
*/
@@ -49,7 +52,7 @@ public class VendorIntents {
* The MBMS middleware should send this when it wishes to request {@code content://} URIs to
* serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
* extras are
- * {@link #EXTRA_REQUEST}
+ * {@link #EXTRA_SERVICE_ID}
*
* Optional extras are
* {@link #EXTRA_FD_COUNT} (0 if not present)
@@ -118,48 +121,35 @@ public class VendorIntents {
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
/**
- * Extra containing the {@link DownloadRequest} for which the download result or file
- * descriptor request is for. Must not be null.
- */
- public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
-
- /**
* Extra containing a single {@link Uri} indicating the path to the temp file in which the
* decoded downloaded file resides. Must not be null.
*/
public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
/**
- * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
+ * Extra containing a String representing a service ID, used by
* file-descriptor requests and cleanup requests to specify which service they want to
* request temp files or clean up temp files for, respectively.
*/
- public static final String EXTRA_SERVICE_INFO =
- "android.telephony.mbms.extra.SERVICE_INFO";
+ public static final String EXTRA_SERVICE_ID =
+ "android.telephony.mbms.extra.SERVICE_ID";
/**
* Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
* the various intents from the middleware should be targeted towards.
- * @param uid The uid of the frontend app.
+ * @param packageName The package name of the app.
* @return The component name of the receiver that the middleware should send its intents to,
* or null if the app didn't declare it in the manifest.
*/
- public static ComponentName getAppReceiverFromUid(Context context, int uid) {
- String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
- if (packageNames == null) {
- return null;
- }
-
- for (String packageName : packageNames) {
- ComponentName candidate = new ComponentName(packageName,
- MbmsDownloadReceiver.class.getCanonicalName());
- Intent queryIntent = new Intent();
- queryIntent.setComponent(candidate);
- List<ResolveInfo> receivers =
- context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
- if (receivers != null && receivers.size() > 0) {
- return candidate;
- }
+ public static ComponentName getAppReceiverFromPackageName(Context context, String packageName) {
+ ComponentName candidate = new ComponentName(packageName,
+ MbmsDownloadReceiver.class.getCanonicalName());
+ Intent queryIntent = new Intent();
+ queryIntent.setComponent(candidate);
+ List<ResolveInfo> receivers =
+ context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
+ if (receivers != null && receivers.size() > 0) {
+ return candidate;
}
return null;
}
diff --git a/android/text/InputType.java b/android/text/InputType.java
index 8967b709..f38482e6 100644
--- a/android/text/InputType.java
+++ b/android/text/InputType.java
@@ -182,9 +182,9 @@ public interface InputType {
* want the IME to correct typos.
* Note the contrast with {@link #TYPE_TEXT_FLAG_AUTO_CORRECT} and
* {@link #TYPE_TEXT_FLAG_AUTO_COMPLETE}:
- * {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME should never
+ * {@code TYPE_TEXT_FLAG_NO_SUGGESTIONS} means the IME does not need to
* show an interface to display suggestions. Most IMEs will also take this to
- * mean they should not try to auto-correct what the user is typing.
+ * mean they do not need to try to auto-correct what the user is typing.
*/
public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 40154880..52086065 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,6 +20,10 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
+ * BEGIN LAYOUTLIB CHANGE
+ * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
+ * END LAYOUTLIB CHANGE
+ *
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
@@ -87,8 +91,9 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- *
* @param maxSize The new maximum size.
+ *
+ * @hide
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -185,13 +190,10 @@ public class LruCache<K, V> {
}
/**
- * Remove the eldest entries until the total of remaining entries is at or
- * below the requested size.
- *
* @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
+ * to evict even 0-sized elements.
*/
- public void trimToSize(int maxSize) {
+ private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -205,7 +207,16 @@ public class LruCache<K, V> {
break;
}
- Map.Entry<K, V> toEvict = map.eldest();
+ // BEGIN LAYOUTLIB CHANGE
+ // get the last item in the linked list.
+ // This is not efficient, the goal here is to minimize the changes
+ // compared to the platform version.
+ Map.Entry<K, V> toEvict = null;
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ toEvict = entry;
+ }
+ // END LAYOUTLIB CHANGE
+
if (toEvict == null) {
break;
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index cac27afa..ebb2af45 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,1209 +16,115 @@
package android.view;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
-import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+import com.android.layoutlib.bridge.MockView;
import android.content.Context;
-import android.content.res.CompatibilityInfo.Translator;
-import android.content.res.Configuration;
import android.graphics.Canvas;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.view.SurfaceCallbackHelper;
-
-import java.util.ArrayList;
-import java.util.concurrent.locks.ReentrantLock;
/**
- * Provides a dedicated drawing surface embedded inside of a view hierarchy.
- * You can control the format of this surface and, if you like, its size; the
- * SurfaceView takes care of placing the surface at the correct location on the
- * screen
- *
- * <p>The surface is Z ordered so that it is behind the window holding its
- * SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed. The view hierarchy will take care of correctly
- * compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
- * buttons on top of the Surface, though note however that it can have an
- * impact on performance since a full alpha-blended composite will be performed
- * each time the Surface changes.
- *
- * <p> The transparent region that makes the surface visible is based on the
- * layout positions in the view hierarchy. If the post-layout transform
- * properties are used to draw a sibling view on top of the SurfaceView, the
- * view may not be properly composited with the surface.
+ * Mock version of the SurfaceView.
+ * Only non override public methods from the real SurfaceView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
*
- * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
- * which can be retrieved by calling {@link #getHolder}.
+ * TODO: generate automatically.
*
- * <p>The Surface will be created for you while the SurfaceView's window is
- * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
- * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
- * Surface is created and destroyed as the window is shown and hidden.
- *
- * <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen. If you are going to use it
- * this way, you need to be aware of some threading semantics:
- *
- * <ul>
- * <li> All SurfaceView and
- * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
- * from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
- * state that is also touched by the drawing thread.
- * <li> You must ensure that the drawing thread only touches the underlying
- * Surface while it is valid -- between
- * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
- * and
- * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
- * </ul>
- *
- * <p class="note"><strong>Note:</strong> Starting in platform version
- * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
- * updated synchronously with other View rendering. This means that translating
- * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
- * artifacts may occur on previous versions of the platform when its window is
- * positioned asynchronously.</p>
*/
-public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
- private static final String TAG = "SurfaceView";
- private static final boolean DEBUG = false;
-
- final ArrayList<SurfaceHolder.Callback> mCallbacks
- = new ArrayList<SurfaceHolder.Callback>();
-
- final int[] mLocation = new int[2];
-
- final ReentrantLock mSurfaceLock = new ReentrantLock();
- final Surface mSurface = new Surface(); // Current surface in use
- boolean mDrawingStopped = true;
- // We use this to track if the application has produced a frame
- // in to the Surface. Up until that point, we should be careful not to punch
- // holes.
- boolean mDrawFinished = false;
-
- final Rect mScreenRect = new Rect();
- SurfaceSession mSurfaceSession;
-
- SurfaceControl mSurfaceControl;
- // In the case of format changes we switch out the surface in-place
- // we need to preserve the old one until the new one has drawn.
- SurfaceControl mDeferredDestroySurfaceControl;
- final Rect mTmpRect = new Rect();
- final Configuration mConfiguration = new Configuration();
-
- int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
-
- boolean mIsCreating = false;
- private volatile boolean mRtHandlingPositionUpdates = false;
-
- private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
- = new ViewTreeObserver.OnScrollChangedListener() {
- @Override
- public void onScrollChanged() {
- updateSurface();
- }
- };
-
- private final ViewTreeObserver.OnPreDrawListener mDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- // reposition ourselves where the surface is
- mHaveFrame = getWidth() > 0 && getHeight() > 0;
- updateSurface();
- return true;
- }
- };
-
- boolean mRequestedVisible = false;
- boolean mWindowVisibility = false;
- boolean mLastWindowVisibility = false;
- boolean mViewVisibility = false;
- boolean mWindowStopped = false;
-
- int mRequestedWidth = -1;
- int mRequestedHeight = -1;
- /* Set SurfaceView's format to 565 by default to maintain backward
- * compatibility with applications assuming this format.
- */
- int mRequestedFormat = PixelFormat.RGB_565;
-
- boolean mHaveFrame = false;
- boolean mSurfaceCreated = false;
- long mLastLockTime = 0;
-
- boolean mVisible = false;
- int mWindowSpaceLeft = -1;
- int mWindowSpaceTop = -1;
- int mSurfaceWidth = -1;
- int mSurfaceHeight = -1;
- int mFormat = -1;
- final Rect mSurfaceFrame = new Rect();
- int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- private Translator mTranslator;
-
- private boolean mGlobalListenersAdded;
- private boolean mAttachedToWindow;
-
- private int mSurfaceFlags = SurfaceControl.HIDDEN;
-
- private int mPendingReportDraws;
+public class SurfaceView extends MockView {
public SurfaceView(Context context) {
this(context, null);
}
public SurfaceView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs , 0);
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mRenderNode.requestPositionUpdates(this);
-
- setWillNotDraw(true);
- }
-
- /**
- * Return the SurfaceHolder providing access and control over this
- * SurfaceView's underlying surface.
- *
- * @return SurfaceHolder The holder of the surface.
- */
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
- }
-
- private void updateRequestedVisibility() {
- mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
- }
-
- /** @hide */
- @Override
- public void windowStopped(boolean stopped) {
- mWindowStopped = stopped;
- updateRequestedVisibility();
- updateSurface();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- getViewRootImpl().addWindowStoppedCallback(this);
- mWindowStopped = false;
-
- mViewVisibility = getVisibility() == VISIBLE;
- updateRequestedVisibility();
-
- mAttachedToWindow = true;
- if (!mGlobalListenersAdded) {
- ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnScrollChangedListener(mScrollChangedListener);
- observer.addOnPreDrawListener(mDrawListener);
- mGlobalListenersAdded = true;
- }
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- mWindowVisibility = visibility == VISIBLE;
- updateRequestedVisibility();
- updateSurface();
- }
-
- @Override
- public void setVisibility(int visibility) {
- super.setVisibility(visibility);
- mViewVisibility = visibility == VISIBLE;
- boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
- if (newRequestedVisible != mRequestedVisible) {
- // our base class (View) invalidates the layout only when
- // we go from/to the GONE state. However, SurfaceView needs
- // to request a re-layout when the visibility changes at all.
- // This is needed because the transparent region is computed
- // as part of the layout phase, and it changes (obviously) when
- // the visibility changes.
- requestLayout();
- }
- mRequestedVisible = newRequestedVisible;
- updateSurface();
- }
-
- private void performDrawFinished() {
- if (mPendingReportDraws > 0) {
- mDrawFinished = true;
- if (mAttachedToWindow) {
- mParent.requestTransparentRegion(SurfaceView.this);
-
- notifyDrawFinished();
- invalidate();
- }
- } else {
- Log.e(TAG, System.identityHashCode(this) + "finished drawing"
- + " but no pending report draw (extra call"
- + " to draw completion runnable?)");
- }
- }
-
- void notifyDrawFinished() {
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot != null) {
- viewRoot.pendingDrawFinished();
- }
- mPendingReportDraws--;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- ViewRootImpl viewRoot = getViewRootImpl();
- // It's possible to create a SurfaceView using the default constructor and never
- // attach it to a view hierarchy, this is a common use case when dealing with
- // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
- // the lifecycle. Instead of attaching it to a view, he/she can just pass
- // the SurfaceHolder forward, most live wallpapers do it.
- if (viewRoot != null) {
- viewRoot.removeWindowStoppedCallback(this);
- }
-
- mAttachedToWindow = false;
- if (mGlobalListenersAdded) {
- ViewTreeObserver observer = getViewTreeObserver();
- observer.removeOnScrollChangedListener(mScrollChangedListener);
- observer.removeOnPreDrawListener(mDrawListener);
- mGlobalListenersAdded = false;
- }
-
- while (mPendingReportDraws > 0) {
- notifyDrawFinished();
- }
-
- mRequestedVisible = false;
-
- updateSurface();
- if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
- }
- mSurfaceControl = null;
-
- mHaveFrame = false;
-
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = mRequestedWidth >= 0
- ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
- : getDefaultSize(0, widthMeasureSpec);
- int height = mRequestedHeight >= 0
- ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
- : getDefaultSize(0, heightMeasureSpec);
- setMeasuredDimension(width, height);
- }
-
- /** @hide */
- @Override
- protected boolean setFrame(int left, int top, int right, int bottom) {
- boolean result = super.setFrame(left, top, right, bottom);
- updateSurface();
- return result;
- }
-
- @Override
public boolean gatherTransparentRegion(Region region) {
- if (isAboveParent() || !mDrawFinished) {
- return super.gatherTransparentRegion(region);
- }
-
- boolean opaque = true;
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
- // this view draws, remove it from the transparent region
- opaque = super.gatherTransparentRegion(region);
- } else if (region != null) {
- int w = getWidth();
- int h = getHeight();
- if (w>0 && h>0) {
- getLocationInWindow(mLocation);
- // otherwise, punch a hole in the whole hierarchy
- int l = mLocation[0];
- int t = mLocation[1];
- region.op(l, t, l+w, t+h, Region.Op.UNION);
- }
- }
- if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
- opaque = false;
- }
- return opaque;
+ return false;
}
- @Override
- public void draw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
- // draw() is not called when SKIP_DRAW is set
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
- // punch a whole in the view-hierarchy below us
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
- }
- super.draw(canvas);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (mDrawFinished && !isAboveParent()) {
- // draw() is not called when SKIP_DRAW is set
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- // punch a whole in the view-hierarchy below us
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
- }
- super.dispatchDraw(canvas);
- }
-
- /**
- * Control whether the surface view's surface is placed on top of another
- * regular surface view in the window (but still behind the window itself).
- * This is typically used to place overlays on top of an underlying media
- * surface view.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
- */
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
- mSubLayer = isMediaOverlay
- ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
- /**
- * Control whether the surface view's surface is placed on top of its
- * window. Normally it is placed behind the window, to allow it to
- * (for the most part) appear to composite with the views in the
- * hierarchy. By setting this, you cause it to be placed above the
- * window. This means that none of the contents of the window this
- * SurfaceView is in will be visible on top of its surface.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
- */
public void setZOrderOnTop(boolean onTop) {
- if (onTop) {
- mSubLayer = APPLICATION_PANEL_SUBLAYER;
- } else {
- mSubLayer = APPLICATION_MEDIA_SUBLAYER;
- }
}
- /**
- * Control whether the surface view's content should be treated as secure,
- * preventing it from appearing in screenshots or from being viewed on
- * non-secure displays.
- *
- * <p>Note that this must be set before the surface view's containing
- * window is attached to the window manager.
- *
- * <p>See {@link android.view.Display#FLAG_SECURE} for details.
- *
- * @param isSecure True if the surface view is secure.
- */
public void setSecure(boolean isSecure) {
- if (isSecure) {
- mSurfaceFlags |= SurfaceControl.SECURE;
- } else {
- mSurfaceFlags &= ~SurfaceControl.SECURE;
- }
- }
-
- private void updateOpaqueFlag() {
- if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
- mSurfaceFlags |= SurfaceControl.OPAQUE;
- } else {
- mSurfaceFlags &= ~SurfaceControl.OPAQUE;
- }
}
- private Rect getParentSurfaceInsets() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- } else {
- return root.mWindowAttributes.surfaceInsets;
- }
- }
-
- /** @hide */
- protected void updateSurface() {
- if (!mHaveFrame) {
- return;
- }
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
- return;
- }
-
- mTranslator = viewRoot.mTranslator;
- if (mTranslator != null) {
- mSurface.setCompatibilityTranslator(mTranslator);
- }
-
- int myWidth = mRequestedWidth;
- if (myWidth <= 0) myWidth = getWidth();
- int myHeight = mRequestedHeight;
- if (myHeight <= 0) myHeight = getHeight();
-
- final boolean formatChanged = mFormat != mRequestedFormat;
- final boolean visibleChanged = mVisible != mRequestedVisible;
- final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
- && mRequestedVisible;
- final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
- final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
- boolean redrawNeeded = false;
-
- if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
- getLocationInWindow(mLocation);
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Changes: creating=" + creating
- + " format=" + formatChanged + " size=" + sizeChanged
- + " visible=" + visibleChanged
- + " left=" + (mWindowSpaceLeft != mLocation[0])
- + " top=" + (mWindowSpaceTop != mLocation[1]));
-
- try {
- final boolean visible = mVisible = mRequestedVisible;
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
- mFormat = mRequestedFormat;
- mLastWindowVisibility = mWindowVisibility;
-
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
- if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- final Rect surfaceInsets = getParentSurfaceInsets();
- mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
- if (creating) {
- mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
- mDeferredDestroySurfaceControl = mSurfaceControl;
-
- updateOpaqueFlag();
- mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
- "SurfaceView - " + viewRoot.getTitle().toString(),
- mSurfaceWidth, mSurfaceHeight, mFormat,
- mSurfaceFlags);
- } else if (mSurfaceControl == null) {
- return;
- }
-
- boolean realSizeChanged = false;
-
- mSurfaceLock.lock();
- try {
- mDrawingStopped = !visible;
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
-
- SurfaceControl.openTransaction();
- try {
- mSurfaceControl.setLayer(mSubLayer);
- if (mViewVisibility) {
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
-
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- //
- // There is one more case when the buffer size changes we aren't yet
- // prepared to sync (as even following the transaction applying
- // we still need to latch a buffer).
- // b/28866173
- if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
- mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
- mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- mScreenRect.height() / (float) mSurfaceHeight);
- }
- if (sizeChanged) {
- mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
- }
- } finally {
- SurfaceControl.closeTransaction();
- }
-
- if (sizeChanged || creating) {
- redrawNeeded = true;
- }
-
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (mTranslator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
- } else {
- float appInvertedScale = mTranslator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
- }
-
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- } finally {
- mSurfaceLock.unlock();
- }
-
- try {
- redrawNeeded |= visible && !mDrawFinished;
-
- SurfaceHolder.Callback callbacks[] = null;
-
- final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
- mSurfaceCreated = false;
- if (mSurface.isValid()) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceDestroyed");
- callbacks = getSurfaceCallbacks();
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceDestroyed(mSurfaceHolder);
- }
- // Since Android N the same surface may be reused and given to us
- // again by the system server at a later point. However
- // as we didn't do this in previous releases, clients weren't
- // necessarily required to clean up properly in
- // surfaceDestroyed. This leads to problems for example when
- // clients don't destroy their EGL context, and try
- // and create a new one on the same surface following reuse.
- // Since there is no valid use of the surface in-between
- // surfaceDestroyed and surfaceCreated, we force a disconnect,
- // so the next connect will always work if we end up reusing
- // the surface.
- if (mSurface.isValid()) {
- mSurface.forceScopedDisconnect();
- }
- }
- }
-
- if (creating) {
- mSurface.copyFrom(mSurfaceControl);
- }
-
- if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
- < Build.VERSION_CODES.O) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- mSurface.createFrom(mSurfaceControl);
- }
-
- if (visible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
- mSurfaceCreated = true;
- mIsCreating = true;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceCreated");
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceCreated(mSurfaceHolder);
- }
- }
- if (creating || formatChanged || sizeChanged
- || visibleChanged || realSizeChanged) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceChanged -- format=" + mFormat
- + " w=" + myWidth + " h=" + myHeight);
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
- }
- }
- if (redrawNeeded) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceRedrawNeeded");
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
-
- mPendingReportDraws++;
- viewRoot.drawPending();
- SurfaceCallbackHelper sch =
- new SurfaceCallbackHelper(this::onDrawFinished);
- sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
- }
- }
- } finally {
- mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- mSurface.release();
- // If we are not in the stopped state, then the destruction of the Surface
- // represents a visual change we need to display, and we should go ahead
- // and destroy the SurfaceControl. However if we are in the stopped state,
- // we can just leave the Surface around so it can be a part of animations,
- // and we let the life-time be tied to the parent surface.
- if (!mWindowStopped) {
- mSurfaceControl.destroy();
- mSurfaceControl = null;
- }
- }
- }
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- if (DEBUG) Log.v(
- TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
- + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
- + ", frame=" + mSurfaceFrame);
- } else {
- // Calculate the window position in case RT loses the window
- // and we need to fallback to a UI-thread driven position update
- getLocationInSurface(mLocation);
- final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
- || mWindowSpaceTop != mLocation[1];
- final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
- || getHeight() != mScreenRect.height();
- if (positionChanged || layoutSizeChanged) { // Only the position has changed
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
- // in view local space.
- mLocation[0] = getWidth();
- mLocation[1] = getHeight();
-
- mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
- mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
-
- if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- if (mSurfaceControl == null) {
- return;
- }
-
- if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
- try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, -1);
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- }
- }
- }
- }
-
- private void onDrawFinished() {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " "
- + "finishedDrawing");
- }
-
- if (mDeferredDestroySurfaceControl != null) {
- mDeferredDestroySurfaceControl.destroy();
- mDeferredDestroySurfaceControl = null;
- }
-
- runOnUiThread(() -> {
- performDrawFinished();
- });
- }
-
- private void setParentSpaceRectangle(Rect position, long frameNumber) {
- ViewRootImpl viewRoot = getViewRootImpl();
-
- SurfaceControl.openTransaction();
- try {
- if (frameNumber > 0) {
- mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
- }
- mSurfaceControl.setPosition(position.left, position.top);
- mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- position.height() / (float) mSurfaceHeight);
- } finally {
- SurfaceControl.closeTransaction();
- }
- }
-
- private Rect mRTLastReportedPosition = new Rect();
-
- /**
- * Called by native by a Rendering Worker thread to update the window position
- * @hide
- */
- public final void updateSurfacePosition_renderWorker(long frameNumber,
- int left, int top, int right, int bottom) {
- if (mSurfaceControl == null) {
- return;
- }
-
- // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
- // its 2nd frame if RenderThread is running slowly could potentially see
- // this as false, enter the branch, get pre-empted, then this comes along
- // and reports a new position, then the UI thread resumes and reports
- // its position. This could therefore be de-sync'd in that interval, but
- // the synchronization would violate the rule that RT must never block
- // on the UI thread which would open up potential deadlocks. The risk of
- // a single-frame desync is therefore preferable for now.
- mRtHandlingPositionUpdates = true;
- if (mRTLastReportedPosition.left == left
- && mRTLastReportedPosition.top == top
- && mRTLastReportedPosition.right == right
- && mRTLastReportedPosition.bottom == bottom) {
- return;
- }
- try {
- if (DEBUG) {
- Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- frameNumber, left, top, right, bottom));
- }
- mRTLastReportedPosition.set(left, top, right, bottom);
- setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
- // Now overwrite mRTLastReportedPosition with our values
- } catch (Exception ex) {
- Log.e(TAG, "Exception from repositionChild", ex);
- }
- }
-
- /**
- * Called by native on RenderThread to notify that the view is no longer in the
- * draw tree. UI thread is blocked at this point.
- * @hide
- */
- public final void surfacePositionLost_uiRtSync(long frameNumber) {
- if (DEBUG) {
- Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
- System.identityHashCode(this), frameNumber));
- }
- mRTLastReportedPosition.setEmpty();
-
- if (mSurfaceControl == null) {
- return;
- }
- if (mRtHandlingPositionUpdates) {
- mRtHandlingPositionUpdates = false;
- // This callback will happen while the UI thread is blocked, so we can
- // safely access other member variables at this time.
- // So do what the UI thread would have done if RT wasn't handling position
- // updates.
- if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
- try {
- if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
- "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mScreenRect.left, mScreenRect.top,
- mScreenRect.right, mScreenRect.bottom));
- setParentSpaceRectangle(mScreenRect, frameNumber);
- } catch (Exception ex) {
- Log.e(TAG, "Exception configuring surface", ex);
- }
- }
- }
- }
-
- private SurfaceHolder.Callback[] getSurfaceCallbacks() {
- SurfaceHolder.Callback callbacks[];
- synchronized (mCallbacks) {
- callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
- mCallbacks.toArray(callbacks);
- }
- return callbacks;
- }
-
- /**
- * This method still exists only for compatibility reasons because some applications have relied
- * on this method via reflection. See Issue 36345857 for details.
- *
- * @deprecated No platform code is using this method anymore.
- * @hide
- */
- @Deprecated
- public void setWindowType(int type) {
- if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
- throw new UnsupportedOperationException(
- "SurfaceView#setWindowType() has never been a public API.");
- }
-
- if (type == TYPE_APPLICATION_PANEL) {
- Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
- + "just to make the SurfaceView to be placed on top of its window, you must "
- + "call setZOrderOnTop(true) instead.", new Throwable());
- setZOrderOnTop(true);
- return;
- }
- Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
- + "type=" + type, new Throwable());
- }
-
- private void runOnUiThread(Runnable runnable) {
- Handler handler = getHandler();
- if (handler != null && handler.getLooper() != Looper.myLooper()) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- /**
- * Check to see if the surface has fixed size dimensions or if the surface's
- * dimensions are dimensions are dependent on its current layout.
- *
- * @return true if the surface has dimensions that are fixed in size
- * @hide
- */
- public boolean isFixedSize() {
- return (mRequestedWidth != -1 || mRequestedHeight != -1);
- }
-
- private boolean isAboveParent() {
- return mSubLayer >= 0;
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
}
- private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
- private static final String LOG_TAG = "SurfaceHolder";
+ private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@Override
public boolean isCreating() {
- return mIsCreating;
+ return false;
}
@Override
public void addCallback(Callback callback) {
- synchronized (mCallbacks) {
- // This is a linear search, but in practice we'll
- // have only a couple callbacks, so it doesn't matter.
- if (mCallbacks.contains(callback) == false) {
- mCallbacks.add(callback);
- }
- }
}
@Override
public void removeCallback(Callback callback) {
- synchronized (mCallbacks) {
- mCallbacks.remove(callback);
- }
}
@Override
public void setFixedSize(int width, int height) {
- if (mRequestedWidth != width || mRequestedHeight != height) {
- mRequestedWidth = width;
- mRequestedHeight = height;
- requestLayout();
- }
}
@Override
public void setSizeFromLayout() {
- if (mRequestedWidth != -1 || mRequestedHeight != -1) {
- mRequestedWidth = mRequestedHeight = -1;
- requestLayout();
- }
}
@Override
public void setFormat(int format) {
- // for backward compatibility reason, OPAQUE always
- // means 565 for SurfaceView
- if (format == PixelFormat.OPAQUE)
- format = PixelFormat.RGB_565;
-
- mRequestedFormat = format;
- if (mSurfaceControl != null) {
- updateSurface();
- }
}
- /**
- * @deprecated setType is now ignored.
- */
@Override
- @Deprecated
- public void setType(int type) { }
+ public void setType(int type) {
+ }
@Override
public void setKeepScreenOn(boolean screenOn) {
- runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
- /**
- * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
- *
- * After drawing into the provided {@link Canvas}, the caller must
- * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
- *
- * The caller must redraw the entire surface.
- * @return A canvas for drawing into the surface.
- */
@Override
public Canvas lockCanvas() {
- return internalLockCanvas(null, false);
- }
-
- /**
- * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
- *
- * After drawing into the provided {@link Canvas}, the caller must
- * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
- *
- * @param inOutDirty A rectangle that represents the dirty region that the caller wants
- * to redraw. This function may choose to expand the dirty rectangle if for example
- * the surface has been resized or if the previous contents of the surface were
- * not available. The caller must redraw the entire dirty region as represented
- * by the contents of the inOutDirty rectangle upon return from this function.
- * The caller may also pass <code>null</code> instead, in the case where the
- * entire surface should be redrawn.
- * @return A canvas for drawing into the surface.
- */
- @Override
- public Canvas lockCanvas(Rect inOutDirty) {
- return internalLockCanvas(inOutDirty, false);
+ return null;
}
@Override
- public Canvas lockHardwareCanvas() {
- return internalLockCanvas(null, true);
- }
-
- private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
- mSurfaceLock.lock();
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
- + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
-
- Canvas c = null;
- if (!mDrawingStopped && mSurfaceControl != null) {
- try {
- if (hardware) {
- c = mSurface.lockHardwareCanvas();
- } else {
- c = mSurface.lockCanvas(dirty);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception locking surface", e);
- }
- }
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
- if (c != null) {
- mLastLockTime = SystemClock.uptimeMillis();
- return c;
- }
-
- // If the Surface is not ready to be drawn, then return null,
- // but throttle calls to this function so it isn't called more
- // than every 100ms.
- long now = SystemClock.uptimeMillis();
- long nextTime = mLastLockTime + 100;
- if (nextTime > now) {
- try {
- Thread.sleep(nextTime-now);
- } catch (InterruptedException e) {
- }
- now = SystemClock.uptimeMillis();
- }
- mLastLockTime = now;
- mSurfaceLock.unlock();
-
+ public Canvas lockCanvas(Rect dirty) {
return null;
}
- /**
- * Posts the new contents of the {@link Canvas} to the surface and
- * releases the {@link Canvas}.
- *
- * @param canvas The canvas previously obtained from {@link #lockCanvas}.
- */
@Override
public void unlockCanvasAndPost(Canvas canvas) {
- mSurface.unlockCanvasAndPost(canvas);
- mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
- return mSurface;
+ return null;
}
@Override
public Rect getSurfaceFrame() {
- return mSurfaceFrame;
+ return null;
}
};
-
- class SurfaceControlWithBackground extends SurfaceControl {
- private SurfaceControl mBackgroundControl;
- private boolean mOpaque = true;
- public boolean mVisible = false;
-
- public SurfaceControlWithBackground(SurfaceSession s,
- String name, int w, int h, int format, int flags)
- throws Exception {
- super(s, name, w, h, format, flags);
- mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
- PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
- mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
- // -3 is below all other child layers as SurfaceView never goes below -2
- mBackgroundControl.setLayer(-3);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
- mBackgroundControl.setPosition(x, y);
- }
-
- @Override
- public void setSize(int w, int h) {
- super.setSize(w, h);
- mBackgroundControl.setSize(w, h);
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
- mBackgroundControl.setWindowCrop(crop);
- }
-
- @Override
- public void setFinalCrop(Rect crop) {
- super.setFinalCrop(crop);
- mBackgroundControl.setFinalCrop(crop);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- mOpaque = isOpaque;
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- super.setMatrix(dsdx, dtdx, dsdy, dtdy);
- mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- void updateBackgroundVisibility() {
- if (mOpaque && mVisible) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
- }
}
+
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc576..11cb046a 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,152 +16,46 @@
package android.view.accessibility;
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
-
-import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SystemService;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
-import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IntPair;
-
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
- * and provides facilities for querying the accessibility state of the system.
- * Accessibility events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
* for example an {@link android.app.Activity} starts, the focus or selection of a
* {@link android.view.View} changes etc. Parties interested in handling accessibility
* events implement and register an accessibility service which extends
- * {@link android.accessibilityservice.AccessibilityService}.
+ * {@code android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see AccessibilityNodeInfo
- * @see android.accessibilityservice.AccessibilityService
- * @see Context#getSystemService
- * @see Context#ACCESSIBILITY_SERVICE
+ * @see android.content.Context#getSystemService
*/
-@SystemService(Context.ACCESSIBILITY_SERVICE)
+@SuppressWarnings("UnusedDeclaration")
public final class AccessibilityManager {
- private static final boolean DEBUG = false;
-
- private static final String LOG_TAG = "AccessibilityManager";
-
- /** @hide */
- public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
-
- /** @hide */
- public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
-
- /** @hide */
- public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
-
- /** @hide */
- public static final int DALTONIZER_DISABLED = -1;
-
- /** @hide */
- public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
-
- /** @hide */
- public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
-
- /** @hide */
- public static final int AUTOCLICK_DELAY_DEFAULT = 600;
-
- /**
- * Activity action: Launch UI to manage which accessibility service or feature is assigned
- * to the navigation bar Accessibility button.
- * <p>
- * Input: Nothing.
- * </p>
- * <p>
- * Output: Nothing.
- * </p>
- *
- * @hide
- */
- @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
- "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
-
- static final Object sInstanceSync = new Object();
-
- private static AccessibilityManager sInstance;
-
- private final Object mLock = new Object();
-
- private IAccessibilityManager mService;
-
- final int mUserId;
-
- final Handler mHandler;
-
- final Handler.Callback mCallback;
-
- boolean mIsEnabled;
- int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
- boolean mIsTouchExplorationEnabled;
-
- boolean mIsHighTextContrastEnabled;
-
- private final ArrayMap<AccessibilityStateChangeListener, Handler>
- mAccessibilityStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<TouchExplorationStateChangeListener, Handler>
- mTouchExplorationStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<HighTextContrastChangeListener, Handler>
- mHighTextContrastStateChangeListeners = new ArrayMap<>();
-
- private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
- mServicesStateChangeListeners = new ArrayMap<>();
-
- /**
- * Map from a view's accessibility id to the list of request preparers set for that view
- */
- private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
/**
- * Listener for the system accessibility state. To listen for changes to the
- * accessibility state on the device, implement this interface and register
- * it with the system by calling {@link #addAccessibilityStateChangeListener}.
+ * Listener for the accessibility state.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called when the accessibility enabled state changes.
+ * Called back on change in the accessibility state.
*
* @param enabled Whether accessibility is enabled.
*/
- void onAccessibilityStateChanged(boolean enabled);
+ public void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -177,24 +71,7 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- void onTouchExplorationStateChanged(boolean enabled);
- }
-
- /**
- * Listener for changes to the state of accessibility services. Changes include services being
- * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
- * {@see #addAccessibilityServicesStateChangeListener}.
- *
- * @hide
- */
- public interface AccessibilityServicesStateChangeListener {
-
- /**
- * Called when the state of accessibility services changes.
- *
- * @param manager The manager that is calling back
- */
- void onAccessibilityServicesStateChanged(AccessibilityManager manager);
+ public void onTouchExplorationStateChanged(boolean enabled);
}
/**
@@ -202,8 +79,6 @@ public final class AccessibilityManager {
* the high text contrast state on the device, implement this interface and
* register it with the system by calling
* {@link #addHighTextContrastStateChangeListener}.
- *
- * @hide
*/
public interface HighTextContrastChangeListener {
@@ -212,72 +87,26 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- void onHighTextContrastStateChanged(boolean enabled);
+ public void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- @Override
- public void setState(int state) {
- // We do not want to change this immediately as the application may
- // have already checked that accessibility is on and fired an event,
- // that is now propagating up the view tree, Hence, if accessibility
- // is now off an exception will be thrown. We want to have the exception
- // enforcement to guard against apps that fire unnecessary accessibility
- // events when accessibility is off.
- mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
- }
-
- @Override
- public void notifyServicesStateChanged() {
- final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mServicesStateChangeListeners.isEmpty()) {
- return;
+ public void setState(int state) {
}
- listeners = new ArrayMap<>(mServicesStateChangeListeners);
- }
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final AccessibilityServicesStateChangeListener listener =
- mServicesStateChangeListeners.keyAt(i);
- mServicesStateChangeListeners.valueAt(i).post(() -> listener
- .onAccessibilityServicesStateChanged(AccessibilityManager.this));
- }
- }
+ public void notifyServicesStateChanged() {
+ }
- @Override
- public void setRelevantEventTypes(int eventTypes) {
- mRelevantEventTypes = eventTypes;
- }
- };
+ public void setRelevantEventTypes(int eventTypes) {
+ }
+ };
/**
* Get an AccessibilityManager instance (create one if necessary).
*
- * @param context Context in which this manager operates.
- *
- * @hide
*/
public static AccessibilityManager getInstance(Context context) {
- synchronized (sInstanceSync) {
- if (sInstance == null) {
- final int userId;
- if (Binder.getCallingUid() == Process.SYSTEM_UID
- || context.checkCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS)
- == PackageManager.PERMISSION_GRANTED
- || context.checkCallingOrSelfPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- == PackageManager.PERMISSION_GRANTED) {
- userId = UserHandle.USER_CURRENT;
- } else {
- userId = UserHandle.myUserId();
- }
- sInstance = new AccessibilityManager(context, null, userId);
- }
- }
return sInstance;
}
@@ -285,68 +114,21 @@ public final class AccessibilityManager {
* Create an instance.
*
* @param context A {@link Context}.
- * @param service An interface to the backing service.
- * @param userId User id under which to run.
- *
- * @hide
*/
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
- // Constructor can't be chained because we can't create an instance of an inner class
- // before calling another constructor.
- mCallback = new MyCallback();
- mHandler = new Handler(context.getMainLooper(), mCallback);
- mUserId = userId;
- synchronized (mLock) {
- tryConnectToServiceLocked(service);
- }
- }
-
- /**
- * Create an instance.
- *
- * @param handler The handler to use
- * @param service An interface to the backing service.
- * @param userId User id under which to run.
- *
- * @hide
- */
- public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
- mCallback = new MyCallback();
- mHandler = handler;
- mUserId = userId;
- synchronized (mLock) {
- tryConnectToServiceLocked(service);
- }
}
- /**
- * @hide
- */
public IAccessibilityManagerClient getClient() {
return mClient;
}
/**
- * @hide
- */
- @VisibleForTesting
- public Handler.Callback getCallback() {
- return mCallback;
- }
-
- /**
- * Returns if the accessibility in the system is enabled.
+ * Returns if the {@link AccessibilityManager} is enabled.
*
- * @return True if accessibility is enabled, false otherwise.
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
*/
public boolean isEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsEnabled;
- }
+ return false;
}
/**
@@ -355,13 +137,7 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsTouchExplorationEnabled;
- }
+ return true;
}
/**
@@ -371,169 +147,35 @@ public final class AccessibilityManager {
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
- * @return True if high text contrast is enabled, false otherwise.
- *
- * @hide
*/
public boolean isHighTextContrastEnabled() {
- synchronized (mLock) {
- IAccessibilityManager service = getServiceLocked();
- if (service == null) {
- return false;
- }
- return mIsHighTextContrastEnabled;
- }
+ return false;
}
/**
* Sends an {@link AccessibilityEvent}.
- *
- * @param event The event to send.
- *
- * @throws IllegalStateException if accessibility is not enabled.
- *
- * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
- * events is through calling
- * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
- * instead of this method to allow predecessors to augment/filter events sent by
- * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- if (!mIsEnabled) {
- Looper myLooper = Looper.myLooper();
- if (myLooper == Looper.getMainLooper()) {
- throw new IllegalStateException(
- "Accessibility off. Did you forget to check that?");
- } else {
- // If we're not running on the thread with the main looper, it's possible for
- // the state of accessibility to change between checking isEnabled and
- // calling this method. So just log the error rather than throwing the
- // exception.
- Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
- return;
- }
- }
- if ((event.getEventType() & mRelevantEventTypes) == 0) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
- + " that is not among "
- + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
- }
- return;
- }
- userId = mUserId;
- }
- try {
- event.setEventTime(SystemClock.uptimeMillis());
- // it is possible that this manager is in the same process as the service but
- // client using it is called through Binder from another process. Example: MMS
- // app adds a SMS notification and the NotificationManagerService calls this method
- long identityToken = Binder.clearCallingIdentity();
- service.sendAccessibilityEvent(event, userId);
- Binder.restoreCallingIdentity(identityToken);
- if (DEBUG) {
- Log.i(LOG_TAG, event + " sent");
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error during sending " + event + " ", re);
- } finally {
- event.recycle();
- }
}
/**
- * Requests feedback interruption from all accessibility services.
+ * Requests interruption of the accessibility feedback from all accessibility services.
*/
public void interrupt() {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- if (!mIsEnabled) {
- Looper myLooper = Looper.myLooper();
- if (myLooper == Looper.getMainLooper()) {
- throw new IllegalStateException(
- "Accessibility off. Did you forget to check that?");
- } else {
- // If we're not running on the thread with the main looper, it's possible for
- // the state of accessibility to change between checking isEnabled and
- // calling this method. So just log the error rather than throwing the
- // exception.
- Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
- return;
- }
- }
- userId = mUserId;
- }
- try {
- service.interrupt(userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Requested interrupt from all services");
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
- }
}
/**
* Returns the {@link ServiceInfo}s of the installed accessibility services.
*
* @return An unmodifiable list with {@link ServiceInfo}s.
- *
- * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
*/
@Deprecated
public List<ServiceInfo> getAccessibilityServiceList() {
- List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
- List<ServiceInfo> services = new ArrayList<>();
- final int infoCount = infos.size();
- for (int i = 0; i < infoCount; i++) {
- AccessibilityServiceInfo info = infos.get(i);
- services.add(info.getResolveInfo().serviceInfo);
- }
- return Collections.unmodifiableList(services);
+ return Collections.emptyList();
}
- /**
- * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
- *
- * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
- */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return Collections.emptyList();
- }
- userId = mUserId;
- }
-
- List<AccessibilityServiceInfo> services = null;
- try {
- services = service.getInstalledAccessibilityServiceList(userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
- }
- if (services != null) {
- return Collections.unmodifiableList(services);
- } else {
- return Collections.emptyList();
- }
+ return Collections.emptyList();
}
/**
@@ -548,48 +190,21 @@ public final class AccessibilityManager {
* @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
* @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
* @see AccessibilityServiceInfo#FEEDBACK_VISUAL
- * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
int feedbackTypeFlags) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return Collections.emptyList();
- }
- userId = mUserId;
- }
-
- List<AccessibilityServiceInfo> services = null;
- try {
- services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
- }
- if (services != null) {
- return Collections.unmodifiableList(services);
- } else {
- return Collections.emptyList();
- }
+ return Collections.emptyList();
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system. Equivalent to calling
- * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
- * with a null handler.
+ * the global accessibility state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- addAccessibilityStateChangeListener(listener, null);
+ AccessibilityStateChangeListener listener) {
return true;
}
@@ -603,40 +218,22 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mAccessibilityStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
- /**
- * Unregisters an {@link AccessibilityStateChangeListener}.
- *
- * @param listener The listener.
- * @return True if the listener was previously registered.
- */
public boolean removeAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener) {
- synchronized (mLock) {
- int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
- mAccessibilityStateChangeListeners.remove(listener);
- return (index >= 0);
- }
+ AccessibilityStateChangeListener listener) {
+ return true;
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system. Equivalent to calling
- * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
- * with a null handler.
+ * the global touch exploration state of the system.
*
* @param listener The listener.
- * @return Always returns {@code true}.
+ * @return True if successfully registered.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -650,103 +247,17 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mTouchExplorationStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if listener was previously registered.
+ * @return True if successfully unregistered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- synchronized (mLock) {
- int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
- mTouchExplorationStateChangeListeners.remove(listener);
- return (index >= 0);
- }
- }
-
- /**
- * Registers a {@link AccessibilityServicesStateChangeListener}.
- *
- * @param listener The listener.
- * @param handler The handler on which the listener should be called back, or {@code null}
- * for a callback on the process's main handler.
- * @hide
- */
- public void addAccessibilityServicesStateChangeListener(
- @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mServicesStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
-
- /**
- * Unregisters a {@link AccessibilityServicesStateChangeListener}.
- *
- * @param listener The listener.
- *
- * @hide
- */
- public void removeAccessibilityServicesStateChangeListener(
- @NonNull AccessibilityServicesStateChangeListener listener) {
- // Final CopyOnWriteArrayList - no lock needed.
- mServicesStateChangeListeners.remove(listener);
- }
-
- /**
- * Registers a {@link AccessibilityRequestPreparer}.
- */
- public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
- if (mRequestPreparerLists == null) {
- mRequestPreparerLists = new SparseArray<>(1);
- }
- int id = preparer.getView().getAccessibilityViewId();
- List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
- if (requestPreparerList == null) {
- requestPreparerList = new ArrayList<>(1);
- mRequestPreparerLists.put(id, requestPreparerList);
- }
- requestPreparerList.add(preparer);
- }
-
- /**
- * Unregisters a {@link AccessibilityRequestPreparer}.
- */
- public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
- if (mRequestPreparerLists == null) {
- return;
- }
- int viewId = preparer.getView().getAccessibilityViewId();
- List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
- if (requestPreparerList != null) {
- requestPreparerList.remove(preparer);
- if (requestPreparerList.isEmpty()) {
- mRequestPreparerLists.remove(viewId);
- }
- }
- }
-
- /**
- * Get the preparers that are registered for an accessibility ID
- *
- * @param id The ID of interest
- * @return The list of preparers, or {@code null} if there are none.
- *
- * @hide
- */
- public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
- if (mRequestPreparerLists == null) {
- return null;
- }
- return mRequestPreparerLists.get(id);
+ return true;
}
/**
@@ -758,12 +269,7 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
- synchronized (mLock) {
- mHighTextContrastStateChangeListeners
- .put(listener, (handler == null) ? mHandler : handler);
- }
- }
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -773,51 +279,7 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {
- synchronized (mLock) {
- mHighTextContrastStateChangeListeners.remove(listener);
- }
- }
-
- /**
- * Check if the accessibility volume stream is active.
- *
- * @return True if accessibility volume is active (i.e. some service has requested it). False
- * otherwise.
- * @hide
- */
- public boolean isAccessibilityVolumeStreamActive() {
- List<AccessibilityServiceInfo> serviceInfos =
- getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- for (int i = 0; i < serviceInfos.size(); i++) {
- if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Report a fingerprint gesture to accessibility. Only available for the system process.
- *
- * @param keyCode The key code of the gesture
- * @return {@code true} if accessibility consumes the event. {@code false} if not.
- * @hide
- */
- public boolean sendFingerprintGesture(int keyCode) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return false;
- }
- }
- try {
- return service.sendFingerprintGesture(keyCode);
- } catch (RemoteException e) {
- return false;
- }
- }
+ @NonNull HighTextContrastChangeListener listener) {}
/**
* Sets the current state and notifies listeners, if necessary.
@@ -825,314 +287,14 @@ public final class AccessibilityManager {
* @param stateFlags The state flags.
*/
private void setStateLocked(int stateFlags) {
- final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
- final boolean touchExplorationEnabled =
- (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
- final boolean highTextContrastEnabled =
- (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
-
- final boolean wasEnabled = mIsEnabled;
- final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
- // Ensure listeners get current state from isZzzEnabled() calls.
- mIsEnabled = enabled;
- mIsTouchExplorationEnabled = touchExplorationEnabled;
- mIsHighTextContrastEnabled = highTextContrastEnabled;
-
- if (wasEnabled != enabled) {
- notifyAccessibilityStateChanged();
- }
-
- if (wasTouchExplorationEnabled != touchExplorationEnabled) {
- notifyTouchExplorationStateChanged();
- }
-
- if (wasHighTextContrastEnabled != highTextContrastEnabled) {
- notifyHighTextContrastStateChanged();
- }
}
- /**
- * Find an installed service with the specified {@link ComponentName}.
- *
- * @param componentName The name to match to the service.
- *
- * @return The info corresponding to the installed service, or {@code null} if no such service
- * is installed.
- * @hide
- */
- public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
- ComponentName componentName) {
- final List<AccessibilityServiceInfo> installedServiceInfos =
- getInstalledAccessibilityServiceList();
- if ((installedServiceInfos == null) || (componentName == null)) {
- return null;
- }
- for (int i = 0; i < installedServiceInfos.size(); i++) {
- if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
- return installedServiceInfos.get(i);
- }
- }
- return null;
- }
-
- /**
- * Adds an accessibility interaction connection interface for a given window.
- * @param windowToken The window token to which a connection is added.
- * @param connection The connection.
- *
- * @hide
- */
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
- final IAccessibilityManager service;
- final int userId;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return View.NO_ID;
- }
- userId = mUserId;
- }
- try {
- return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
- }
return View.NO_ID;
}
- /**
- * Removed an accessibility interaction connection interface for a given window.
- * @param windowToken The window token to which a connection is removed.
- *
- * @hide
- */
public void removeAccessibilityInteractionConnection(IWindow windowToken) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.removeAccessibilityInteractionConnection(windowToken);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
- }
- }
-
- /**
- * Perform the accessibility shortcut if the caller has permission.
- *
- * @hide
- */
- public void performAccessibilityShortcut() {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.performAccessibilityShortcut();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
- }
- }
-
- /**
- * Notifies that the accessibility button in the system's navigation area has been clicked
- *
- * @hide
- */
- public void notifyAccessibilityButtonClicked() {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.notifyAccessibilityButtonClicked();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
- }
- }
-
- /**
- * Notifies that the visibility of the accessibility button in the system's navigation area
- * has changed.
- *
- * @param shown {@code true} if the accessibility button is visible within the system
- * navigation area, {@code false} otherwise
- * @hide
- */
- public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.notifyAccessibilityButtonVisibilityChanged(shown);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
- }
- }
-
- /**
- * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
- * window. Intended for use by the System UI only.
- *
- * @param connection The connection to handle the actions. Set to {@code null} to avoid
- * affecting the actions.
- *
- * @hide
- */
- public void setPictureInPictureActionReplacingConnection(
- @Nullable IAccessibilityInteractionConnection connection) {
- final IAccessibilityManager service;
- synchronized (mLock) {
- service = getServiceLocked();
- if (service == null) {
- return;
- }
- }
- try {
- service.setPictureInPictureActionReplacingConnection(connection);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
- }
}
- private IAccessibilityManager getServiceLocked() {
- if (mService == null) {
- tryConnectToServiceLocked(null);
- }
- return mService;
- }
-
- private void tryConnectToServiceLocked(IAccessibilityManager service) {
- if (service == null) {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- if (iBinder == null) {
- return;
- }
- service = IAccessibilityManager.Stub.asInterface(iBinder);
- }
-
- try {
- final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
- setStateLocked(IntPair.first(userStateAndRelevantEvents));
- mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
- mService = service;
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
- }
- }
-
- /**
- * Notifies the registered {@link AccessibilityStateChangeListener}s.
- */
- private void notifyAccessibilityStateChanged() {
- final boolean isEnabled;
- final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mAccessibilityStateChangeListeners.isEmpty()) {
- return;
- }
- isEnabled = mIsEnabled;
- listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final AccessibilityStateChangeListener listener =
- mAccessibilityStateChangeListeners.keyAt(i);
- mAccessibilityStateChangeListeners.valueAt(i)
- .post(() -> listener.onAccessibilityStateChanged(isEnabled));
- }
- }
-
- /**
- * Notifies the registered {@link TouchExplorationStateChangeListener}s.
- */
- private void notifyTouchExplorationStateChanged() {
- final boolean isTouchExplorationEnabled;
- final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mTouchExplorationStateChangeListeners.isEmpty()) {
- return;
- }
- isTouchExplorationEnabled = mIsTouchExplorationEnabled;
- listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final TouchExplorationStateChangeListener listener =
- mTouchExplorationStateChangeListeners.keyAt(i);
- mTouchExplorationStateChangeListeners.valueAt(i)
- .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
- }
- }
-
- /**
- * Notifies the registered {@link HighTextContrastChangeListener}s.
- */
- private void notifyHighTextContrastStateChanged() {
- final boolean isHighTextContrastEnabled;
- final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
- synchronized (mLock) {
- if (mHighTextContrastStateChangeListeners.isEmpty()) {
- return;
- }
- isHighTextContrastEnabled = mIsHighTextContrastEnabled;
- listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
- }
-
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; i++) {
- final HighTextContrastChangeListener listener =
- mHighTextContrastStateChangeListeners.keyAt(i);
- mHighTextContrastStateChangeListeners.valueAt(i)
- .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
- }
- }
-
- /**
- * Determines if the accessibility button within the system navigation area is supported.
- *
- * @return {@code true} if the accessibility button is supported on this device,
- * {@code false} otherwise
- */
- public static boolean isAccessibilityButtonSupported() {
- final Resources res = Resources.getSystem();
- return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
- }
-
- private final class MyCallback implements Handler.Callback {
- public static final int MSG_SET_STATE = 1;
-
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_SET_STATE: {
- // See comment at mClient
- final int state = message.arg1;
- synchronized (mLock) {
- setStateLocked(state);
- }
- } break;
- }
- return true;
- }
- }
}
diff --git a/android/view/inputmethod/ExtractedText.java b/android/view/inputmethod/ExtractedText.java
index 0c5d9e9f..003f221d 100644
--- a/android/view/inputmethod/ExtractedText.java
+++ b/android/view/inputmethod/ExtractedText.java
@@ -87,6 +87,11 @@ public class ExtractedText implements Parcelable {
public int flags;
/**
+ * The hint that has been extracted.
+ */
+ public CharSequence hint;
+
+ /**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
@@ -100,6 +105,7 @@ public class ExtractedText implements Parcelable {
dest.writeInt(selectionStart);
dest.writeInt(selectionEnd);
dest.writeInt(this.flags);
+ TextUtils.writeToParcel(hint, dest, flags);
}
/**
@@ -107,17 +113,18 @@ public class ExtractedText implements Parcelable {
*/
public static final Parcelable.Creator<ExtractedText> CREATOR
= new Parcelable.Creator<ExtractedText>() {
- public ExtractedText createFromParcel(Parcel source) {
- ExtractedText res = new ExtractedText();
- res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- res.startOffset = source.readInt();
- res.partialStartOffset = source.readInt();
- res.partialEndOffset = source.readInt();
- res.selectionStart = source.readInt();
- res.selectionEnd = source.readInt();
- res.flags = source.readInt();
- return res;
- }
+ public ExtractedText createFromParcel(Parcel source) {
+ ExtractedText res = new ExtractedText();
+ res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.startOffset = source.readInt();
+ res.partialStartOffset = source.readInt();
+ res.partialEndOffset = source.readInt();
+ res.selectionStart = source.readInt();
+ res.selectionEnd = source.readInt();
+ res.flags = source.readInt();
+ res.hint = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ return res;
+ }
public ExtractedText[] newArray(int size) {
return new ExtractedText[size];
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 77aea234..8d88ba60 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -42,17 +42,16 @@ import java.util.UUID;
//TODO: Do not allow any crashes from this class.
public final class SmartSelectionEventTracker {
- private static final String LOG_TAG = "SmartSelectionEventTracker";
+ private static final String LOG_TAG = "SmartSelectEventTracker";
private static final boolean DEBUG_LOG_ENABLED = true;
- private static final int START_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS;
- private static final int PREV_EVENT_DELTA = MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS;
- private static final int ENTITY_TYPE = MetricsEvent.NOTIFICATION_TAG;
- private static final int INDEX = MetricsEvent.NOTIFICATION_SHADE_INDEX;
- private static final int TAG = MetricsEvent.FIELD_CLASS_NAME;
- private static final int SMART_INDICES = MetricsEvent.FIELD_GESTURE_LENGTH;
- private static final int EVENT_INDICES = MetricsEvent.FIELD_CONTEXT;
- private static final int SESSION_ID = MetricsEvent.FIELD_INSTANT_APP_LAUNCH_TOKEN;
+ private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
+ private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
+ private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
+ private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
+ private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
+ private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+ private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
private static final String ZERO = "0";
private static final String TEXTVIEW = "textview";
@@ -84,6 +83,7 @@ public final class SmartSelectionEventTracker {
private long mSessionStartTime;
private long mLastEventTime;
private boolean mSmartSelectionTriggered;
+ private String mVersionTag;
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
@@ -98,7 +98,8 @@ public final class SmartSelectionEventTracker {
public void logEvent(@NonNull SelectionEvent event) {
Preconditions.checkNotNull(event);
- if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null) {
+ if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null
+ && DEBUG_LOG_ENABLED) {
Log.d(LOG_TAG, "Selection session not yet started. Ignoring event");
return;
}
@@ -114,6 +115,7 @@ public final class SmartSelectionEventTracker {
case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
case SelectionEvent.EventType.SMART_SELECTION_MULTI:
mSmartSelectionTriggered = true;
+ mVersionTag = getVersionTag(event);
mSmartIndices[0] = event.mStart;
mSmartIndices[1] = event.mEnd;
break;
@@ -132,16 +134,15 @@ public final class SmartSelectionEventTracker {
}
private void writeEvent(SelectionEvent event, long now) {
- final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
+ final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
- .setSubtype(event.mEventType)
+ .setSubtype(getLogSubType(event))
.setPackageName(mContext.getPackageName())
- .setTimestamp(now)
.addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
- .addTaggedData(PREV_EVENT_DELTA, now - mLastEventTime)
- .addTaggedData(ENTITY_TYPE, event.mEntityType)
+ .addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
.addTaggedData(INDEX, mIndex)
- .addTaggedData(TAG, getTag(event))
+ .addTaggedData(VERSION_TAG, mVersionTag)
.addTaggedData(SMART_INDICES, getSmartDelta())
.addTaggedData(EVENT_INDICES, getEventDelta(event))
.addTaggedData(SESSION_ID, mSessionId);
@@ -168,42 +169,120 @@ public final class SmartSelectionEventTracker {
mSessionStartTime = 0;
mLastEventTime = 0;
mSmartSelectionTriggered = false;
+ mVersionTag = getVersionTag(null);
mSessionId = null;
}
- private int getLogType(SelectionEvent event) {
+ private static int getLogType(SelectionEvent event) {
switch (event.mEventType) {
- case SelectionEvent.EventType.SELECTION_STARTED: // fall through
- case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
- case SelectionEvent.EventType.SMART_SELECTION_MULTI: // fall through
- case SelectionEvent.EventType.AUTO_SELECTION:
- return MetricsEvent.TYPE_OPEN;
+ case SelectionEvent.ActionType.OVERTYPE:
+ return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE;
+ case SelectionEvent.ActionType.COPY:
+ return MetricsEvent.ACTION_TEXT_SELECTION_COPY;
+ case SelectionEvent.ActionType.PASTE:
+ return MetricsEvent.ACTION_TEXT_SELECTION_PASTE;
+ case SelectionEvent.ActionType.CUT:
+ return MetricsEvent.ACTION_TEXT_SELECTION_CUT;
+ case SelectionEvent.ActionType.SHARE:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SHARE;
+ case SelectionEvent.ActionType.SMART_SHARE:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
+ case SelectionEvent.ActionType.DRAG:
+ return MetricsEvent.ACTION_TEXT_SELECTION_DRAG;
case SelectionEvent.ActionType.ABANDON:
- return MetricsEvent.TYPE_CLOSE;
+ return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON;
+ case SelectionEvent.ActionType.OTHER:
+ return MetricsEvent.ACTION_TEXT_SELECTION_OTHER;
+ case SelectionEvent.ActionType.SELECT_ALL:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL;
+ case SelectionEvent.ActionType.RESET:
+ return MetricsEvent.ACTION_TEXT_SELECTION_RESET;
+ case SelectionEvent.EventType.SELECTION_STARTED:
+ return MetricsEvent.ACTION_TEXT_SELECTION_START;
+ case SelectionEvent.EventType.SELECTION_MODIFIED:
+ return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY;
+ case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE;
+ case SelectionEvent.EventType.SMART_SELECTION_MULTI:
+ return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI;
+ case SelectionEvent.EventType.AUTO_SELECTION:
+ return MetricsEvent.ACTION_TEXT_SELECTION_AUTO;
+ default:
+ return MetricsEvent.VIEW_UNKNOWN;
}
- if (event.isActionType()) {
- if (event.isTerminal() && mSmartSelectionTriggered) {
- if (matchesSmartSelectionBounds(event)) {
- // Smart selection accepted.
- return MetricsEvent.TYPE_SUCCESS;
- } else if (containsOriginalSelection(event)) {
- // Smart selection rejected.
- return MetricsEvent.TYPE_FAILURE;
- }
- // User changed the original selection entirely.
- }
- return MetricsEvent.TYPE_ACTION;
- } else {
- return MetricsEvent.TYPE_UPDATE;
+ }
+
+ private static String getLogTypeString(int logType) {
+ switch (logType) {
+ case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE:
+ return "OVERTYPE";
+ case MetricsEvent.ACTION_TEXT_SELECTION_COPY:
+ return "COPY";
+ case MetricsEvent.ACTION_TEXT_SELECTION_PASTE:
+ return "PASTE";
+ case MetricsEvent.ACTION_TEXT_SELECTION_CUT:
+ return "CUT";
+ case MetricsEvent.ACTION_TEXT_SELECTION_SHARE:
+ return "SHARE";
+ case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE:
+ return "SMART_SHARE";
+ case MetricsEvent.ACTION_TEXT_SELECTION_DRAG:
+ return "DRAG";
+ case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON:
+ return "ABANDON";
+ case MetricsEvent.ACTION_TEXT_SELECTION_OTHER:
+ return "OTHER";
+ case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL:
+ return "SELECT_ALL";
+ case MetricsEvent.ACTION_TEXT_SELECTION_RESET:
+ return "RESET";
+ case MetricsEvent.ACTION_TEXT_SELECTION_START:
+ return "SELECTION_STARTED";
+ case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY:
+ return "SELECTION_MODIFIED";
+ case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE:
+ return "SMART_SELECTION_SINGLE";
+ case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI:
+ return "SMART_SELECTION_MULTI";
+ case MetricsEvent.ACTION_TEXT_SELECTION_AUTO:
+ return "AUTO_SELECTION";
+ default:
+ return UNKNOWN;
}
}
- private boolean matchesSmartSelectionBounds(SelectionEvent event) {
- return event.mStart == mSmartIndices[0] && event.mEnd == mSmartIndices[1];
+ private static int getLogSubType(SelectionEvent event) {
+ switch (event.mEntityType) {
+ case TextClassifier.TYPE_OTHER:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
+ case TextClassifier.TYPE_EMAIL:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
+ case TextClassifier.TYPE_PHONE:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
+ case TextClassifier.TYPE_ADDRESS:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
+ case TextClassifier.TYPE_URL:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
+ default:
+ return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
+ }
}
- private boolean containsOriginalSelection(SelectionEvent event) {
- return event.mStart <= mOrigStart && event.mEnd > mOrigStart;
+ private static String getLogSubTypeString(int logSubType) {
+ switch (logSubType) {
+ case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
+ return TextClassifier.TYPE_OTHER;
+ case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
+ return TextClassifier.TYPE_EMAIL;
+ case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
+ return TextClassifier.TYPE_PHONE;
+ case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
+ return TextClassifier.TYPE_ADDRESS;
+ case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
+ return TextClassifier.TYPE_URL;
+ default:
+ return TextClassifier.TYPE_UNKNOWN;
+ }
}
private int getSmartDelta() {
@@ -211,8 +290,9 @@ public final class SmartSelectionEventTracker {
return (clamp(mSmartIndices[0] - mOrigStart) << 16)
| (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
}
- // If no smart selection, return start selection indices (i.e. [0, 1])
- return /* (0 << 16) | */ (1 & 0xffff);
+ // If the smart selection model was not run, return invalid selection indices [0,0]. This
+ // allows us to tell from the terminal event alone whether the model was run.
+ return 0;
}
private int getEventDelta(SelectionEvent event) {
@@ -220,7 +300,7 @@ public final class SmartSelectionEventTracker {
| (clamp(event.mEnd - mOrigStart) & 0xffff);
}
- private String getTag(SelectionEvent event) {
+ private String getVersionTag(@Nullable SelectionEvent event) {
final String widgetType;
switch (mWidgetType) {
case WidgetType.TEXTVIEW:
@@ -238,7 +318,9 @@ public final class SmartSelectionEventTracker {
default:
widgetType = UNKNOWN;
}
- final String version = Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
+ final String version = event == null
+ ? SelectionEvent.NO_VERSION_TAG
+ : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
return String.format("%s/%s", widgetType, version);
}
@@ -253,66 +335,17 @@ public final class SmartSelectionEventTracker {
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
- final String tag = Objects.toString(log.getTaggedData(TAG), "tag");
+ final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
-
- final String event;
- switch (log.getSubtype()) {
- case SelectionEvent.ActionType.OVERTYPE:
- event = "OVERTYPE";
- break;
- case SelectionEvent.ActionType.COPY:
- event = "COPY";
- break;
- case SelectionEvent.ActionType.PASTE:
- event = "PASTE";
- break;
- case SelectionEvent.ActionType.CUT:
- event = "CUT";
- break;
- case SelectionEvent.ActionType.SHARE:
- event = "SHARE";
- break;
- case SelectionEvent.ActionType.SMART_SHARE:
- event = "SMART_SHARE";
- break;
- case SelectionEvent.ActionType.DRAG:
- event = "DRAG";
- break;
- case SelectionEvent.ActionType.ABANDON:
- event = "ABANDON";
- break;
- case SelectionEvent.ActionType.OTHER:
- event = "OTHER";
- break;
- case SelectionEvent.ActionType.SELECT_ALL:
- event = "SELECT_ALL";
- break;
- case SelectionEvent.ActionType.RESET:
- event = "RESET";
- break;
- case SelectionEvent.EventType.SELECTION_STARTED:
- String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
- sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
- Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
- event = "SELECTION_STARTED";
- break;
- case SelectionEvent.EventType.SELECTION_MODIFIED:
- event = "SELECTION_MODIFIED";
- break;
- case SelectionEvent.EventType.SMART_SELECTION_SINGLE:
- event = "SMART_SELECTION_SINGLE";
- break;
- case SelectionEvent.EventType.SMART_SELECTION_MULTI:
- event = "SMART_SELECTION_MULTI";
- break;
- case SelectionEvent.EventType.AUTO_SELECTION:
- event = "AUTO_SELECTION";
- break;
- default:
- event = "UNKNOWN";
+ if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
+ String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
+ sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
+ Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
}
+ final String type = getLogTypeString(log.getType());
+ final String subType = getLogSubTypeString(log.getSubtype());
+
final int smartIndices = Integer.parseInt(
Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
@@ -323,11 +356,8 @@ public final class SmartSelectionEventTracker {
final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
final int eventEnd = (short) (eventIndices & 0xffff);
- final String entity = Objects.toString(
- log.getTaggedData(ENTITY_TYPE), TextClassifier.TYPE_UNKNOWN);
-
- Log.d(LOG_TAG, String.format("%2d: %s, context=%d,%d - old=%d,%d [%s] (%s)",
- index, event, eventStart, eventEnd, smartStart, smartEnd, entity, tag));
+ Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
+ index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
}
/**
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index f368c74a..8e1f2183 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,213 +1,58 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package android.view.textservice;
-import android.annotation.SystemService;
-import android.content.Context;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.Log;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
-import com.android.internal.textservice.ITextServicesManager;
-
import java.util.Locale;
/**
- * System API to the overall text services, which arbitrates interaction between applications
- * and text services.
- *
- * The user can change the current text services in Settings. And also applications can specify
- * the target text services.
- *
- * <h3>Architecture Overview</h3>
- *
- * <p>There are three primary parties involved in the text services
- * framework (TSF) architecture:</p>
- *
- * <ul>
- * <li> The <strong>text services manager</strong> as expressed by this class
- * is the central point of the system that manages interaction between all
- * other parts. It is expressed as the client-side API here which exists
- * in each application context and communicates with a global system service
- * that manages the interaction across all processes.
- * <li> A <strong>text service</strong> implements a particular
- * interaction model allowing the client application to retrieve information of text.
- * The system binds to the current text service that is in use, causing it to be created and run.
- * <li> Multiple <strong>client applications</strong> arbitrate with the text service
- * manager for connections to text services.
- * </ul>
- *
- * <h3>Text services sessions</h3>
- * <ul>
- * <li>The <strong>spell checker session</strong> is one of the text services.
- * {@link android.view.textservice.SpellCheckerSession}</li>
- * </ul>
- *
+ * A stub class of TextServicesManager for Layout-Lib.
*/
-@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- private static final String TAG = TextServicesManager.class.getSimpleName();
- private static final boolean DBG = false;
-
- private static TextServicesManager sInstance;
-
- private final ITextServicesManager mService;
-
- private TextServicesManager() throws ServiceNotFoundException {
- mService = ITextServicesManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
- }
+ private static final TextServicesManager sInstance = new TextServicesManager();
+ private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- synchronized (TextServicesManager.class) {
- if (sInstance == null) {
- try {
- sInstance = new TextServicesManager();
- } catch (ServiceNotFoundException e) {
- throw new IllegalStateException(e);
- }
- }
- return sInstance;
- }
- }
-
- /**
- * Returns the language component of a given locale string.
- */
- private static String parseLanguageFromLocaleString(String locale) {
- final int idx = locale.indexOf('_');
- if (idx < 0) {
- return locale;
- } else {
- return locale.substring(0, idx);
- }
+ return sInstance;
}
- /**
- * Get a spell checker session for the specified spell checker
- * @param locale the locale for the spell checker. If {@code locale} is null and
- * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
- * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
- * the locale specified in Settings will be returned only when it is same as {@code locale}.
- * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
- * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
- * selected.
- * @param listener a spell checker session lister for getting results from a spell checker.
- * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
- * languages in settings will be returned.
- * @return the spell checker session of the spell checker
- */
public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
- if (listener == null) {
- throw new NullPointerException();
- }
- if (!referToSpellCheckerLanguageSettings && locale == null) {
- throw new IllegalArgumentException("Locale should not be null if you don't refer"
- + " settings.");
- }
-
- if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
- return null;
- }
-
- final SpellCheckerInfo sci;
- try {
- sci = mService.getCurrentSpellChecker(null);
- } catch (RemoteException e) {
- return null;
- }
- if (sci == null) {
- return null;
- }
- SpellCheckerSubtype subtypeInUse = null;
- if (referToSpellCheckerLanguageSettings) {
- subtypeInUse = getCurrentSpellCheckerSubtype(true);
- if (subtypeInUse == null) {
- return null;
- }
- if (locale != null) {
- final String subtypeLocale = subtypeInUse.getLocale();
- final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
- if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
- return null;
- }
- }
- } else {
- final String localeStr = locale.toString();
- for (int i = 0; i < sci.getSubtypeCount(); ++i) {
- final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
- final String tempSubtypeLocale = subtype.getLocale();
- final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
- if (tempSubtypeLocale.equals(localeStr)) {
- subtypeInUse = subtype;
- break;
- } else if (tempSubtypeLanguage.length() >= 2 &&
- locale.getLanguage().equals(tempSubtypeLanguage)) {
- subtypeInUse = subtype;
- }
- }
- }
- if (subtypeInUse == null) {
- return null;
- }
- final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
- try {
- mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
- session.getTextServicesSessionListener(),
- session.getSpellCheckerSessionListener(), bundle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return session;
+ return null;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- try {
- final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
- if (DBG) {
- Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
- }
- return retval;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return EMPTY_SPELL_CHECKER_INFO;
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- try {
- // Passing null as a locale for ICS
- return mService.getCurrentSpellChecker(null);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return null;
}
/**
@@ -215,22 +60,13 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- try {
- // Passing null as a locale until we support multiple enabled spell checker subtypes.
- return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return null;
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- try {
- return mService.isSpellCheckerEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return false;
}
}
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 419b7b24..202f2046 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,3016 +16,223 @@
package android.webkit;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.Widget;
+import com.android.layoutlib.bridge.MockView;
+
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Picture;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.net.http.SslCertificate;
-import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
-import android.os.StrictMode;
-import android.print.PrintDocumentAdapter;
-import android.security.KeyChain;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.DragEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ViewHierarchyEncoder;
-import android.view.ViewStructure;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.textclassifier.TextClassifier;
-import android.widget.AbsoluteLayout;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Map;
/**
- * <p>A View that displays web pages. This class is the basis upon which you
- * can roll your own web browser or simply display some online content within your Activity.
- * It uses the WebKit rendering engine to display
- * web pages and includes methods to navigate forward and backward
- * through a history, zoom in and out, perform text searches and more.
- *
- * <p>Note that, in order for your Activity to access the Internet and load web pages
- * in a WebView, you must add the {@code INTERNET} permissions to your
- * Android Manifest file:
- *
- * <pre>
- * {@code <uses-permission android:name="android.permission.INTERNET" />}
- * </pre>
- *
- * <p>This must be a child of the <a
- * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
- * element.
- *
- * <p>For more information, read
- * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
- *
- * <h3>Basic usage</h3>
- *
- * <p>By default, a WebView provides no browser-like widgets, does not
- * enable JavaScript and web page errors are ignored. If your goal is only
- * to display some HTML as a part of your UI, this is probably fine;
- * the user won't need to interact with the web page beyond reading
- * it, and the web page won't need to interact with the user. If you
- * actually want a full-blown web browser, then you probably want to
- * invoke the Browser application with a URL Intent rather than show it
- * with a WebView. For example:
- * <pre>
- * Uri uri = Uri.parse("https://www.example.com");
- * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- * startActivity(intent);
- * </pre>
- * <p>See {@link android.content.Intent} for more information.
- *
- * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
- * or set the entire Activity window as a WebView during {@link
- * android.app.Activity#onCreate(Bundle) onCreate()}:
- *
- * <pre class="prettyprint">
- * WebView webview = new WebView(this);
- * setContentView(webview);
- * </pre>
- *
- * <p>Then load the desired web page:
- *
- * <pre>
- * // Simplest usage: note that an exception will NOT be thrown
- * // if there is an error loading this page (see below).
- * webview.loadUrl("https://example.com/");
- *
- * // OR, you can also load from an HTML string:
- * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
- * webview.loadData(summary, "text/html", null);
- * // ... although note that there are restrictions on what this HTML can do.
- * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
- * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
- * </pre>
- *
- * <p>A WebView has several customization points where you can add your
- * own behavior. These are:
- *
- * <ul>
- * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
- * This class is called when something that might impact a
- * browser UI happens, for instance, progress updates and
- * JavaScript alerts are sent here (see <a
- * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
- * </li>
- * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
- * It will be called when things happen that impact the
- * rendering of the content, eg, errors or form submissions. You
- * can also intercept URL loading here (via {@link
- * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
- * shouldOverrideUrlLoading()}).</li>
- * <li>Modifying the {@link android.webkit.WebSettings}, such as
- * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
- * setJavaScriptEnabled()}. </li>
- * <li>Injecting Java objects into the WebView using the
- * {@link android.webkit.WebView#addJavascriptInterface} method. This
- * method allows you to inject Java objects into a page's JavaScript
- * context, so that they can be accessed by JavaScript in the page.</li>
- * </ul>
- *
- * <p>Here's a more complicated example, showing error handling,
- * settings, and progress notification:
- *
- * <pre class="prettyprint">
- * // Let's display the progress in the activity title bar, like the
- * // browser app does.
- * getWindow().requestFeature(Window.FEATURE_PROGRESS);
- *
- * webview.getSettings().setJavaScriptEnabled(true);
- *
- * final Activity activity = this;
- * webview.setWebChromeClient(new WebChromeClient() {
- * public void onProgressChanged(WebView view, int progress) {
- * // Activities and WebViews measure progress with different scales.
- * // The progress meter will automatically disappear when we reach 100%
- * activity.setProgress(progress * 1000);
- * }
- * });
- * webview.setWebViewClient(new WebViewClient() {
- * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
- * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
- * }
- * });
- *
- * webview.loadUrl("https://developer.android.com/");
- * </pre>
- *
- * <h3>Zoom</h3>
- *
- * <p>To enable the built-in zoom, set
- * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
- * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
- *
- * <p>NOTE: Using zoom if either the height or width is set to
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
- * and should be avoided.
- *
- * <h3>Cookie and window management</h3>
- *
- * <p>For obvious security reasons, your application has its own
- * cache, cookie store etc.&mdash;it does not share the Browser
- * application's data.
- *
- * <p>By default, requests by the HTML to open new windows are
- * ignored. This is {@code true} whether they be opened by JavaScript or by
- * the target attribute on a link. You can customize your
- * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
- * and render them in whatever manner you want.
- *
- * <p>The standard behavior for an Activity is to be destroyed and
- * recreated when the device orientation or any other configuration changes. This will cause
- * the WebView to reload the current page. If you don't want that, you
- * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
- * changes, and then just leave the WebView alone. It'll automatically
- * re-orient itself as appropriate. Read <a
- * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
- * more information about how to handle configuration changes during runtime.
- *
- *
- * <h3>Building web pages to support different screen densities</h3>
- *
- * <p>The screen density of a device is based on the screen resolution. A screen with low density
- * has fewer available pixels per inch, where a screen with high density
- * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
- * screen is important because, other things being equal, a UI element (such as a button) whose
- * height and width are defined in terms of screen pixels will appear larger on the lower density
- * screen and smaller on the higher density screen.
- * For simplicity, Android collapses all actual screen densities into three generalized densities:
- * high, medium, and low.
- * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
- * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
- * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
- * are bigger).
- * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
- * and meta tag features to help you (as a web developer) target screens with different screen
- * densities.
- * <p>Here's a summary of the features you can use to handle different screen densities:
- * <ul>
- * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
- * default scaling factor used for the current device. For example, if the value of {@code
- * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
- * and default scaling is not applied to the web page; if the value is "1.5", then the device is
- * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
- * value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x.</li>
- * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
- * densities for which this style sheet is to be used. The corresponding value should be either
- * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
- * density, or high density screens, respectively. For example:
- * <pre>
- * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
- * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
- * which is the high density pixel ratio.
- * </li>
- * </ul>
- *
- * <h3>HTML5 Video support</h3>
- *
- * <p>In order to support inline HTML5 video in your application you need to have hardware
- * acceleration turned on.
- *
- * <h3>Full screen support</h3>
- *
- * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
- * {@link android.webkit.WebChromeClient} and implement both
- * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
- * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
- * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
- * is loading.
- *
- * <h3>HTML5 Geolocation API support</h3>
- *
- * <p>For applications targeting Android N and later releases
- * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
- * secure origins such as https. For such applications requests to geolocation api on non-secure
- * origins are automatically denied without invoking the corresponding
- * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
- * method.
- *
- * <h3>Layout size</h3>
- * <p>
- * It is recommended to set the WebView layout height to a fixed value or to
- * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
- * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
- * for the height none of the WebView's parents should use a
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
- * incorrect sizing of the views.
- *
- * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
- * enables the following behaviors:
- * <ul>
- * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
- * relative to the HTML body may not be sized correctly. </li>
- * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
- * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
- * </ul>
- *
- * <p>
- * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
- * supported. If such a width is used the WebView will attempt to use the width of the parent
- * instead.
- *
- * <h3>Metrics</h3>
- *
- * <p>
- * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
- * helps Google improve WebView. Data is collected on a per-app basis for each app which has
- * instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- * android:value="true" /&gt;
- * </pre>
- * <p>
- * Data will only be uploaded for a given app if the user has consented AND the app has not opted
- * out.
- *
- * <h3>Safe Browsing</h3>
- *
- * <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
- * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
- * <p>
- * <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- * android:value="true" /&gt;
- * </pre>
+ * Mock version of the WebView.
+ * Only non override public methods from the real WebView have been added in there.
+ * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ *
+ * TODO: generate automatically.
*
*/
-// Implementation notes.
-// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
-// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
-// Methods are delegated to the provider implementation: all public API methods introduced in this
-// file are fully delegated, whereas public and protected methods from the View base classes are
-// only delegated where a specific need exists for them to do so.
-@Widget
-public class WebView extends AbsoluteLayout
- implements ViewTreeObserver.OnGlobalFocusChangeListener,
- ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
-
- private static final String LOGTAG = "WebView";
-
- // Throwing an exception for incorrect thread usage if the
- // build target is JB MR2 or newer. Defaults to false, and is
- // set in the WebView constructor.
- private static volatile boolean sEnforceThreadChecking = false;
-
- /**
- * Transportation object for returning WebView across thread boundaries.
- */
- public class WebViewTransport {
- private WebView mWebview;
+public class WebView extends MockView {
- /**
- * Sets the WebView to the transportation object.
- *
- * @param webview the WebView to transport
- */
- public synchronized void setWebView(WebView webview) {
- mWebview = webview;
- }
-
- /**
- * Gets the WebView object.
- *
- * @return the transported WebView object
- */
- public synchronized WebView getWebView() {
- return mWebview;
- }
- }
-
- /**
- * URI scheme for telephone number.
- */
- public static final String SCHEME_TEL = "tel:";
/**
- * URI scheme for email address.
- */
- public static final String SCHEME_MAILTO = "mailto:";
- /**
- * URI scheme for map address.
- */
- public static final String SCHEME_GEO = "geo:0,0?q=";
-
- /**
- * Interface to listen for find results.
- */
- public interface FindListener {
- /**
- * Notifies the listener about progress made by a find operation.
- *
- * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
- * @param numberOfMatches how many matches have been found
- * @param isDoneCounting whether the find operation has actually completed. The listener
- * may be notified multiple times while the
- * operation is underway, and the numberOfMatches
- * value should not be considered final unless
- * isDoneCounting is {@code true}.
- */
- public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
- boolean isDoneCounting);
- }
-
- /**
- * Callback interface supplied to {@link #postVisualStateCallback} 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 identifier passed to {@link #postVisualStateCallback} when this
- * callback was posted.
- */
- public abstract void onComplete(long requestId);
- }
-
- /**
- * Interface to listen for new pictures as they change.
- *
- * @deprecated This interface is now obsolete.
- */
- @Deprecated
- public interface PictureListener {
- /**
- * Used to provide notification that the WebView's picture has changed.
- * See {@link WebView#capturePicture} for details of the picture.
- *
- * @param view the WebView that owns the picture
- * @param picture the new picture. Applications targeting
- * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
- * will always receive a {@code null} Picture.
- * @deprecated Deprecated due to internal changes.
- */
- @Deprecated
- public void onNewPicture(WebView view, Picture picture);
- }
-
- public static class HitTestResult {
- /**
- * Default HitTestResult, where the target is unknown.
- */
- public static final int UNKNOWN_TYPE = 0;
- /**
- * @deprecated This type is no longer used.
- */
- @Deprecated
- public static final int ANCHOR_TYPE = 1;
- /**
- * HitTestResult for hitting a phone number.
- */
- public static final int PHONE_TYPE = 2;
- /**
- * HitTestResult for hitting a map address.
- */
- public static final int GEO_TYPE = 3;
- /**
- * HitTestResult for hitting an email address.
- */
- public static final int EMAIL_TYPE = 4;
- /**
- * HitTestResult for hitting an HTML::img tag.
- */
- public static final int IMAGE_TYPE = 5;
- /**
- * @deprecated This type is no longer used.
- */
- @Deprecated
- public static final int IMAGE_ANCHOR_TYPE = 6;
- /**
- * HitTestResult for hitting a HTML::a tag with src=http.
- */
- public static final int SRC_ANCHOR_TYPE = 7;
- /**
- * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
- */
- public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
- /**
- * HitTestResult for hitting an edit text area.
- */
- public static final int EDIT_TEXT_TYPE = 9;
-
- private int mType;
- private String mExtra;
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public HitTestResult() {
- mType = UNKNOWN_TYPE;
- }
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public void setType(int type) {
- mType = type;
- }
-
- /**
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public void setExtra(String extra) {
- mExtra = extra;
- }
-
- /**
- * Gets the type of the hit test result. See the XXX_TYPE constants
- * defined in this class.
- *
- * @return the type of the hit test result
- */
- public int getType() {
- return mType;
- }
-
- /**
- * Gets additional type-dependant information about the result. See
- * {@link WebView#getHitTestResult()} for details. May either be {@code null}
- * or contain extra information about this result.
- *
- * @return additional type-dependant information about the result
- */
- public String getExtra() {
- return mExtra;
- }
- }
-
- /**
- * Constructs a new WebView with a Context object.
- *
- * @param context a Context object used to access application assets
+ * Construct a new WebView with a Context object.
+ * @param context A Context object used to access application assets.
*/
public WebView(Context context) {
this(context, null);
}
/**
- * Constructs a new WebView with layout parameters.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
+ * Construct a new WebView with layout parameters.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
*/
- public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
+ public WebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
}
-
- /**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param defStyleRes a resource identifier of a style resource that
- * supplies default values for the view, used only if
- * defStyleAttr is 0 or can not be found in the theme. Can be 0
- * to not look for defaults.
- */
- public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- this(context, attrs, defStyleAttr, defStyleRes, null, false);
- }
-
- /**
- * Constructs a new WebView with layout parameters and a default style.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param privateBrowsing whether this WebView will be initialized in
- * private mode
- *
- * @deprecated Private browsing is no longer supported directly via
- * WebView and will be removed in a future release. Prefer using
- * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
- * and {@link WebStorage} for fine-grained control of privacy data.
- */
- @Deprecated
- public WebView(Context context, AttributeSet attrs, int defStyleAttr,
- boolean privateBrowsing) {
- this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
- }
-
- /**
- * Constructs a new WebView with layout parameters, a default style and a set
- * of custom JavaScript interfaces to be added to this WebView at initialization
- * time. This guarantees that these interfaces will be available when the JS
- * context is initialized.
- *
- * @param context a Context object used to access application assets
- * @param attrs an AttributeSet passed to our parent
- * @param defStyleAttr an attribute in the current theme that contains a
- * reference to a style resource that supplies default values for
- * the view. Can be 0 to not look for defaults.
- * @param javaScriptInterfaces a Map of interface names, as keys, and
- * object implementing those interfaces, as
- * values
- * @param privateBrowsing whether this WebView will be initialized in
- * private mode
- * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
- * be added synchronously, before a subsequent loadUrl call takes effect.
- */
- protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
- Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
- }
-
- /**
- * @hide
- */
- @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
- protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
- Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- super(context, attrs, defStyleAttr, defStyleRes);
-
- // WebView is important by default, unless app developer overrode attribute.
- if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
- setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
- }
-
- if (context == null) {
- throw new IllegalArgumentException("Invalid context argument");
- }
- sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
- Build.VERSION_CODES.JELLY_BEAN_MR2;
- checkThread();
-
- ensureProviderCreated();
- mProvider.init(javaScriptInterfaces, privateBrowsing);
- // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
- CookieSyncManager.setGetInstanceIsAllowed();
- }
-
- /**
- * Specifies whether the horizontal scrollbar has overlay style.
- *
- * @deprecated This method has no effect.
- * @param overlay {@code true} if horizontal scrollbar should have overlay style
- */
- @Deprecated
+
+ // START FAKE PUBLIC METHODS
+
public void setHorizontalScrollbarOverlay(boolean overlay) {
}
- /**
- * Specifies whether the vertical scrollbar has overlay style.
- *
- * @deprecated This method has no effect.
- * @param overlay {@code true} if vertical scrollbar should have overlay style
- */
- @Deprecated
public void setVerticalScrollbarOverlay(boolean overlay) {
}
- /**
- * Gets whether horizontal scrollbar has overlay style.
- *
- * @deprecated This method is now obsolete.
- * @return {@code true}
- */
- @Deprecated
public boolean overlayHorizontalScrollbar() {
- // The old implementation defaulted to true, so return true for consistency
- return true;
+ return false;
}
- /**
- * Gets whether vertical scrollbar has overlay style.
- *
- * @deprecated This method is now obsolete.
- * @return {@code false}
- */
- @Deprecated
public boolean overlayVerticalScrollbar() {
- // The old implementation defaulted to false, so return false for consistency
return false;
}
- /**
- * Gets the visible height (in pixels) of the embedded title bar (if any).
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public int getVisibleTitleHeight() {
- checkThread();
- return mProvider.getVisibleTitleHeight();
- }
-
- /**
- * Gets the SSL certificate for the main top-level page or {@code null} if there is
- * no certificate (the site is not secure).
- *
- * @return the SSL certificate for the main top-level page
- */
- public SslCertificate getCertificate() {
- checkThread();
- return mProvider.getCertificate();
- }
-
- /**
- * Sets the SSL certificate for the main top-level page.
- *
- * @deprecated Calling this function has no useful effect, and will be
- * ignored in future releases.
- */
- @Deprecated
- public void setCertificate(SslCertificate certificate) {
- checkThread();
- mProvider.setCertificate(certificate);
- }
-
- //-------------------------------------------------------------------------
- // Methods called by activity
- //-------------------------------------------------------------------------
-
- /**
- * Sets a username and password pair for the specified host. This data is
- * used by the WebView to autocomplete username and password fields in web
- * forms. Note that this is unrelated to the credentials used for HTTP
- * authentication.
- *
- * @param host the host that required the credentials
- * @param username the username for the given host
- * @param password the password for the given host
- * @see WebViewDatabase#clearUsernamePassword
- * @see WebViewDatabase#hasUsernamePassword
- * @deprecated Saving passwords in WebView will not be supported in future versions.
- */
- @Deprecated
public void savePassword(String host, String username, String password) {
- checkThread();
- mProvider.savePassword(host, username, password);
}
- /**
- * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
- * instance.
- *
- * @param host the host to which the credentials apply
- * @param realm the realm to which the credentials apply
- * @param username the username
- * @param password the password
- * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
- */
- @Deprecated
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
- checkThread();
- mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
}
- /**
- * Retrieves HTTP authentication credentials for a given host and realm from the {@link
- * WebViewDatabase} instance.
- * @param host the host to which the credentials apply
- * @param realm the realm to which the credentials apply
- * @return the credentials as a String array, if found. The first element
- * is the username and the second element is the password. {@code null} if
- * no credentials are found.
- * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
- */
- @Deprecated
public String[] getHttpAuthUsernamePassword(String host, String realm) {
- checkThread();
- return mProvider.getHttpAuthUsernamePassword(host, realm);
+ return null;
}
- /**
- * Destroys the internal state of this WebView. This method should be called
- * after this WebView has been removed from the view system. No other
- * methods may be called on this WebView after destroy.
- */
public void destroy() {
- checkThread();
- mProvider.destroy();
}
- /**
- * Enables platform notifications of data state and proxy changes.
- * Notifications are enabled by default.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
public static void enablePlatformNotifications() {
- // noop
}
- /**
- * Disables platform notifications of data state and proxy changes.
- * Notifications are enabled by default.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
public static void disablePlatformNotifications() {
- // noop
}
- /**
- * Used only by internal tests to free up memory.
- *
- * @hide
- */
- public static void freeMemoryForTests() {
- getFactory().getStatics().freeMemoryForTests();
- }
-
- /**
- * Informs WebView of the network state. This is used to set
- * the JavaScript property window.navigator.isOnline and
- * generates the online/offline event as specified in HTML5, sec. 5.7.7
- *
- * @param networkUp a boolean indicating if network is available
- */
- public void setNetworkAvailable(boolean networkUp) {
- checkThread();
- mProvider.setNetworkAvailable(networkUp);
- }
-
- /**
- * Saves the state of this WebView used in
- * {@link android.app.Activity#onSaveInstanceState}. Please note that this
- * method no longer stores the display data for this WebView. The previous
- * behavior could potentially leak files if {@link #restoreState} was never
- * called.
- *
- * @param outState the Bundle to store this WebView's state
- * @return the same copy of the back/forward list used to save the state. If
- * saveState fails, the returned list will be {@code null}.
- */
- public WebBackForwardList saveState(Bundle outState) {
- checkThread();
- return mProvider.saveState(outState);
- }
-
- /**
- * Saves the current display data to the Bundle given. Used in conjunction
- * with {@link #saveState}.
- * @param b a Bundle to store the display data
- * @param dest the file to store the serialized picture data. Will be
- * overwritten with this WebView's picture data.
- * @return {@code true} if the picture was successfully saved
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public boolean savePicture(Bundle b, final File dest) {
- checkThread();
- return mProvider.savePicture(b, dest);
- }
-
- /**
- * Restores the display data that was saved in {@link #savePicture}. Used in
- * conjunction with {@link #restoreState}. Note that this will not work if
- * this WebView is hardware accelerated.
- *
- * @param b a Bundle containing the saved display data
- * @param src the file where the picture data was stored
- * @return {@code true} if the picture was successfully restored
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public boolean restorePicture(Bundle b, File src) {
- checkThread();
- return mProvider.restorePicture(b, src);
- }
-
- /**
- * Restores the state of this WebView from the given Bundle. This method is
- * intended for use in {@link android.app.Activity#onRestoreInstanceState}
- * and should be called to restore the state of this WebView. If
- * it is called after this WebView has had a chance to build state (load
- * pages, create a back/forward list, etc.) there may be undesirable
- * side-effects. Please note that this method no longer restores the
- * display data for this WebView.
- *
- * @param inState the incoming Bundle of state
- * @return the restored back/forward list or {@code null} if restoreState failed
- */
- public WebBackForwardList restoreState(Bundle inState) {
- checkThread();
- return mProvider.restoreState(inState);
- }
-
- /**
- * Loads the given URL with the specified additional HTTP headers.
- * <p>
- * Also see compatibility note on {@link #evaluateJavascript}.
- *
- * @param url the URL of the resource to load
- * @param additionalHttpHeaders the additional headers to be used in the
- * HTTP request for this URL, specified as a map from name to
- * value. Note that if this map contains any of the headers
- * that are set by default by this WebView, such as those
- * controlling caching, accept types or the User-Agent, their
- * values may be overridden by this WebView's defaults.
- */
- public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
- checkThread();
- mProvider.loadUrl(url, additionalHttpHeaders);
- }
-
- /**
- * Loads the given URL.
- * <p>
- * Also see compatibility note on {@link #evaluateJavascript}.
- *
- * @param url the URL of the resource to load
- */
public void loadUrl(String url) {
- checkThread();
- mProvider.loadUrl(url);
- }
-
- /**
- * Loads the URL with postData using "POST" method into this WebView. If url
- * is not a network URL, it will be loaded with {@link #loadUrl(String)}
- * instead, ignoring the postData param.
- *
- * @param url the URL of the resource to load
- * @param postData the data will be passed to "POST" request, which must be
- * be "application/x-www-form-urlencoded" encoded.
- */
- public void postUrl(String url, byte[] postData) {
- checkThread();
- if (URLUtil.isNetworkUrl(url)) {
- mProvider.postUrl(url, postData);
- } else {
- mProvider.loadUrl(url);
- }
}
- /**
- * Loads the given data into this WebView using a 'data' scheme URL.
- * <p>
- * Note that JavaScript's same origin policy means that script running in a
- * page loaded using this method will be unable to access content loaded
- * using any scheme other than 'data', including 'http(s)'. To avoid this
- * restriction, use {@link
- * #loadDataWithBaseURL(String,String,String,String,String)
- * loadDataWithBaseURL()} with an appropriate base URL.
- * <p>
- * The encoding parameter specifies whether the data is base64 or URL
- * encoded. If the data is base64 encoded, the value of the encoding
- * parameter must be 'base64'. For all other values of the parameter,
- * including {@code null}, it is assumed that the data uses ASCII encoding for
- * octets inside the range of safe URL characters and use the standard %xx
- * hex encoding of URLs for octets outside that range. For example, '#',
- * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
- * <p>
- * The 'data' scheme URL formed by this method uses the default US-ASCII
- * charset. If you need need to set a different charset, you should form a
- * 'data' scheme URL which explicitly specifies a charset parameter in the
- * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
- * Note that the charset obtained from the mediatype portion of a data URL
- * always overrides that specified in the HTML or XML document itself.
- *
- * @param data a String of data in the given encoding
- * @param mimeType the MIME type of the data, e.g. 'text/html'
- * @param encoding the encoding of the data
- */
public void loadData(String data, String mimeType, String encoding) {
- checkThread();
- mProvider.loadData(data, mimeType, encoding);
}
- /**
- * Loads the given data into this WebView, using baseUrl as the base URL for
- * the content. The base URL is used both to resolve relative URLs and when
- * applying JavaScript's same origin policy. The historyUrl is used for the
- * history entry.
- * <p>
- * Note that content specified in this way can access local device files
- * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
- * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
- * <p>
- * If the base URL uses the data scheme, this method is equivalent to
- * calling {@link #loadData(String,String,String) loadData()} and the
- * historyUrl is ignored, and the data will be treated as part of a data: URL.
- * If the base URL uses any other scheme, then the data will be loaded into
- * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
- * entities in the string will not be decoded.
- * <p>
- * Note that the baseUrl is sent in the 'Referer' HTTP header when
- * requesting subresources (images, etc.) of the page loaded using this method.
- *
- * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
- * 'about:blank'.
- * @param data a String of data in the given encoding
- * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
- * defaults to 'text/html'.
- * @param encoding the encoding of the data
- * @param historyUrl the URL to use as the history entry. If {@code null} defaults
- * to 'about:blank'. If non-null, this must be a valid URL.
- */
public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String historyUrl) {
- checkThread();
- mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
+ String mimeType, String encoding, String failUrl) {
}
- /**
- * Asynchronously evaluates JavaScript in the context of the currently displayed page.
- * If non-null, |resultCallback| will be invoked with any result returned from that
- * execution. This method must be called on the UI thread and the callback will
- * be made on the UI thread.
- * <p>
- * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
- * later, JavaScript state from an empty WebView is no longer persisted across navigations like
- * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
- * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
- * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
- *
- * @param script the JavaScript to execute.
- * @param resultCallback A callback to be invoked when the script execution
- * completes with the result of the execution (if any).
- * May be {@code null} if no notification of the result is required.
- */
- public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
- checkThread();
- mProvider.evaluateJavaScript(script, resultCallback);
- }
-
- /**
- * Saves the current view as a web archive.
- *
- * @param filename the filename where the archive should be placed
- */
- public void saveWebArchive(String filename) {
- checkThread();
- mProvider.saveWebArchive(filename);
- }
-
- /**
- * Saves the current view as a web archive.
- *
- * @param basename the filename where the archive should be placed
- * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
- * is assumed to be a directory in which a filename will be
- * chosen according to the URL of the current page.
- * @param callback called after the web archive has been saved. The
- * parameter for onReceiveValue will either be the filename
- * under which the file was saved, or {@code null} if saving the
- * file failed.
- */
- public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
- checkThread();
- mProvider.saveWebArchive(basename, autoname, callback);
- }
-
- /**
- * Stops the current load.
- */
public void stopLoading() {
- checkThread();
- mProvider.stopLoading();
}
- /**
- * Reloads the current URL.
- */
public void reload() {
- checkThread();
- mProvider.reload();
}
- /**
- * Gets whether this WebView has a back history item.
- *
- * @return {@code true} iff this WebView has a back history item
- */
public boolean canGoBack() {
- checkThread();
- return mProvider.canGoBack();
+ return false;
}
- /**
- * Goes back in the history of this WebView.
- */
public void goBack() {
- checkThread();
- mProvider.goBack();
}
- /**
- * Gets whether this WebView has a forward history item.
- *
- * @return {@code true} iff this WebView has a forward history item
- */
public boolean canGoForward() {
- checkThread();
- return mProvider.canGoForward();
+ return false;
}
- /**
- * Goes forward in the history of this WebView.
- */
public void goForward() {
- checkThread();
- mProvider.goForward();
}
- /**
- * Gets whether the page can go back or forward the given
- * number of steps.
- *
- * @param steps the negative or positive number of steps to move the
- * history
- */
public boolean canGoBackOrForward(int steps) {
- checkThread();
- return mProvider.canGoBackOrForward(steps);
+ return false;
}
- /**
- * Goes to the history item that is the number of steps away from
- * the current item. Steps is negative if backward and positive
- * if forward.
- *
- * @param steps the number of steps to take back or forward in the back
- * forward list
- */
public void goBackOrForward(int steps) {
- checkThread();
- mProvider.goBackOrForward(steps);
- }
-
- /**
- * Gets whether private browsing is enabled in this WebView.
- */
- public boolean isPrivateBrowsingEnabled() {
- checkThread();
- return mProvider.isPrivateBrowsingEnabled();
}
- /**
- * Scrolls the contents of this WebView up by half the view size.
- *
- * @param top {@code true} to jump to the top of the page
- * @return {@code true} if the page was scrolled
- */
public boolean pageUp(boolean top) {
- checkThread();
- return mProvider.pageUp(top);
+ return false;
}
-
- /**
- * Scrolls the contents of this WebView down by half the page size.
- *
- * @param bottom {@code true} to jump to bottom of page
- * @return {@code true} if the page was scrolled
- */
+
public boolean pageDown(boolean bottom) {
- checkThread();
- return mProvider.pageDown(bottom);
- }
-
- /**
- * Posts a {@link VisualStateCallback}, which will be called when
- * the current state of the WebView is ready to be drawn.
- *
- * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
- * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. 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.
- *
- * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
- * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
- * contain updates applied after the callback was posted.
- *
- * <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>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>When using this API it is also recommended to enable pre-rasterization if the {@link
- * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
- * more details and do consider its caveats.
- *
- * @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 postVisualStateCallback(long requestId, VisualStateCallback callback) {
- checkThread();
- mProvider.insertVisualStateCallback(requestId, callback);
+ return false;
}
- /**
- * 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
- * and release page resources (including any running JavaScript).
- */
- @Deprecated
public void clearView() {
- checkThread();
- mProvider.clearView();
}
-
- /**
- * Gets a new picture that captures the current contents of this WebView.
- * The picture is of the entire document being displayed, and is not
- * limited to the area currently displayed by this WebView. Also, the
- * picture is a static copy and is unaffected by later changes to the
- * content being displayed.
- * <p>
- * Note that due to internal changes, for API levels between
- * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
- * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
- * picture does not include fixed position elements or scrollable divs.
- * <p>
- * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
- * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
- * additional conversion at a cost in memory and performance. Also the
- * {@link android.graphics.Picture#createFromStream} and
- * {@link android.graphics.Picture#writeToStream} methods are not supported on the
- * returned object.
- *
- * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
- * {@link #saveWebArchive} to save the content to a file.
- *
- * @return a picture that captures the current contents of this WebView
- */
- @Deprecated
+
public Picture capturePicture() {
- checkThread();
- return mProvider.capturePicture();
- }
-
- /**
- * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
- * to provide a print document name.
- */
- @Deprecated
- public PrintDocumentAdapter createPrintDocumentAdapter() {
- checkThread();
- return mProvider.createPrintDocumentAdapter("default");
+ return null;
}
- /**
- * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
- *
- * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
- * be drawn during the conversion process - any such draws are undefined. It is recommended
- * to use a dedicated off screen WebView for the printing. If necessary, an application may
- * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
- * wrapped around the object returned and observing the onStart and onFinish methods. See
- * {@link android.print.PrintDocumentAdapter} for more information.
- *
- * @param documentName The user-facing name of the printed document. See
- * {@link android.print.PrintDocumentInfo}
- */
- public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
- checkThread();
- return mProvider.createPrintDocumentAdapter(documentName);
- }
-
- /**
- * Gets the current scale of this WebView.
- *
- * @return the current scale
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- @ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
- checkThread();
- return mProvider.getScale();
+ return 0;
}
- /**
- * Sets the initial scale for this WebView. 0 means default.
- * The behavior for the default scale depends on the state of
- * {@link WebSettings#getUseWideViewPort()} and
- * {@link WebSettings#getLoadWithOverviewMode()}.
- * If the content fits into the WebView control by width, then
- * the zoom is set to 100%. For wide content, the behavior
- * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
- * If its value is {@code true}, the content will be zoomed out to be fit
- * by width into the WebView control, otherwise not.
- *
- * If initial scale is greater than 0, WebView starts with this value
- * as initial scale.
- * Please note that unlike the scale properties in the viewport meta tag,
- * this method doesn't take the screen density into account.
- *
- * @param scaleInPercent the initial scale in percent
- */
public void setInitialScale(int scaleInPercent) {
- checkThread();
- mProvider.setInitialScale(scaleInPercent);
}
- /**
- * Invokes the graphical zoom picker widget for this WebView. This will
- * result in the zoom widget appearing on the screen to control the zoom
- * level of this WebView.
- */
public void invokeZoomPicker() {
- checkThread();
- mProvider.invokeZoomPicker();
- }
-
- /**
- * Gets a HitTestResult based on the current cursor node. If a HTML::a
- * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
- * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
- * If the anchor does not have a URL or if it is a JavaScript URL, the type
- * will be UNKNOWN_TYPE and the URL has to be retrieved through
- * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
- * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
- * the "extra" field. A type of
- * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
- * a child node. If a phone number is found, the HitTestResult type is set
- * to PHONE_TYPE and the phone number is set in the "extra" field of
- * HitTestResult. If a map address is found, the HitTestResult type is set
- * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
- * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
- * and the email is set in the "extra" field of HitTestResult. Otherwise,
- * HitTestResult type is set to UNKNOWN_TYPE.
- */
- public HitTestResult getHitTestResult() {
- checkThread();
- return mProvider.getHitTestResult();
}
- /**
- * Requests the anchor or image element URL at the last tapped point.
- * If hrefMsg is {@code null}, this method returns immediately and does not
- * dispatch hrefMsg to its target. If the tapped point hits an image,
- * an anchor, or an image in an anchor, the message associates
- * strings in named keys in its data. The value paired with the key
- * may be an empty string.
- *
- * @param hrefMsg the message to be dispatched with the result of the
- * request. The message data contains three keys. "url"
- * returns the anchor's href attribute. "title" returns the
- * anchor's text. "src" returns the image's src attribute.
- */
public void requestFocusNodeHref(Message hrefMsg) {
- checkThread();
- mProvider.requestFocusNodeHref(hrefMsg);
}
- /**
- * Requests the URL of the image last touched by the user. msg will be sent
- * to its target with a String representing the URL as its object.
- *
- * @param msg the message to be dispatched with the result of the request
- * as the data member with "url" as key. The result can be {@code null}.
- */
public void requestImageRef(Message msg) {
- checkThread();
- mProvider.requestImageRef(msg);
}
- /**
- * Gets the URL for the current page. This is not always the same as the URL
- * passed to WebViewClient.onPageStarted because although the load for
- * that URL has begun, the current page may not have changed.
- *
- * @return the URL for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
public String getUrl() {
- checkThread();
- return mProvider.getUrl();
+ return null;
}
- /**
- * Gets the original URL for the current page. This is not always the same
- * as the URL passed to WebViewClient.onPageStarted because although the
- * load for that URL has begun, the current page may not have changed.
- * Also, there may have been redirects resulting in a different URL to that
- * originally requested.
- *
- * @return the URL that was originally requested for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
- public String getOriginalUrl() {
- checkThread();
- return mProvider.getOriginalUrl();
- }
-
- /**
- * Gets the title for the current page. This is the title of the current page
- * until WebViewClient.onReceivedTitle is called.
- *
- * @return the title for the current page
- */
- @ViewDebug.ExportedProperty(category = "webview")
public String getTitle() {
- checkThread();
- return mProvider.getTitle();
+ return null;
}
- /**
- * Gets the favicon for the current page. This is the favicon of the current
- * page until WebViewClient.onReceivedIcon is called.
- *
- * @return the favicon for the current page
- */
public Bitmap getFavicon() {
- checkThread();
- return mProvider.getFavicon();
+ return null;
}
- /**
- * Gets the touch icon URL for the apple-touch-icon <link> element, or
- * a URL on this site's server pointing to the standard location of a
- * touch icon.
- *
- * @hide
- */
- public String getTouchIconUrl() {
- return mProvider.getTouchIconUrl();
- }
-
- /**
- * Gets the progress for the current page.
- *
- * @return the progress for the current page between 0 and 100
- */
public int getProgress() {
- checkThread();
- return mProvider.getProgress();
+ return 0;
}
-
- /**
- * Gets the height of the HTML content.
- *
- * @return the height of the HTML content
- */
- @ViewDebug.ExportedProperty(category = "webview")
+
public int getContentHeight() {
- checkThread();
- return mProvider.getContentHeight();
- }
-
- /**
- * Gets the width of the HTML content.
- *
- * @return the width of the HTML content
- * @hide
- */
- @ViewDebug.ExportedProperty(category = "webview")
- public int getContentWidth() {
- return mProvider.getContentWidth();
+ return 0;
}
- /**
- * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
- * is a global requests, not restricted to just this WebView. This can be
- * useful if the application has been paused.
- */
public void pauseTimers() {
- checkThread();
- mProvider.pauseTimers();
}
- /**
- * Resumes all layout, parsing, and JavaScript timers for all WebViews.
- * This will resume dispatching all timers.
- */
public void resumeTimers() {
- checkThread();
- mProvider.resumeTimers();
- }
-
- /**
- * Does a best-effort attempt to pause any processing that can be paused
- * safely, such as animations and geolocation. Note that this call
- * does not pause JavaScript. To pause JavaScript globally, use
- * {@link #pauseTimers}.
- *
- * To resume WebView, call {@link #onResume}.
- */
- public void onPause() {
- checkThread();
- mProvider.onPause();
- }
-
- /**
- * Resumes a WebView after a previous call to {@link #onPause}.
- */
- public void onResume() {
- checkThread();
- mProvider.onResume();
- }
-
- /**
- * Gets whether this WebView is paused, meaning onPause() was called.
- * Calling onResume() sets the paused state back to {@code false}.
- *
- * @hide
- */
- public boolean isPaused() {
- return mProvider.isPaused();
- }
-
- /**
- * Informs this WebView that memory is low so that it can free any available
- * memory.
- * @deprecated Memory caches are automatically dropped when no longer needed, and in response
- * to system memory pressure.
- */
- @Deprecated
- public void freeMemory() {
- checkThread();
- mProvider.freeMemory();
}
- /**
- * Clears the resource cache. Note that the cache is per-application, so
- * this will clear the cache for all WebViews used.
- *
- * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
- */
- public void clearCache(boolean includeDiskFiles) {
- checkThread();
- mProvider.clearCache(includeDiskFiles);
+ public void clearCache() {
}
- /**
- * Removes the autocomplete popup from the currently focused form field, if
- * present. Note this only affects the display of the autocomplete popup,
- * it does not remove any saved form data from this WebView's store. To do
- * that, use {@link WebViewDatabase#clearFormData}.
- */
public void clearFormData() {
- checkThread();
- mProvider.clearFormData();
}
- /**
- * Tells this WebView to clear its internal back/forward list.
- */
public void clearHistory() {
- checkThread();
- mProvider.clearHistory();
}
- /**
- * Clears the SSL preferences table stored in response to proceeding with
- * SSL certificate errors.
- */
public void clearSslPreferences() {
- checkThread();
- mProvider.clearSslPreferences();
- }
-
- /**
- * Clears the client certificate preferences stored in response
- * to proceeding/cancelling client cert requests. Note that WebView
- * automatically clears these preferences when it receives a
- * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
- * shared by all the WebViews that are created by the embedder application.
- *
- * @param onCleared A runnable to be invoked when client certs are cleared.
- * The embedder can pass {@code null} if not interested in the
- * callback. The runnable will be called in UI thread.
- */
- public static void clearClientCertPreferences(Runnable onCleared) {
- getFactory().getStatics().clearClientCertPreferences(onCleared);
- }
-
- /**
- * Starts Safe Browsing initialization.
- * <p>
- * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
- * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
- * devices {@code callback} will receive {@code false}.
- * <p>
- * This does not enable the Safe Browsing feature itself, and should only be called if Safe
- * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
- * prepares resources used for Safe Browsing.
- * <p>
- * This should be called with the Application Context (and will always use the Application
- * context to do its work regardless).
- *
- * @param context Application Context.
- * @param callback will be called on the UI thread with {@code true} if initialization is
- * successful, {@code false} otherwise.
- */
- public static void startSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
- getFactory().getStatics().initSafeBrowsing(context, callback);
- }
-
- /**
- * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
- * global for all the WebViews.
- * <p>
- * Each rule should take one of these:
- * <table>
- * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
- * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
- * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
- * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
- * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
- * </table>
- * <p>
- * All other rules, including wildcards, are invalid.
- *
- * @param urls the list of URLs
- * @param callback will be called with {@code true} if URLs are successfully added to the
- * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
- * be run on the UI thread
- */
- public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
- @Nullable ValueCallback<Boolean> callback) {
- getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
- }
-
- /**
- * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
- *
- * @return the url pointing to a privacy policy document which can be displayed to users.
- */
- @NonNull
- public static Uri getSafeBrowsingPrivacyPolicyUrl() {
- return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
- }
-
- /**
- * Gets the WebBackForwardList for this WebView. This contains the
- * back/forward list for use in querying each item in the history stack.
- * This is a copy of the private WebBackForwardList so it contains only a
- * snapshot of the current state. Multiple calls to this method may return
- * different objects. The object returned from this method will not be
- * updated to reflect any new state.
- */
- public WebBackForwardList copyBackForwardList() {
- checkThread();
- return mProvider.copyBackForwardList();
-
- }
-
- /**
- * Registers the listener to be notified as find-on-page operations
- * progress. This will replace the current listener.
- *
- * @param listener an implementation of {@link FindListener}
- */
- public void setFindListener(FindListener listener) {
- checkThread();
- setupFindListenerIfNeeded();
- mFindListener.mUserFindListener = listener;
- }
-
- /**
- * Highlights and scrolls to the next match found by
- * {@link #findAllAsync}, wrapping around page boundaries as necessary.
- * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
- * has not been called yet, or if {@link #clearMatches} has been called since the
- * last find operation, this function does nothing.
- *
- * @param forward the direction to search
- * @see #setFindListener
- */
- public void findNext(boolean forward) {
- checkThread();
- mProvider.findNext(forward);
- }
-
- /**
- * Finds all instances of find on the page and highlights them.
- * Notifies any registered {@link FindListener}.
- *
- * @param find the string to find
- * @return the number of occurrences of the String "find" that were found
- * @deprecated {@link #findAllAsync} is preferred.
- * @see #setFindListener
- */
- @Deprecated
- public int findAll(String find) {
- checkThread();
- StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
- return mProvider.findAll(find);
- }
-
- /**
- * Finds all instances of find on the page and highlights them,
- * asynchronously. Notifies any registered {@link FindListener}.
- * Successive calls to this will cancel any pending searches.
- *
- * @param find the string to find.
- * @see #setFindListener
- */
- public void findAllAsync(String find) {
- checkThread();
- mProvider.findAllAsync(find);
- }
-
- /**
- * Starts an ActionMode for finding text in this WebView. Only works if this
- * WebView is attached to the view system.
- *
- * @param text if non-null, will be the initial text to search for.
- * Otherwise, the last String searched for in this WebView will
- * be used to start.
- * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
- * If {@code false} and text is non-null, perform a find all.
- * @return {@code true} if the find dialog is shown, {@code false} otherwise
- * @deprecated This method does not work reliably on all Android versions;
- * implementing a custom find dialog using WebView.findAllAsync()
- * provides a more robust solution.
- */
- @Deprecated
- public boolean showFindDialog(String text, boolean showIme) {
- checkThread();
- return mProvider.showFindDialog(text, showIme);
}
- /**
- * Gets the first substring consisting of the address of a physical
- * location. Currently, only addresses in the United States are detected,
- * and consist of:
- * <ul>
- * <li>a house number</li>
- * <li>a street name</li>
- * <li>a street type (Road, Circle, etc), either spelled out or
- * abbreviated</li>
- * <li>a city name</li>
- * <li>a state or territory, either spelled out or two-letter abbr</li>
- * <li>an optional 5 digit or 9 digit zip code</li>
- * </ul>
- * All names must be correctly capitalized, and the zip code, if present,
- * must be valid for the state. The street type must be a standard USPS
- * spelling or abbreviation. The state or territory must also be spelled
- * or abbreviated using USPS standards. The house number may not exceed
- * five digits.
- *
- * @param addr the string to search for addresses
- * @return the address, or if no address is found, {@code null}
- */
public static String findAddress(String addr) {
- // TODO: Rewrite this in Java so it is not needed to start up chromium
- // Could also be deprecated
- return getFactory().getStatics().findAddress(addr);
- }
-
- /**
- * For apps targeting the L release, WebView has a new default behavior that reduces
- * memory footprint and increases performance by intelligently choosing
- * the portion of the HTML document that needs to be drawn. These
- * optimizations are transparent to the developers. However, under certain
- * circumstances, an App developer may want to disable them:
- * <ol>
- * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
- * of the page that is way outside the visible portion of the page.</li>
- * <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
- * Note that capturePicture is a deprecated API.</li>
- * </ol>
- * Enabling drawing the entire HTML document has a significant performance
- * cost. This method should be called before any WebViews are created.
- */
- public static void enableSlowWholeDocumentDraw() {
- getFactory().getStatics().enableSlowWholeDocumentDraw();
+ return null;
}
- /**
- * Clears the highlighting surrounding text matches created by
- * {@link #findAllAsync}.
- */
- public void clearMatches() {
- checkThread();
- mProvider.clearMatches();
- }
-
- /**
- * Queries the document to see if it contains any image references. The
- * message object will be dispatched with arg1 being set to 1 if images
- * were found and 0 if the document does not reference any images.
- *
- * @param response the message that will be dispatched with the result
- */
public void documentHasImages(Message response) {
- checkThread();
- mProvider.documentHasImages(response);
}
- /**
- * Sets the WebViewClient that will receive various notifications and
- * requests. This will replace the current handler.
- *
- * @param client an implementation of WebViewClient
- * @see #getWebViewClient
- */
public void setWebViewClient(WebViewClient client) {
- checkThread();
- mProvider.setWebViewClient(client);
- }
-
- /**
- * Gets the WebViewClient.
- *
- * @return the WebViewClient, or a default client if not yet set
- * @see #setWebViewClient
- */
- public WebViewClient getWebViewClient() {
- checkThread();
- return mProvider.getWebViewClient();
}
- /**
- * Registers the interface to be used when content can not be handled by
- * the rendering engine, and should be downloaded instead. This will replace
- * the current handler.
- *
- * @param listener an implementation of DownloadListener
- */
public void setDownloadListener(DownloadListener listener) {
- checkThread();
- mProvider.setDownloadListener(listener);
}
- /**
- * Sets the chrome handler. This is an implementation of WebChromeClient for
- * use in handling JavaScript dialogs, favicons, titles, and the progress.
- * This will replace the current handler.
- *
- * @param client an implementation of WebChromeClient
- * @see #getWebChromeClient
- */
public void setWebChromeClient(WebChromeClient client) {
- checkThread();
- mProvider.setWebChromeClient(client);
}
- /**
- * Gets the chrome handler.
- *
- * @return the WebChromeClient, or {@code null} if not yet set
- * @see #setWebChromeClient
- */
- public WebChromeClient getWebChromeClient() {
- checkThread();
- return mProvider.getWebChromeClient();
- }
-
- /**
- * Sets the Picture listener. This is an interface used to receive
- * notifications of a new Picture.
- *
- * @param listener an implementation of WebView.PictureListener
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public void setPictureListener(PictureListener listener) {
- checkThread();
- mProvider.setPictureListener(listener);
- }
-
- /**
- * Injects the supplied Java object into this WebView. The object is
- * injected into the JavaScript context of the main frame, using the
- * supplied name. This allows the Java object's methods to be
- * accessed from JavaScript. For applications targeted to API
- * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- * and above, only public methods that are annotated with
- * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
- * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
- * all public methods (including the inherited ones) can be accessed, see the
- * important security note below for implications.
- * <p> Note that injected objects will not appear in JavaScript until the page is next
- * (re)loaded. JavaScript should be enabled before injecting the object. For example:
- * <pre>
- * class JsObject {
- * {@literal @}JavascriptInterface
- * public String toString() { return "injectedObject"; }
- * }
- * webview.getSettings().setJavaScriptEnabled(true);
- * webView.addJavascriptInterface(new JsObject(), "injectedObject");
- * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
- * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
- * <p>
- * <strong>IMPORTANT:</strong>
- * <ul>
- * <li> This method can be used to allow JavaScript to control the host
- * application. This is a powerful feature, but also presents a security
- * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
- * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
- * are still vulnerable if the app runs on a device running Android earlier than 4.2.
- * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- * and to ensure the method is called only when running on Android 4.2 or later.
- * With these older versions, JavaScript could use reflection to access an
- * injected object's public fields. Use of this method in a WebView
- * containing untrusted content could allow an attacker to manipulate the
- * host application in unintended ways, executing Java code with the
- * permissions of the host application. Use extreme care when using this
- * method in a WebView which could contain untrusted content.</li>
- * <li> JavaScript interacts with Java object on a private, background
- * thread of this WebView. Care is therefore required to maintain thread
- * safety.
- * </li>
- * <li> The Java object's fields are not accessible.</li>
- * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
- * and above, methods of injected Java objects are enumerable from
- * JavaScript.</li>
- * </ul>
- *
- * @param object the Java object to inject into this WebView's JavaScript
- * context. {@code null} values are ignored.
- * @param name the name used to expose the object in JavaScript
- */
- public void addJavascriptInterface(Object object, String name) {
- checkThread();
- mProvider.addJavascriptInterface(object, name);
- }
-
- /**
- * Removes a previously injected Java object from this WebView. Note that
- * the removal will not be reflected in JavaScript until the page is next
- * (re)loaded. See {@link #addJavascriptInterface}.
- *
- * @param name the name used to expose the object in JavaScript
- */
- public void removeJavascriptInterface(String name) {
- checkThread();
- mProvider.removeJavascriptInterface(name);
- }
-
- /**
- * Creates a message channel to communicate with JS and returns the message
- * ports that represent the endpoints of this message channel. The HTML5 message
- * channel functionality is described
- * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
- * </a>
- *
- * <p>The returned message channels are entangled and already in started state.
- *
- * @return the two message ports that form the message channel.
- */
- public WebMessagePort[] createWebMessageChannel() {
- checkThread();
- return mProvider.createWebMessageChannel();
- }
-
- /**
- * Post a message to main frame. The embedded application can restrict the
- * messages to a certain target origin. See
- * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
- * HTML5 spec</a> for how target origin can be used.
- * <p>
- * A target origin can be set as a wildcard ("*"). However this is not recommended.
- * See the page above for security issues.
- *
- * @param message the WebMessage
- * @param targetOrigin the target origin.
- */
- public void postWebMessage(WebMessage message, Uri targetOrigin) {
- checkThread();
- mProvider.postMessageToMainFrame(message, targetOrigin);
- }
-
- /**
- * Gets the WebSettings object used to control the settings for this
- * WebView.
- *
- * @return a WebSettings object that can be used to control this WebView's
- * settings
- */
- public WebSettings getSettings() {
- checkThread();
- return mProvider.getSettings();
- }
-
- /**
- * Enables debugging of web contents (HTML / CSS / JavaScript)
- * loaded into any WebViews of this application. This flag can be enabled
- * in order to facilitate debugging of web layouts and JavaScript
- * code running inside WebViews. Please refer to WebView documentation
- * for the debugging guide.
- *
- * The default is {@code false}.
- *
- * @param enabled whether to enable web contents debugging
- */
- public static void setWebContentsDebuggingEnabled(boolean enabled) {
- getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
- }
-
- /**
- * Gets the list of currently loaded plugins.
- *
- * @return the list of currently loaded plugins
- * @deprecated This was used for Gears, which has been deprecated.
- * @hide
- */
- @Deprecated
- public static synchronized PluginList getPluginList() {
- return new PluginList();
- }
-
- /**
- * @deprecated This was used for Gears, which has been deprecated.
- * @hide
- */
- @Deprecated
- public void refreshPlugins(boolean reloadOpenPages) {
- checkThread();
- }
-
- /**
- * Puts this WebView into text selection mode. Do not rely on this
- * functionality; it will be deprecated in the future.
- *
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public void emulateShiftHeld() {
- checkThread();
- }
-
- /**
- * @deprecated WebView no longer needs to implement
- * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onChildViewAdded(View parent, View child) {}
-
- /**
- * @deprecated WebView no longer needs to implement
- * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onChildViewRemoved(View p, View child) {}
-
- /**
- * @deprecated WebView should not have implemented
- * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
- */
- @Override
- // Cannot add @hide as this can always be accessed via the interface.
- @Deprecated
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- }
-
- /**
- * @deprecated Only the default case, {@code true}, will be supported in a future version.
- */
- @Deprecated
- public void setMapTrackballToArrowKeys(boolean setMap) {
- checkThread();
- mProvider.setMapTrackballToArrowKeys(setMap);
+ public void addJavascriptInterface(Object obj, String interfaceName) {
}
-
- public void flingScroll(int vx, int vy) {
- checkThread();
- mProvider.flingScroll(vx, vy);
- }
-
- /**
- * Gets the zoom controls for this WebView, as a separate View. The caller
- * is responsible for inserting this View into the layout hierarchy.
- * <p/>
- * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
- * built-in zoom mechanisms for the WebView, as opposed to these separate
- * zoom controls. The built-in mechanisms are preferred and can be enabled
- * using {@link WebSettings#setBuiltInZoomControls}.
- *
- * @deprecated the built-in zoom mechanisms are preferred
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
- */
- @Deprecated
public View getZoomControls() {
- checkThread();
- return mProvider.getZoomControls();
+ return null;
}
- /**
- * Gets whether this WebView can be zoomed in.
- *
- * @return {@code true} if this WebView can be zoomed in
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- public boolean canZoomIn() {
- checkThread();
- return mProvider.canZoomIn();
- }
-
- /**
- * Gets whether this WebView can be zoomed out.
- *
- * @return {@code true} if this WebView can be zoomed out
- *
- * @deprecated This method is prone to inaccuracy due to race conditions
- * between the web rendering and UI threads; prefer
- * {@link WebViewClient#onScaleChanged}.
- */
- @Deprecated
- public boolean canZoomOut() {
- checkThread();
- return mProvider.canZoomOut();
- }
-
- /**
- * Performs a zoom operation in this WebView.
- *
- * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
- * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
- */
- public void zoomBy(float zoomFactor) {
- checkThread();
- if (zoomFactor < 0.01)
- throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
- if (zoomFactor > 100.0)
- throw new IllegalArgumentException("zoomFactor must be less than 100.");
- mProvider.zoomBy(zoomFactor);
- }
-
- /**
- * Performs zoom in in this WebView.
- *
- * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
- */
public boolean zoomIn() {
- checkThread();
- return mProvider.zoomIn();
+ return false;
}
- /**
- * Performs zoom out in this WebView.
- *
- * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
- */
public boolean zoomOut() {
- checkThread();
- return mProvider.zoomOut();
- }
-
- /**
- * @deprecated This method is now obsolete.
- * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
- */
- @Deprecated
- public void debugDump() {
- checkThread();
- }
-
- /**
- * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
- * @hide
- */
- @Override
- public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
- mProvider.dumpViewHierarchyWithProperties(out, level);
- }
-
- /**
- * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
- * @hide
- */
- @Override
- public View findHierarchyView(String className, int hashCode) {
- return mProvider.findHierarchyView(className, hashCode);
- }
-
- /** @hide */
- @IntDef({
- RENDERER_PRIORITY_WAIVED,
- RENDERER_PRIORITY_BOUND,
- RENDERER_PRIORITY_IMPORTANT
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RendererPriority {}
-
- /**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
- * {@link WebView} renderers will be strong targets for out of memory
- * killing.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_WAIVED = 0;
- /**
- * The renderer associated with this WebView is bound with
- * the default priority for services.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_BOUND = 1;
- /**
- * The renderer associated with this WebView is bound with
- * {@link Context#BIND_IMPORTANT}.
- *
- * Use with {@link #setRendererPriorityPolicy}.
- */
- public static final int RENDERER_PRIORITY_IMPORTANT = 2;
-
- /**
- * Set the renderer priority policy for this {@link WebView}. The
- * priority policy will be used to determine whether an out of
- * process renderer should be considered to be a target for OOM
- * killing.
- *
- * Because a renderer can be associated with more than one
- * WebView, the final priority it is computed as the maximum of
- * any attached WebViews. When a WebView is destroyed it will
- * cease to be considerered when calculating the renderer
- * priority. Once no WebViews remain associated with the renderer,
- * the priority of the renderer will be reduced to
- * {@link #RENDERER_PRIORITY_WAIVED}.
- *
- * The default policy is to set the priority to
- * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
- * and this should not be changed unless the caller also handles
- * renderer crashes with
- * {@link WebViewClient#onRenderProcessGone}. Any other setting
- * will result in WebView renderers being killed by the system
- * more aggressively than the application.
- *
- * @param rendererRequestedPriority the minimum priority at which
- * this WebView desires the renderer process to be bound.
- * @param waivedWhenNotVisible if {@code true}, this flag specifies that
- * when this WebView is not visible, it will be treated as
- * if it had requested a priority of
- * {@link #RENDERER_PRIORITY_WAIVED}.
- */
- public void setRendererPriorityPolicy(
- @RendererPriority int rendererRequestedPriority,
- boolean waivedWhenNotVisible) {
- mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
- }
-
- /**
- * Get the requested renderer priority for this WebView.
- *
- * @return the requested renderer priority policy.
- */
- @RendererPriority
- public int getRendererRequestedPriority() {
- return mProvider.getRendererRequestedPriority();
- }
-
- /**
- * Return whether this WebView requests a priority of
- * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
- *
- * @return whether this WebView requests a priority of
- * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
- */
- public boolean getRendererPriorityWaivedWhenNotVisible() {
- return mProvider.getRendererPriorityWaivedWhenNotVisible();
- }
-
- /**
- * Sets the {@link TextClassifier} for this WebView.
- */
- public void setTextClassifier(@Nullable TextClassifier textClassifier) {
- mProvider.setTextClassifier(textClassifier);
- }
-
- /**
- * Returns the {@link TextClassifier} used by this WebView.
- * If no TextClassifier has been set, this WebView uses the default set by the system.
- */
- @NonNull
- public TextClassifier getTextClassifier() {
- return mProvider.getTextClassifier();
- }
-
- //-------------------------------------------------------------------------
- // Interface for WebView providers
- //-------------------------------------------------------------------------
-
- /**
- * Gets the WebViewProvider. Used by providers to obtain the underlying
- * implementation, e.g. when the application responds to
- * WebViewClient.onCreateWindow() request.
- *
- * @hide WebViewProvider is not public API.
- */
- @SystemApi
- public WebViewProvider getWebViewProvider() {
- return mProvider;
- }
-
- /**
- * Callback interface, allows the provider implementation to access non-public methods
- * and fields, and make super-class calls in this WebView instance.
- * @hide Only for use by WebViewProvider implementations
- */
- @SystemApi
- public class PrivateAccess {
- // ---- Access to super-class methods ----
- public int super_getScrollBarStyle() {
- return WebView.super.getScrollBarStyle();
- }
-
- public void super_scrollTo(int scrollX, int scrollY) {
- WebView.super.scrollTo(scrollX, scrollY);
- }
-
- public void super_computeScroll() {
- WebView.super.computeScroll();
- }
-
- public boolean super_onHoverEvent(MotionEvent event) {
- return WebView.super.onHoverEvent(event);
- }
-
- public boolean super_performAccessibilityAction(int action, Bundle arguments) {
- return WebView.super.performAccessibilityActionInternal(action, arguments);
- }
-
- public boolean super_performLongClick() {
- return WebView.super.performLongClick();
- }
-
- public boolean super_setFrame(int left, int top, int right, int bottom) {
- return WebView.super.setFrame(left, top, right, bottom);
- }
-
- public boolean super_dispatchKeyEvent(KeyEvent event) {
- return WebView.super.dispatchKeyEvent(event);
- }
-
- public boolean super_onGenericMotionEvent(MotionEvent event) {
- return WebView.super.onGenericMotionEvent(event);
- }
-
- public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
- return WebView.super.requestFocus(direction, previouslyFocusedRect);
- }
-
- public void super_setLayoutParams(ViewGroup.LayoutParams params) {
- WebView.super.setLayoutParams(params);
- }
-
- public void super_startActivityForResult(Intent intent, int requestCode) {
- WebView.super.startActivityForResult(intent, requestCode);
- }
-
- // ---- Access to non-public methods ----
- public void overScrollBy(int deltaX, int deltaY,
- int scrollX, int scrollY,
- int scrollRangeX, int scrollRangeY,
- int maxOverScrollX, int maxOverScrollY,
- boolean isTouchEvent) {
- WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
- maxOverScrollX, maxOverScrollY, isTouchEvent);
- }
-
- public void awakenScrollBars(int duration) {
- WebView.this.awakenScrollBars(duration);
- }
-
- public void awakenScrollBars(int duration, boolean invalidate) {
- WebView.this.awakenScrollBars(duration, invalidate);
- }
-
- public float getVerticalScrollFactor() {
- return WebView.this.getVerticalScrollFactor();
- }
-
- public float getHorizontalScrollFactor() {
- return WebView.this.getHorizontalScrollFactor();
- }
-
- public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
- }
-
- public void onScrollChanged(int l, int t, int oldl, int oldt) {
- WebView.this.onScrollChanged(l, t, oldl, oldt);
- }
-
- public int getHorizontalScrollbarHeight() {
- return WebView.this.getHorizontalScrollbarHeight();
- }
-
- public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
- int l, int t, int r, int b) {
- WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
- }
-
- // ---- Access to (non-public) fields ----
- /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
- public void setScrollXRaw(int scrollX) {
- WebView.this.mScrollX = scrollX;
- }
-
- /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
- public void setScrollYRaw(int scrollY) {
- WebView.this.mScrollY = scrollY;
- }
-
- }
-
- //-------------------------------------------------------------------------
- // Package-private internal stuff
- //-------------------------------------------------------------------------
-
- // Only used by android.webkit.FindActionModeCallback.
- void setFindDialogFindListener(FindListener listener) {
- checkThread();
- setupFindListenerIfNeeded();
- mFindListener.mFindDialogFindListener = listener;
- }
-
- // Only used by android.webkit.FindActionModeCallback.
- void notifyFindDialogDismissed() {
- checkThread();
- mProvider.notifyFindDialogDismissed();
- }
-
- //-------------------------------------------------------------------------
- // Private internal stuff
- //-------------------------------------------------------------------------
-
- private WebViewProvider mProvider;
-
- /**
- * In addition to the FindListener that the user may set via the WebView.setFindListener
- * API, FindActionModeCallback will register it's own FindListener. We keep them separate
- * via this class so that the two FindListeners can potentially exist at once.
- */
- private class FindListenerDistributor implements FindListener {
- private FindListener mFindDialogFindListener;
- private FindListener mUserFindListener;
-
- @Override
- public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
- boolean isDoneCounting) {
- if (mFindDialogFindListener != null) {
- mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
- isDoneCounting);
- }
-
- if (mUserFindListener != null) {
- mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
- isDoneCounting);
- }
- }
- }
- private FindListenerDistributor mFindListener;
-
- private void setupFindListenerIfNeeded() {
- if (mFindListener == null) {
- mFindListener = new FindListenerDistributor();
- mProvider.setFindListener(mFindListener);
- }
- }
-
- private void ensureProviderCreated() {
- checkThread();
- if (mProvider == null) {
- // As this can get called during the base class constructor chain, pass the minimum
- // number of dependencies here; the rest are deferred to init().
- mProvider = getFactory().createWebView(this, new PrivateAccess());
- }
- }
-
- private static WebViewFactoryProvider getFactory() {
- return WebViewFactory.getProvider();
- }
-
- private final Looper mWebViewThread = Looper.myLooper();
-
- private void checkThread() {
- // Ignore mWebViewThread == null because this can be called during in the super class
- // constructor, before this class's own constructor has even started.
- if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
- Throwable throwable = new Throwable(
- "A WebView method was called on thread '" +
- Thread.currentThread().getName() + "'. " +
- "All WebView methods must be called on the same thread. " +
- "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
- ", FYI main Looper is " + Looper.getMainLooper() + ")");
- Log.w(LOGTAG, Log.getStackTraceString(throwable));
- StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
-
- if (sEnforceThreadChecking) {
- throw new RuntimeException(throwable);
- }
- }
- }
-
- //-------------------------------------------------------------------------
- // Override View methods
- //-------------------------------------------------------------------------
-
- // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
- // there's a corresponding override (or better, caller) for each of them in here.
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mProvider.getViewDelegate().onAttachedToWindow();
- }
-
- /** @hide */
- @Override
- protected void onDetachedFromWindowInternal() {
- mProvider.getViewDelegate().onDetachedFromWindow();
- super.onDetachedFromWindowInternal();
- }
-
- /** @hide */
- @Override
- public void onMovedToDisplay(int displayId, Configuration config) {
- mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
- }
-
- @Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- mProvider.getViewDelegate().setLayoutParams(params);
- }
-
- @Override
- public void setOverScrollMode(int mode) {
- super.setOverScrollMode(mode);
- // This method may be called in the constructor chain, before the WebView provider is
- // created.
- ensureProviderCreated();
- mProvider.getViewDelegate().setOverScrollMode(mode);
- }
-
- @Override
- public void setScrollBarStyle(int style) {
- mProvider.getViewDelegate().setScrollBarStyle(style);
- super.setScrollBarStyle(style);
- }
-
- @Override
- protected int computeHorizontalScrollRange() {
- return mProvider.getScrollDelegate().computeHorizontalScrollRange();
- }
-
- @Override
- protected int computeHorizontalScrollOffset() {
- return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- return mProvider.getScrollDelegate().computeVerticalScrollRange();
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- return mProvider.getScrollDelegate().computeVerticalScrollOffset();
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- return mProvider.getScrollDelegate().computeVerticalScrollExtent();
- }
-
- @Override
- public void computeScroll() {
- mProvider.getScrollDelegate().computeScroll();
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onHoverEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onTouchEvent(event);
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onGenericMotionEvent(event);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- return mProvider.getViewDelegate().onTrackballEvent(event);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
- }
-
- /*
- TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
- to be delegating them too.
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
- }
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
- }
- @Override
- public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
- }
- */
-
- @Override
- public AccessibilityNodeProvider getAccessibilityNodeProvider() {
- AccessibilityNodeProvider provider =
- mProvider.getViewDelegate().getAccessibilityNodeProvider();
- return provider == null ? super.getAccessibilityNodeProvider() : provider;
- }
-
- @Deprecated
- @Override
- public boolean shouldDelayChildPressedState() {
- return mProvider.getViewDelegate().shouldDelayChildPressedState();
- }
-
- @Override
- public CharSequence getAccessibilityClassName() {
- return WebView.class.getName();
- }
-
- @Override
- public void onProvideVirtualStructure(ViewStructure structure) {
- mProvider.getViewDelegate().onProvideVirtualStructure(structure);
- }
-
- /**
- * {@inheritDoc}
- *
- * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
- * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
- * understood by the {@link android.service.autofill.AutofillService} implementations:
- *
- * <ol>
- * <li>Only the HTML nodes inside a {@code FORM} are generated.
- * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
- * node representing the WebView.
- * <li>If a web page has multiple {@code FORM}s, only the data for the current form is
- * represented&mdash;if the user taps a field from another form, then the current autofill
- * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
- * a new context is created for that {@code FORM}.
- * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
- * the view structure until the user taps a field from a {@code FORM} inside the
- * {@code IFRAME}, in which case it would be treated the same way as multiple forms described
- * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
- * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
- * <li>If the Android SDK provides a similar View, then should be set with the
- * fully-qualified class name of such view.
- * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
- * {@link ViewStructure#setAutofillHints(String[])}.
- * <li>The {@code type} attribute of {@code INPUT} tags maps to
- * {@link ViewStructure#setInputType(int)}.
- * <li>The {@code value} attribute of {@code INPUT} tags maps to
- * {@link ViewStructure#setText(CharSequence)}.
- * <li>If the view is editalbe, the {@link ViewStructure#setAutofillType(int)} and
- * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
- * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
- * <li>Other HTML attributes can be represented through
- * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
- * </ol>
- *
- * <p>It should also call {@code structure.setDataIsSensitive(false)} for fields whose value
- * were not dynamically changed (for example, through Javascript).
- *
- * <p>Example1: an HTML form with 2 fields for username and password.
- *
- * <pre class="prettyprint">
- * &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
- * &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
- * </pre>
- *
- * <p>Would map to:
- *
- * <pre class="prettyprint">
- * int index = structure.addChildCount(2);
- * ViewStructure username = structure.newChild(index);
- * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
- * username.setClassName("input");
- * username.setInputType("android.widget.EditText");
- * username.setAutofillHints("username");
- * username.setHtmlInfo(username.newHtmlInfoBuilder("input")
- * .addAttribute("type", "text")
- * .addAttribute("name", "username")
- * .addAttribute("id", "user")
- * .build());
- * username.setHint("Email or username");
- * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
- * username.setAutofillValue(AutofillValue.forText("Type your username"));
- * username.setText("Type your username");
- * // Value of the field is not sensitive because it was not dynamically changed:
- * username.setDataIsSensitive(false);
- *
- * ViewStructure password = structure.newChild(index + 1);
- * username.setAutofillId(structure, 2); // id 2 - second child
- * password.setInputType("android.widget.EditText");
- * password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
- * password.setAutofillHints("current-password");
- * password.setHtmlInfo(password.newHtmlInfoBuilder("input")
- * .addAttribute("type", "password")
- * .addAttribute("name", "password")
- * .addAttribute("id", "pass")
- * .build());
- * password.setHint("Password");
- * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
- * </pre>
- *
- * <p>Example2: an IFRAME tag.
- *
- * <pre class="prettyprint">
- * &lt;iframe src="https://example.com/login"/&gt;
- * </pre>
- *
- * <p>Would map to:
- *
- * <pre class="prettyprint">
- * int index = structure.addChildCount(1);
- * ViewStructure iframe = structure.newChildFor(index);
- * iframe.setAutofillId(structure.getAutofillId(), 1);
- * iframe.setHtmlInfo(iframe.newHtmlInfoBuilder("iframe")
- * .addAttribute("src", "https://example.com/login")
- * .build());
- * </pre>
- */
- @Override
- public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
- mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
- }
-
- @Override
- public void autofill(SparseArray<AutofillValue>values) {
- mProvider.getViewDelegate().autofill(values);
- }
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfoInternal(info);
- mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
- }
-
- /** @hide */
- @Override
- public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
- super.onInitializeAccessibilityEventInternal(event);
- mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
- }
-
- /** @hide */
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
- }
-
- /** @hide */
- @Override
- protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
- int l, int t, int r, int b) {
- mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
- }
-
- @Override
- protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
- mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
- }
-
- @Override
- protected void onWindowVisibilityChanged(int visibility) {
- super.onWindowVisibilityChanged(visibility);
- mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mProvider.getViewDelegate().onDraw(canvas);
- }
-
- @Override
- public boolean performLongClick() {
- return mProvider.getViewDelegate().performLongClick();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- mProvider.getViewDelegate().onConfigurationChanged(newConfig);
- }
-
- /**
- * Creates a new InputConnection for an InputMethod to interact with the WebView.
- * This is similar to {@link View#onCreateInputConnection} but note that WebView
- * calls InputConnection methods on a thread other than the UI thread.
- * If these methods are overridden, then the overriding methods should respect
- * thread restrictions when calling View methods or accessing data.
- */
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- return mProvider.getViewDelegate().onDragEvent(event);
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- // This method may be called in the constructor chain, before the WebView provider is
- // created.
- ensureProviderCreated();
- mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
- super.onWindowFocusChanged(hasWindowFocus);
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- }
-
- /** @hide */
- @Override
- protected boolean setFrame(int left, int top, int right, int bottom) {
- return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int ow, int oh) {
- super.onSizeChanged(w, h, ow, oh);
- mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
- }
-
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return mProvider.getViewDelegate().dispatchKeyEvent(event);
- }
-
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
- return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
- }
-
- @Override
- public void setBackgroundColor(int color) {
- mProvider.getViewDelegate().setBackgroundColor(color);
- }
-
- @Override
- public void setLayerType(int layerType, Paint paint) {
- super.setLayerType(layerType, paint);
- mProvider.getViewDelegate().setLayerType(layerType, paint);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- mProvider.getViewDelegate().preDispatchDraw(canvas);
- super.dispatchDraw(canvas);
- }
-
- @Override
- public void onStartTemporaryDetach() {
- super.onStartTemporaryDetach();
- mProvider.getViewDelegate().onStartTemporaryDetach();
- }
-
- @Override
- public void onFinishTemporaryDetach() {
- super.onFinishTemporaryDetach();
- mProvider.getViewDelegate().onFinishTemporaryDetach();
- }
-
- @Override
- public Handler getHandler() {
- return mProvider.getViewDelegate().getHandler(super.getHandler());
- }
-
- @Override
- public View findFocus() {
- return mProvider.getViewDelegate().findFocus(super.findFocus());
- }
-
- /**
- * If WebView has already been loaded into the current process this method will return the
- * package that was used to load it. Otherwise, the package that would be used if the WebView
- * was loaded right now will be returned; this does not cause WebView to be loaded, so this
- * information may become outdated at any time.
- * The WebView package changes either when the current WebView package is updated, disabled, or
- * uninstalled. It can also be changed through a Developer Setting.
- * If the WebView package changes, any app process that has loaded WebView will be killed. The
- * next time the app starts and loads WebView it will use the new WebView package instead.
- * @return the current WebView package, or {@code null} if there is none.
- */
- public static PackageInfo getCurrentWebViewPackage() {
- PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
- if (webviewPackage != null) {
- return webviewPackage;
- }
-
- try {
- return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
- *
- * @param requestCode The integer request code originally supplied to
- * startActivityForResult(), allowing you to identify who this
- * result came from.
- * @param resultCode The integer result code returned by the child activity
- * through its setResult().
- * @param data An Intent, which can return result data to the caller
- * (various data can be attached to Intent "extras").
- * @hide
- */
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
- }
-
- /** @hide */
- @Override
- protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
- super.encodeProperties(encoder);
-
- checkThread();
- encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
- encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
- encoder.addProperty("webview:scale", mProvider.getScale());
- encoder.addProperty("webview:title", mProvider.getTitle());
- encoder.addProperty("webview:url", mProvider.getUrl());
- encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
+ return false;
}
}
diff --git a/android/widget/DatePickerCalendarDelegate.java b/android/widget/DatePickerCalendarDelegate.java
index 60b47572..e40023d0 100644
--- a/android/widget/DatePickerCalendarDelegate.java
+++ b/android/widget/DatePickerCalendarDelegate.java
@@ -22,11 +22,10 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
-import android.icu.text.SimpleDateFormat;
import android.icu.util.Calendar;
import android.os.Parcelable;
-import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.StateSet;
import android.view.HapticFeedbackConstants;
@@ -62,8 +61,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
com.android.internal.R.attr.disabledAlpha};
- private SimpleDateFormat mYearFormat;
- private SimpleDateFormat mMonthDayFormat;
+ private DateFormat mYearFormat;
+ private DateFormat mMonthDayFormat;
// Top-level container.
private ViewGroup mContainer;
@@ -273,19 +272,16 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
/**
* Listener called when the user clicks on a header item.
*/
- private final OnClickListener mOnHeaderClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- tryVibrate();
+ private final OnClickListener mOnHeaderClickListener = v -> {
+ tryVibrate();
- switch (v.getId()) {
- case R.id.date_picker_header_year:
- setCurrentView(VIEW_YEAR);
- break;
- case R.id.date_picker_header_date:
- setCurrentView(VIEW_MONTH_DAY);
- break;
- }
+ switch (v.getId()) {
+ case R.id.date_picker_header_year:
+ setCurrentView(VIEW_YEAR);
+ break;
+ case R.id.date_picker_header_date:
+ setCurrentView(VIEW_MONTH_DAY);
+ break;
}
};
@@ -299,10 +295,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
}
// Update the date formatter.
- final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd");
- mMonthDayFormat = new SimpleDateFormat(datePattern, locale);
+ mMonthDayFormat = DateFormat.getInstanceForSkeleton("EMMMd", locale);
mMonthDayFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
- mYearFormat = new SimpleDateFormat("y", locale);
+ mYearFormat = DateFormat.getInstanceForSkeleton("y", locale);
// Update the header text.
onCurrentDateChanged(false);
@@ -344,14 +339,11 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
case VIEW_YEAR:
final int year = mCurrentDate.get(Calendar.YEAR);
mYearPickerView.setYear(year);
- mYearPickerView.post(new Runnable() {
- @Override
- public void run() {
- mYearPickerView.requestFocus();
- final View selected = mYearPickerView.getSelectedView();
- if (selected != null) {
- selected.requestFocus();
- }
+ mYearPickerView.post(() -> {
+ mYearPickerView.requestFocus();
+ final View selected = mYearPickerView.getSelectedView();
+ if (selected != null) {
+ selected.requestFocus();
}
});
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index d23dfe46..0f617242 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -41,6 +41,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
@@ -1585,49 +1586,49 @@ public class Editor {
outText.startOffset = 0;
outText.selectionStart = mTextView.getSelectionStart();
outText.selectionEnd = mTextView.getSelectionEnd();
+ outText.hint = mTextView.getHint();
return true;
}
boolean reportExtractedText() {
final Editor.InputMethodState ims = mInputMethodState;
- if (ims != null) {
- final boolean contentChanged = ims.mContentChanged;
- if (contentChanged || ims.mSelectionModeChanged) {
- ims.mContentChanged = false;
- ims.mSelectionModeChanged = false;
- final ExtractedTextRequest req = ims.mExtractedTextRequest;
- if (req != null) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- if (TextView.DEBUG_EXTRACT) {
- Log.v(TextView.LOG_TAG, "Retrieving extracted start="
- + ims.mChangedStart
- + " end=" + ims.mChangedEnd
- + " delta=" + ims.mChangedDelta);
- }
- if (ims.mChangedStart < 0 && !contentChanged) {
- ims.mChangedStart = EXTRACT_NOTHING;
- }
- if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
- ims.mChangedDelta, ims.mExtractedText)) {
- if (TextView.DEBUG_EXTRACT) {
- Log.v(TextView.LOG_TAG,
- "Reporting extracted start="
- + ims.mExtractedText.partialStartOffset
- + " end=" + ims.mExtractedText.partialEndOffset
- + ": " + ims.mExtractedText.text);
- }
-
- imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
- ims.mChangedStart = EXTRACT_UNKNOWN;
- ims.mChangedEnd = EXTRACT_UNKNOWN;
- ims.mChangedDelta = 0;
- ims.mContentChanged = false;
- return true;
- }
- }
- }
- }
+ if (ims == null) {
+ return false;
+ }
+ ims.mSelectionModeChanged = false;
+ final ExtractedTextRequest req = ims.mExtractedTextRequest;
+ if (req == null) {
+ return false;
+ }
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm == null) {
+ return false;
+ }
+ if (TextView.DEBUG_EXTRACT) {
+ Log.v(TextView.LOG_TAG, "Retrieving extracted start="
+ + ims.mChangedStart
+ + " end=" + ims.mChangedEnd
+ + " delta=" + ims.mChangedDelta);
+ }
+ if (ims.mChangedStart < 0 && !ims.mContentChanged) {
+ ims.mChangedStart = EXTRACT_NOTHING;
+ }
+ if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
+ ims.mChangedDelta, ims.mExtractedText)) {
+ if (TextView.DEBUG_EXTRACT) {
+ Log.v(TextView.LOG_TAG,
+ "Reporting extracted start="
+ + ims.mExtractedText.partialStartOffset
+ + " end=" + ims.mExtractedText.partialEndOffset
+ + ": " + ims.mExtractedText.text);
+ }
+
+ imm.updateExtractedText(mTextView, req.token, ims.mExtractedText);
+ ims.mChangedStart = EXTRACT_UNKNOWN;
+ ims.mChangedEnd = EXTRACT_UNKNOWN;
+ ims.mChangedDelta = 0;
+ ims.mContentChanged = false;
+ return true;
}
return false;
}
@@ -3932,6 +3933,10 @@ public class Editor {
textClassification.getLabel())
.setIcon(textClassification.getIcon())
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ mMetricsLogger.write(
+ new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setSubtype(textClassification.getLogType()));
}
}
@@ -3973,6 +3978,9 @@ public class Editor {
.onClick(mTextView);
}
}
+ mMetricsLogger.action(
+ MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+ textClassification.getLogType());
stopTextActionMode();
return true;
}
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 4b6c4d35..efcc3a2f 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -5547,6 +5547,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public final void setHint(CharSequence hint) {
+ setHintInternal(hint);
+
+ if (isInputMethodTarget()) {
+ mEditor.reportExtractedText();
+ }
+ }
+
+ private void setHintInternal(CharSequence hint) {
mHint = TextUtils.stringOrSpannedString(hint);
if (mLayout != null) {
@@ -7644,6 +7652,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
MetaKeyKeyListener.stopSelecting(this, sp);
}
+
+ setHintInternal(text.hint);
}
/**
@@ -8433,7 +8443,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMaxMode != LINES) {
desired = Math.min(desired, mMaximum);
- } else if (cap && linecount > mMaximum && layout instanceof DynamicLayout) {
+ } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
+ || layout instanceof BoringLayout)) {
desired = layout.getLineTop(mMaximum);
if (dr != null) {