summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-30 17:25:37 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-30 17:25:37 -0400
commit46c77c203439b3b37c99d09e326df4b1fe08c10b (patch)
tree70d29abbfbb1106cd0830b33bc7e69e6fb151b1e /android
parent47ed54e5d312f899507d28d6e95ccc18a0de19fe (diff)
downloadandroid-28-46c77c203439b3b37c99d09e326df4b1fe08c10b.tar.gz
Import Android SDK Platform P [4423826]
/google/data/ro/projects/android/fetch_artifact \ --bid 4423826 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4423826.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I45f7bdc9b9c1cdcba75386623ae5f3ead6db4da8
Diffstat (limited to 'android')
-rw-r--r--android/accounts/AccountManagerPerfTest.java55
-rw-r--r--android/app/Activity.java3
-rw-r--r--android/app/ActivityManager.java66
-rw-r--r--android/app/AlarmManager.java23
-rw-r--r--android/app/Notification.java16
-rw-r--r--android/app/PendingIntent.java21
-rw-r--r--android/app/WindowConfiguration.java2
-rw-r--r--android/app/admin/DevicePolicyManager.java147
-rw-r--r--android/app/job/JobInfo.java88
-rw-r--r--android/app/job/JobParameters.java38
-rw-r--r--android/app/job/JobWorkItem.java35
-rw-r--r--android/app/slice/Slice.java22
-rw-r--r--android/app/slice/SliceProvider.java43
-rw-r--r--android/app/slice/SliceQuery.java21
-rw-r--r--android/app/slice/views/SliceView.java251
-rw-r--r--android/app/slice/widget/ActionRow.java (renamed from android/app/slice/views/ActionRow.java)2
-rw-r--r--android/app/slice/widget/GridView.java (renamed from android/app/slice/views/GridView.java)4
-rw-r--r--android/app/slice/widget/LargeSliceAdapter.java (renamed from android/app/slice/views/LargeSliceAdapter.java)8
-rw-r--r--android/app/slice/widget/LargeTemplateView.java (renamed from android/app/slice/views/LargeTemplateView.java)19
-rw-r--r--android/app/slice/widget/MessageView.java (renamed from android/app/slice/views/MessageView.java)4
-rw-r--r--android/app/slice/widget/RemoteInputView.java (renamed from android/app/slice/views/RemoteInputView.java)2
-rw-r--r--android/app/slice/widget/ShortcutView.java (renamed from android/app/slice/views/ShortcutView.java)10
-rw-r--r--android/app/slice/widget/SliceView.java402
-rw-r--r--android/app/slice/widget/SliceViewUtil.java (renamed from android/app/slice/views/SliceViewUtil.java)2
-rw-r--r--android/app/slice/widget/SmallTemplateView.java (renamed from android/app/slice/views/SmallTemplateView.java)6
-rw-r--r--android/app/usage/AppStandby.java83
-rw-r--r--android/app/usage/UsageStatsManager.java24
-rw-r--r--android/appwidget/AppWidgetHostView.java81
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/paging/ContiguousDataSource.java101
-rw-r--r--android/arch/paging/ContiguousPagedList.java25
-rw-r--r--android/arch/paging/DataSource.java4
-rw-r--r--android/arch/paging/KeyedDataSource.java64
-rw-r--r--android/arch/paging/ListDataSource.java39
-rw-r--r--android/arch/paging/LivePagedListBuilder.java159
-rw-r--r--android/arch/paging/LivePagedListProvider.java132
-rw-r--r--android/arch/paging/NullPaddedList.java140
-rw-r--r--android/arch/paging/PageResult.java8
-rw-r--r--android/arch/paging/PagedList.java158
-rw-r--r--android/arch/paging/PagedListAdapter.java27
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java18
-rw-r--r--android/arch/paging/PagedStorage.java4
-rw-r--r--android/arch/paging/PositionalDataSource.java34
-rw-r--r--android/arch/paging/SnapshotPagedList.java1
-rw-r--r--android/arch/paging/TiledDataSource.java14
-rw-r--r--android/arch/paging/TiledPagedList.java18
-rw-r--r--android/arch/paging/integration/testapp/PagedListItemAdapter.java2
-rw-r--r--android/arch/paging/integration/testapp/PagedListItemViewModel.java25
-rw-r--r--android/arch/paging/integration/testapp/PagedListSampleActivity.java10
-rw-r--r--android/arch/persistence/room/integration/testapp/CustomerViewModel.java34
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/UserDao.java67
-rw-r--r--android/arch/persistence/room/integration/testapp/database/CustomerDao.java9
-rw-r--r--android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java12
-rw-r--r--android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java (renamed from android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java)52
-rw-r--r--android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java185
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java179
-rw-r--r--android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java14
-rw-r--r--android/bluetooth/BluetoothGattCharacteristic.java2
-rw-r--r--android/bluetooth/BluetoothHidDevice.java71
-rw-r--r--android/bluetooth/BluetoothHidDeviceAppConfiguration.java11
-rw-r--r--android/bluetooth/BluetoothHidDeviceAppQosSettings.java24
-rw-r--r--android/bluetooth/BluetoothHidDeviceAppSdpSettings.java21
-rw-r--r--android/bluetooth/BluetoothHidDeviceCallback.java18
-rw-r--r--android/content/pm/ApplicationInfo.java106
-rw-r--r--android/content/pm/PackageManagerInternal.java9
-rw-r--r--android/content/pm/PackageParser.java40
-rw-r--r--android/content/res/Resources_Delegate.java20
-rw-r--r--android/hardware/location/ContextHubClient.java95
-rw-r--r--android/hardware/location/ContextHubClientCallback.java85
-rw-r--r--android/hardware/location/ContextHubManager.java121
-rw-r--r--android/hardware/location/ContextHubTransaction.java229
-rw-r--r--android/hardware/location/NanoAppBinary.java198
-rw-r--r--android/hardware/location/NanoAppMessage.java143
-rw-r--r--android/hardware/location/NanoAppState.java88
-rw-r--r--android/hardware/usb/UsbManager.java5
-rw-r--r--android/location/GnssClock.java3
-rw-r--r--android/net/NetworkCapabilities.java87
-rw-r--r--android/net/NetworkRequest.java22
-rw-r--r--android/net/metrics/DefaultNetworkEvent.java79
-rw-r--r--android/net/util/VersionedBroadcastListener.java109
-rw-r--r--android/net/wifi/WifiManager.java45
-rw-r--r--android/os/BatteryStats.java4
-rw-r--r--android/os/Binder.java276
-rw-r--r--android/os/IServiceManager.java20
-rw-r--r--android/os/Parcel.java16
-rw-r--r--android/os/ServiceManager.java105
-rw-r--r--android/os/ShellCommand.java37
-rw-r--r--android/os/StrictMode.java228
-rw-r--r--android/os/UserManager.java17
-rw-r--r--android/provider/Settings.java6
-rw-r--r--android/security/NetworkSecurityPolicy.java4
-rw-r--r--android/security/net/config/NetworkSecurityConfig.java6
-rw-r--r--android/service/autofill/CustomDescription.java4
-rw-r--r--android/service/autofill/FillResponse.java108
-rw-r--r--android/service/autofill/SaveInfo.java17
-rw-r--r--android/service/autofill/Transformation.java2
-rw-r--r--android/support/car/widget/CarRecyclerView.java62
-rw-r--r--android/support/design/widget/CoordinatorLayout.java4
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaBrowserConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaControllerConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java2
-rw-r--r--android/support/v17/leanback/app/BrowseFragment.java19
-rw-r--r--android/support/v17/leanback/app/BrowseSupportFragment.java19
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java51
-rw-r--r--android/support/v4/app/FragmentActivity.java6
-rw-r--r--android/support/v7/widget/TooltipPopup.java2
-rw-r--r--android/telephony/CarrierConfigManager.java15
-rw-r--r--android/telephony/CellIdentityCdma.java71
-rw-r--r--android/telephony/CellIdentityGsm.java181
-rw-r--r--android/telephony/CellIdentityLte.java171
-rw-r--r--android/telephony/CellIdentityWcdma.java174
-rw-r--r--android/telephony/DisconnectCause.java9
-rw-r--r--android/telephony/MbmsDownloadSession.java9
-rw-r--r--android/telephony/MbmsStreamingSession.java9
-rw-r--r--android/telephony/SmsManager.java15
-rw-r--r--android/telephony/SmsMessage.java20
-rw-r--r--android/telephony/mbms/MbmsUtils.java56
-rw-r--r--android/telephony/mbms/StreamingServiceInfo.java2
-rw-r--r--android/telephony/mbms/vendor/MbmsStreamingServiceBase.java2
-rw-r--r--android/text/Layout.java2
-rw-r--r--android/text/MeasuredText.java43
-rw-r--r--android/text/StaticLayout.java613
-rw-r--r--android/text/StaticLayout_Delegate.java140
-rw-r--r--android/text/TextLine.java38
-rw-r--r--android/text/TextUtils.java4
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/view/RectShadowPainter.java17
-rw-r--r--android/view/ShadowPainter.java6
-rw-r--r--android/view/SurfaceControl.java248
-rw-r--r--android/view/SurfaceView.java1142
-rw-r--r--android/view/TouchDelegate.java46
-rw-r--r--android/view/ViewGroup_Delegate.java9
-rw-r--r--android/view/ViewRootImpl.java42
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java86
-rw-r--r--android/view/textclassifier/TextClassification.java46
-rw-r--r--android/view/textclassifier/TextClassifier.java8
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/MimeTypeMap.java10
-rw-r--r--android/webkit/URLUtil.java26
-rw-r--r--android/webkit/WebView.java2878
-rw-r--r--android/webkit/WebViewFactory.java30
-rw-r--r--android/webkit/WebViewLibraryLoader.java232
-rw-r--r--android/widget/AdapterView.java10
-rw-r--r--android/widget/Editor.java162
-rw-r--r--android/widget/ListPopupWindow.java2
-rw-r--r--android/widget/PopupWindow.java4
-rw-r--r--android/widget/RemoteViews.java31
151 files changed, 11412 insertions, 2847 deletions
diff --git a/android/accounts/AccountManagerPerfTest.java b/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 00000000..b9411fa4
--- /dev/null
+++ b/android/accounts/AccountManagerPerfTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.accounts;
+
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testGetAccounts() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ fail("Missing required GET_ACCOUNTS permission");
+ }
+ AccountManager accountManager = AccountManager.get(context);
+ while (state.keepRunning()) {
+ accountManager.getAccounts();
+ }
+ }
+}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 85f73bb7..9d331a02 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -5867,10 +5867,11 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Returns complete component name of this activity.
+ * Returns the complete component name of this activity.
*
* @return Returns the complete component name for this activity
*/
+ @Override
public ComponentName getComponentName()
{
return mComponent;
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index fc4c8d7f..8d9dc1fa 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -46,6 +46,7 @@ import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.BatteryStats;
+import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -500,7 +501,7 @@ public class ActivityManager {
public static final int PROCESS_STATE_SERVICE = 11;
/** @hide Process is in the background running a receiver. Note that from the
- * perspective of oom_adj receivers run at a higher foreground level, but for our
+ * perspective of oom_adj, receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
public static final int PROCESS_STATE_RECEIVER = 12;
@@ -524,6 +525,20 @@ public class ActivityManager {
/** @hide Process does not exist. */
public static final int PROCESS_STATE_NONEXISTENT = 18;
+ // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
+ // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
+ // be updated to correctly map between them.
+ /**
+ * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum.
+ *
+ * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+ * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum.
+ * @hide
+ */
+ public static final int processStateAmToProto(int amInt) {
+ return amInt * 100;
+ }
+
/** @hide The lowest process state number */
public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
@@ -833,9 +848,8 @@ public class ActivityManager {
/**
* Returns true if this is a low-RAM device. Exactly whether a device is low-RAM
* is ultimately up to the device configuration, but currently it generally means
- * something in the class of a 512MB device with about a 800x480 or less screen.
- * This is mostly intended to be used by apps to determine whether they should turn
- * off certain features that require more RAM.
+ * something with 1GB or less of RAM. This is mostly intended to be used by apps
+ * to determine whether they should turn off certain features that require more RAM.
*/
public boolean isLowRamDevice() {
return isLowRamDeviceStatic();
@@ -1596,6 +1610,9 @@ public class ActivityManager {
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
+ if (maxNum < 0) {
+ throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+ }
return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1882,7 +1899,7 @@ public class ActivityManager {
public List<RunningTaskInfo> getRunningTasks(int maxNum)
throws SecurityException {
try {
- return getService().getTasks(maxNum, 0);
+ return getService().getTasks(maxNum);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3882,21 +3899,36 @@ public class ActivityManager {
IBinder service = ServiceManager.checkService(name);
if (service == null) {
pw.println(" (Service not found)");
+ pw.flush();
return;
}
- TransferPipe tp = null;
- try {
- pw.flush();
- tp = new TransferPipe();
- tp.setBufferPrefix(" ");
- service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
- tp.go(fd, 10000);
- } catch (Throwable e) {
- if (tp != null) {
- tp.kill();
+ pw.flush();
+ if (service instanceof Binder) {
+ // If this is a local object, it doesn't make sense to do an async dump with it,
+ // just directly dump.
+ try {
+ service.dump(fd, args);
+ } catch (Throwable e) {
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
+ pw.flush();
+ }
+ } else {
+ // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+ TransferPipe tp = null;
+ try {
+ pw.flush();
+ tp = new TransferPipe();
+ tp.setBufferPrefix(" ");
+ service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+ tp.go(fd, 10000);
+ } catch (Throwable e) {
+ if (tp != null) {
+ tp.kill();
+ }
+ pw.println("Failure dumping service:");
+ e.printStackTrace(pw);
}
- pw.println("Failure dumping service:");
- e.printStackTrace(pw);
}
}
diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java
index 2813e8b9..55f9e289 100644
--- a/android/app/AlarmManager.java
+++ b/android/app/AlarmManager.java
@@ -33,6 +33,7 @@ import android.os.WorkSource;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import libcore.util.ZoneInfoDB;
@@ -48,7 +49,7 @@ import java.lang.annotation.RetentionPolicy;
* if it is not already running. Registered alarms are retained while the
* device is asleep (and can optionally wake the device up if they go off
* during that time), but will be cleared if it is turned off and rebooted.
- *
+ *
* <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
* onReceive() method is executing. This guarantees that the phone will not sleep
* until you have finished handling the broadcast. Once onReceive() returns, the
@@ -296,7 +297,7 @@ public class AlarmManager {
* {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
* how many past alarm events have been accumulated into this intent
* broadcast. Recurring alarms that have gone undelivered because the
- * phone was asleep may have a count greater than one when delivered.
+ * phone was asleep may have a count greater than one when delivered.
*
* <div class="note">
* <p>
@@ -396,10 +397,10 @@ public class AlarmManager {
* set a recurring alarm for the top of every hour but the phone was asleep
* from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
* then the next alarm will be sent at 9:00.
- *
- * <p>If your application wants to allow the delivery times to drift in
+ *
+ * <p>If your application wants to allow the delivery times to drift in
* order to guarantee that at least a certain time interval always elapses
- * between alarms, then the approach to take is to use one-time alarms,
+ * between alarms, then the approach to take is to use one-time alarms,
* scheduling the next one yourself when handling each alarm delivery.
*
* <p class="note">
@@ -1056,7 +1057,7 @@ public class AlarmManager {
/**
* Creates a new alarm clock description.
*
- * @param triggerTime time at which the underlying alarm is triggered in wall time
+ * @param triggerTime time at which the underlying alarm is triggered in wall time
* milliseconds since the epoch
* @param showIntent an intent that can be used to show or edit details of
* the alarm clock.
@@ -1089,7 +1090,7 @@ public class AlarmManager {
* Returns an intent that can be used to show or edit details of the alarm clock in
* the application that scheduled it.
*
- * <p class="note">Beware that any application can retrieve and send this intent,
+ * <p class="note">Beware that any application can retrieve and send this intent,
* potentially with additional fields filled in. See
* {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
* PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
@@ -1121,5 +1122,13 @@ public class AlarmManager {
return new AlarmClockInfo[size];
}
};
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+ mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+ proto.end(token);
+ }
}
}
diff --git a/android/app/Notification.java b/android/app/Notification.java
index fee7d6c8..8226e0fb 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -858,7 +858,7 @@ public class Notification implements Parcelable
*
* @hide
*/
- static public IBinder whitelistToken;
+ private IBinder mWhitelistToken;
/**
* Must be set by a process to start associating tokens with Notification objects
@@ -1876,12 +1876,12 @@ public class Notification implements Parcelable
{
int version = parcel.readInt();
- whitelistToken = parcel.readStrongBinder();
- if (whitelistToken == null) {
- whitelistToken = processWhitelistToken;
+ mWhitelistToken = parcel.readStrongBinder();
+ if (mWhitelistToken == null) {
+ mWhitelistToken = processWhitelistToken;
}
// Propagate this token to all pending intents that are unmarshalled from the parcel.
- parcel.setClassCookie(PendingIntent.class, whitelistToken);
+ parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
when = parcel.readLong();
creationTime = parcel.readLong();
@@ -1989,7 +1989,7 @@ public class Notification implements Parcelable
* @hide
*/
public void cloneInto(Notification that, boolean heavy) {
- that.whitelistToken = this.whitelistToken;
+ that.mWhitelistToken = this.mWhitelistToken;
that.when = this.when;
that.creationTime = this.creationTime;
that.mSmallIcon = this.mSmallIcon;
@@ -2219,7 +2219,7 @@ public class Notification implements Parcelable
private void writeToParcelImpl(Parcel parcel, int flags) {
parcel.writeInt(1);
- parcel.writeStrongBinder(whitelistToken);
+ parcel.writeStrongBinder(mWhitelistToken);
parcel.writeLong(when);
parcel.writeLong(creationTime);
if (mSmallIcon == null && icon != 0) {
@@ -4981,6 +4981,8 @@ public class Notification implements Parcelable
mN.flags |= FLAG_SHOW_LIGHTS;
}
+ mN.allPendingIntents = null;
+
return mN;
}
diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java
index a25c2267..8b76cc7c 100644
--- a/android/app/PendingIntent.java
+++ b/android/app/PendingIntent.java
@@ -33,6 +33,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1081,7 +1082,16 @@ public final class PendingIntent implements Parcelable {
sb.append('}');
return sb.toString();
}
-
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ if (mTarget != null) {
+ proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString());
+ }
+ proto.end(token);
+ }
+
public int describeContents() {
return 0;
}
@@ -1119,8 +1129,13 @@ public final class PendingIntent implements Parcelable {
*/
public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender,
@NonNull Parcel out) {
- out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
- : null);
+ out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
+ if (sender != null) {
+ OnMarshaledListener listener = sOnMarshaledListener.get();
+ if (listener != null) {
+ listener.onMarshaled(sender, out, 0 /* flags */);
+ }
+ }
}
/**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 251863ca..de27b4fd 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -102,7 +102,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
public static final int ACTIVITY_TYPE_STANDARD = 1;
/** Home/Launcher activity type. */
public static final int ACTIVITY_TYPE_HOME = 2;
- /** Recents/Overview activity type. */
+ /** Recents/Overview activity type. There is only one activity with this type in the system. */
public static final int ACTIVITY_TYPE_RECENTS = 3;
/** Assistant activity type. */
public static final int ACTIVITY_TYPE_ASSISTANT = 4;
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index ab8edee7..772c6d60 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -1542,6 +1542,92 @@ public class DevicePolicyManager {
public @interface ProvisioningPreCondition {}
/**
+ * Disable all configurable SystemUI features during LockTask mode. This includes,
+ * <ul>
+ * <li>system info area in the status bar (connectivity icons, clock, etc.)
+ * <li>notifications (including alerts, icons, and the notification shade)
+ * <li>Home button
+ * <li>Recents button and UI
+ * <li>global actions menu (i.e. power button menu)
+ * <li>keyguard
+ * </ul>
+ *
+ * This is the default configuration for LockTask.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_NONE = 0;
+
+ /**
+ * Enable the system info area in the status bar during LockTask mode. The system info area
+ * usually occupies the right side of the status bar (although this can differ across OEMs). It
+ * includes all system information indicators, such as date and time, connectivity, battery,
+ * vibration mode, etc.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1;
+
+ /**
+ * Enable notifications during LockTask mode. This includes notification icons on the status
+ * bar, heads-up notifications, and the expandable notification shade. Note that the Quick
+ * Settings panel will still be disabled.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 1 << 1;
+
+ /**
+ * Enable the Home button during LockTask mode. Note that if a custom launcher is used, it has
+ * to be registered as the default launcher with
+ * {@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}, and its
+ * package needs to be whitelisted for LockTask with
+ * {@link #setLockTaskPackages(ComponentName, String[])}.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
+
+ /**
+ * Enable the Recents button and the Recents screen during LockTask mode.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+
+ /**
+ * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
+ * the user long-presses the power button, for example. Note that the user may not be able to
+ * power off the device if this flag is not set.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4;
+
+ /**
+ * Enable the keyguard during LockTask mode. Note that if the keyguard is already disabled with
+ * {@link #setKeyguardDisabled(ComponentName, boolean)}, setting this flag will have no effect.
+ * If this flag is not set, the keyguard will not be shown even if the user has a lock screen
+ * credential.
+ *
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5;
+
+ /**
+ * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO,
+ LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME,
+ LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+ LOCK_TASK_FEATURE_KEYGUARD})
+ public @interface LockTaskFeature {}
+
+ /**
* Service action: Action for a service that device owner and profile owner can optionally
* own. If a device owner or a profile owner has such a service, the system tries to keep
* a bound connection to it, in order to keep their process always running.
@@ -6484,6 +6570,61 @@ public class DevicePolicyManager {
}
/**
+ * Sets which system features to enable for LockTask mode.
+ * <p>
+ * Feature flags set through this method will only take effect for the duration when the device
+ * is in LockTask mode. If this method is not called, none of the features listed here will be
+ * enabled.
+ * <p>
+ * This function can only be called by the device owner or by a profile owner of a user/profile
+ * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features
+ * set via this method will be cleared if the user becomes unaffiliated.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param flags Bitfield of feature flags:
+ * {@link #LOCK_TASK_FEATURE_NONE} (default),
+ * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
+ * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
+ * {@link #LOCK_TASK_FEATURE_HOME},
+ * {@link #LOCK_TASK_FEATURE_RECENTS},
+ * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
+ * {@link #LOCK_TASK_FEATURE_KEYGUARD}
+ * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+ * an affiliated user or profile.
+ */
+ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
+ throwIfParentInstance("setLockTaskFeatures");
+ if (mService != null) {
+ try {
+ mService.setLockTaskFeatures(admin, flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Gets which system features are enabled for LockTask mode.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
+ * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+ * an affiliated user or profile.
+ * @see #setLockTaskFeatures(ComponentName, int)
+ */
+ public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) {
+ throwIfParentInstance("getLockTaskFeatures");
+ if (mService != null) {
+ try {
+ return mService.getLockTaskFeatures(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return 0;
+ }
+
+ /**
* Called by device owners to update {@link android.provider.Settings.Global} settings.
* Validation that the value of the setting is in the correct form for the setting type should
* be performed by the caller.
@@ -6901,6 +7042,12 @@ public class DevicePolicyManager {
* Called by device owner to disable the status bar. Disabling the status bar blocks
* notifications, quick settings and other screen overlays that allow escaping from a single use
* device.
+ * <p>
+ * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
+ * status bar in LockTask mode can be configured with
+ * {@link #setLockTaskFeatures(ComponentName, int)}. Calls to this method when the device is in
+ * LockTask mode will be registered, but will only take effect when the device leaves LockTask
+ * mode.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled {@code true} disables the status bar, {@code false} reenables it.
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 1434c9ba..b640bd5b 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@ package android.app.job;
import static android.util.TimeUtils.formatDuration;
+import android.annotation.BytesLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -71,6 +72,9 @@ public class JobInfo implements Parcelable {
/** This job requires metered connectivity such as most cellular data networks. */
public static final int NETWORK_TYPE_METERED = 4;
+ /** Sentinel value indicating that bytes are unknown. */
+ public static final int NETWORK_BYTES_UNKNOWN = -1;
+
/**
* Amount of backoff a job has initially by default, in milliseconds.
*/
@@ -250,6 +254,7 @@ public class JobInfo implements Parcelable {
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final int networkType;
+ private final long networkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
@@ -387,6 +392,18 @@ public class JobInfo implements Parcelable {
}
/**
+ * Return the estimated size of network traffic that will be performed by
+ * this job, in bytes.
+ *
+ * @return Estimated size of network traffic, or
+ * {@link #NETWORK_BYTES_UNKNOWN} when unknown.
+ * @see Builder#setEstimatedNetworkBytes(long)
+ */
+ public @BytesLong long getEstimatedNetworkBytes() {
+ return networkBytes;
+ }
+
+ /**
* Set for a job that does not recur periodically, to specify a delay after which the job
* will be eligible for execution. This value is not set if the job recurs periodically.
*/
@@ -524,6 +541,9 @@ public class JobInfo implements Parcelable {
if (networkType != j.networkType) {
return false;
}
+ if (networkBytes != j.networkBytes) {
+ return false;
+ }
if (minLatencyMillis != j.minLatencyMillis) {
return false;
}
@@ -582,6 +602,7 @@ public class JobInfo implements Parcelable {
hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
hashCode = 31 * hashCode + networkType;
+ hashCode = 31 * hashCode + Long.hashCode(networkBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
@@ -612,6 +633,7 @@ public class JobInfo implements Parcelable {
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
networkType = in.readInt();
+ networkBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
isPeriodic = in.readInt() == 1;
@@ -640,6 +662,7 @@ public class JobInfo implements Parcelable {
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
networkType = b.mNetworkType;
+ networkBytes = b.mNetworkBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
isPeriodic = b.mIsPeriodic;
@@ -677,6 +700,7 @@ public class JobInfo implements Parcelable {
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
out.writeInt(networkType);
+ out.writeLong(networkBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
out.writeInt(isPeriodic ? 1 : 0);
@@ -810,6 +834,7 @@ public class JobInfo implements Parcelable {
// Requirements.
private int mConstraintFlags;
private int mNetworkType;
+ private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
private long mTriggerContentMaxDelay = -1;
@@ -909,12 +934,21 @@ public class JobInfo implements Parcelable {
}
/**
- * Set some description of the kind of network type your job needs to have.
- * Not calling this function means the network is not necessary, as the default is
- * {@link #NETWORK_TYPE_NONE}.
- * Bear in mind that calling this function defines network as a strict requirement for your
- * job. If the network requested is not available your job will never run. See
- * {@link #setOverrideDeadline(long)} to change this behaviour.
+ * Set some description of the kind of network type your job needs to
+ * have. Not calling this function means the network is not necessary,
+ * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
+ * calling this function defines network as a strict requirement for
+ * your job. If the network requested is not available your job will
+ * never run. See {@link #setOverrideDeadline(long)} to change this
+ * behaviour.
+ * <p class="note">
+ * Note: When your job executes in
+ * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+ * specific network returned by {@link JobParameters#getNetwork()},
+ * otherwise you'll use the default network which may not meet this
+ * constraint.
+ *
+ * @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
mNetworkType = networkType;
@@ -922,6 +956,43 @@ public class JobInfo implements Parcelable {
}
/**
+ * Set the estimated size of network traffic that will be performed by
+ * this job, in bytes.
+ * <p>
+ * Apps are encouraged to provide values that are as accurate as
+ * possible, but when the exact size isn't available, an
+ * order-of-magnitude estimate can be provided instead. Here are some
+ * specific examples:
+ * <ul>
+ * <li>A job that is backing up a photo knows the exact size of that
+ * photo, so it should provide that size as the estimate.
+ * <li>A job that refreshes top news stories wouldn't know an exact
+ * size, but if the size is expected to be consistently around 100KB, it
+ * can provide that order-of-magnitude value as the estimate.
+ * <li>A job that synchronizes email could end up using an extreme range
+ * of data, from under 1KB when nothing has changed, to dozens of MB
+ * when there are new emails with attachments. Jobs that cannot provide
+ * reasonable estimates should leave this estimated value undefined.
+ * </ul>
+ * Note that the system may choose to delay jobs with large network
+ * usage estimates when the device has a poor network connection, in
+ * order to save battery.
+ *
+ * @param networkBytes The estimated size of network traffic that will
+ * be performed by this job, in bytes. This value only
+ * reflects the traffic that will be performed by the base
+ * job; if you're using {@link JobWorkItem} then you also
+ * need to define the network traffic used by each work item
+ * when constructing them.
+ * @see JobInfo#getEstimatedNetworkBytes()
+ * @see JobWorkItem#JobWorkItem(android.content.Intent, long)
+ */
+ public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
+ mNetworkBytes = networkBytes;
+ return this;
+ }
+
+ /**
* Specify that to run this job, the device must be charging (or be a
* non-battery-powered device connected to permanent power, such as Android TV
* devices). This defaults to {@code false}.
@@ -1147,6 +1218,11 @@ public class JobInfo implements Parcelable {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
+ // Check that network estimates require network type
+ if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+ throw new IllegalArgumentException(
+ "Can't provide estimated network usage without requiring a network");
+ }
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic) {
if (mMaxExecutionDelayMillis != 0L) {
diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java
index a6f6be22..5053dc6f 100644
--- a/android/app/job/JobParameters.java
+++ b/android/app/job/JobParameters.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.IJobCallback;
import android.content.ClipData;
+import android.net.Network;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -66,6 +67,7 @@ public class JobParameters implements Parcelable {
private final boolean overrideDeadlineExpired;
private final Uri[] mTriggeredContentUris;
private final String[] mTriggeredContentAuthorities;
+ private final Network network;
private int stopReason; // Default value of stopReason is REASON_CANCELED
@@ -73,7 +75,7 @@ public class JobParameters implements Parcelable {
public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
Bundle transientExtras, ClipData clipData, int clipGrantFlags,
boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
- String[] triggeredContentAuthorities) {
+ String[] triggeredContentAuthorities, Network network) {
this.jobId = jobId;
this.extras = extras;
this.transientExtras = transientExtras;
@@ -83,6 +85,7 @@ public class JobParameters implements Parcelable {
this.overrideDeadlineExpired = overrideDeadlineExpired;
this.mTriggeredContentUris = triggeredContentUris;
this.mTriggeredContentAuthorities = triggeredContentAuthorities;
+ this.network = network;
}
/**
@@ -171,6 +174,28 @@ public class JobParameters implements Parcelable {
}
/**
+ * Return the network that should be used to perform any network requests
+ * for this job.
+ * <p>
+ * Devices may have multiple active network connections simultaneously, or
+ * they may not have a default network route at all. To correctly handle all
+ * situations like this, your job should always use the network returned by
+ * this method instead of implicitly using the default network route.
+ * <p>
+ * Note that the system may relax the constraints you originally requested,
+ * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
+ * a metered network when there is a surplus of metered data available.
+ *
+ * @return the network that should be used to perform any network requests
+ * for this job, or {@code null} if this job didn't set any required
+ * network type.
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public @Nullable Network getNetwork() {
+ return network;
+ }
+
+ /**
* Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
* currently running job. Calling this method when there is no more work available and all
* previously dequeued work has been completed will result in the system taking care of
@@ -257,6 +282,11 @@ public class JobParameters implements Parcelable {
overrideDeadlineExpired = in.readInt() == 1;
mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
mTriggeredContentAuthorities = in.createStringArray();
+ if (in.readInt() != 0) {
+ network = Network.CREATOR.createFromParcel(in);
+ } else {
+ network = null;
+ }
stopReason = in.readInt();
}
@@ -286,6 +316,12 @@ public class JobParameters implements Parcelable {
dest.writeInt(overrideDeadlineExpired ? 1 : 0);
dest.writeTypedArray(mTriggeredContentUris, flags);
dest.writeStringArray(mTriggeredContentAuthorities);
+ if (network != null) {
+ dest.writeInt(1);
+ network.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeInt(stopReason);
}
diff --git a/android/app/job/JobWorkItem.java b/android/app/job/JobWorkItem.java
index 0eb0450e..1c46e8ec 100644
--- a/android/app/job/JobWorkItem.java
+++ b/android/app/job/JobWorkItem.java
@@ -16,6 +16,7 @@
package android.app.job;
+import android.annotation.BytesLong;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,7 @@ import android.os.Parcelable;
*/
final public class JobWorkItem implements Parcelable {
final Intent mIntent;
+ final long mNetworkBytes;
int mDeliveryCount;
int mWorkId;
Object mGrants;
@@ -39,6 +41,22 @@ final public class JobWorkItem implements Parcelable {
*/
public JobWorkItem(Intent intent) {
mIntent = intent;
+ mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+ }
+
+ /**
+ * Create a new piece of work, which can be submitted to
+ * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+ *
+ * @param intent The general Intent describing this work.
+ * @param networkBytes The estimated size of network traffic that will be
+ * performed by this job work item, in bytes. See
+ * {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for
+ * details about how to estimate.
+ */
+ public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+ mIntent = intent;
+ mNetworkBytes = networkBytes;
}
/**
@@ -49,6 +67,17 @@ final public class JobWorkItem implements Parcelable {
}
/**
+ * Return the estimated size of network traffic that will be performed by
+ * this job work item, in bytes.
+ *
+ * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when
+ * unknown.
+ */
+ public @BytesLong long getEstimatedNetworkBytes() {
+ return mNetworkBytes;
+ }
+
+ /**
* Return the count of the number of times this work item has been delivered
* to the job. The value will be > 1 if it has been redelivered because the job
* was stopped or crashed while it had previously been delivered but before the
@@ -99,6 +128,10 @@ final public class JobWorkItem implements Parcelable {
sb.append(mWorkId);
sb.append(" intent=");
sb.append(mIntent);
+ if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ sb.append(" networkBytes=");
+ sb.append(mNetworkBytes);
+ }
if (mDeliveryCount != 0) {
sb.append(" dcount=");
sb.append(mDeliveryCount);
@@ -118,6 +151,7 @@ final public class JobWorkItem implements Parcelable {
} else {
out.writeInt(0);
}
+ out.writeLong(mNetworkBytes);
out.writeInt(mDeliveryCount);
out.writeInt(mWorkId);
}
@@ -139,6 +173,7 @@ final public class JobWorkItem implements Parcelable {
} else {
mIntent = null;
}
+ mNetworkBytes = in.readLong();
mDeliveryCount = in.readInt();
mWorkId = in.readInt();
}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 7f9f74b4..f6b6b869 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -154,25 +154,6 @@ public final class Slice implements Parcelable {
return Arrays.asList(mHints);
}
- /**
- * @hide
- */
- public SliceItem getPrimaryIcon() {
- for (SliceItem item : getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
- return item;
- }
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
- && !item.hasHint(Slice.HINT_ACTIONS)
- && !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
- if (icon != null) return icon;
- }
- }
- return null;
- }
-
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(mHints);
@@ -405,6 +386,9 @@ public final class Slice implements Parcelable {
final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
null, extras);
Bundle.setDefusable(res, true);
+ if (res == null) {
+ return null;
+ }
return res.getParcelable(SliceProvider.EXTRA_SLICE);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index df87b455..33825b4b 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -156,27 +156,34 @@ public abstract class SliceProvider extends ContentProvider {
}
private Slice handleBindSlice(Uri sliceUri) {
- Slice[] output = new Slice[1];
- CountDownLatch latch = new CountDownLatch(1);
- Handler mainHandler = new Handler(Looper.getMainLooper());
- mainHandler.post(() -> {
- ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyDeath()
- .build());
- output[0] = onBindSlice(sliceUri);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ return onBindSliceStrict(sliceUri);
+ } else {
+ CountDownLatch latch = new CountDownLatch(1);
+ Slice[] output = new Slice[1];
+ Handler.getMain().post(() -> {
+ output[0] = onBindSliceStrict(sliceUri);
latch.countDown();
+ });
+ try {
+ latch.await();
+ return output[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
}
- });
+ }
+ }
+
+ private Slice onBindSliceStrict(Uri sliceUri) {
+ ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectAll()
+ .penaltyDeath()
+ .build());
+ return onBindSlice(sliceUri);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
}
}
diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index d1fe2c90..9943c492 100644
--- a/android/app/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -35,6 +35,27 @@ public class SliceQuery {
/**
* @hide
*/
+ public static SliceItem getPrimaryIcon(Slice slice) {
+ for (SliceItem item : slice.getItems()) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
+ return item;
+ }
+ if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ && !item.hasHint(Slice.HINT_ACTIONS)
+ && !item.hasHint(Slice.HINT_LIST_ITEM)
+ && (item.getType() != SliceItem.TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ if (icon != null) {
+ return icon;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
SliceItem ret = null;
while (ret == null && list.size() != 0) {
diff --git a/android/app/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
deleted file mode 100644
index 32484fca..00000000
--- a/android/app/slice/views/SliceView.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.app.slice.views;
-
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.List;
-
-/**
- * A view that can display a {@link Slice} in different {@link SliceMode}'s.
- *
- * @hide
- */
-public class SliceView extends LinearLayout {
-
- private static final String TAG = "SliceView";
-
- /**
- * @hide
- */
- public abstract static class SliceModeView extends FrameLayout {
-
- public SliceModeView(Context context) {
- super(context);
- }
-
- /**
- * @return the {@link SliceMode} of the slice being presented.
- */
- public abstract String getMode();
-
- /**
- * @param slice the slice to show in this view.
- */
- public abstract void setSlice(Slice slice);
- }
-
- /**
- * @hide
- */
- @StringDef({
- MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
- })
- public @interface SliceMode {}
-
- /**
- * Mode indicating this slice should be presented in small template format.
- */
- public static final String MODE_SMALL = "SLICE_SMALL";
- /**
- * Mode indicating this slice should be presented in large template format.
- */
- public static final String MODE_LARGE = "SLICE_LARGE";
- /**
- * Mode indicating this slice should be presented as an icon.
- */
- public static final String MODE_SHORTCUT = "SLICE_ICON";
-
- /**
- * Will select the type of slice binding based on size of the View. TODO: Put in some info about
- * that selection.
- */
- private static final String MODE_AUTO = "auto";
-
- private String mMode = MODE_AUTO;
- private SliceModeView mCurrentView;
- private final ActionRow mActions;
- private Slice mCurrentSlice;
- private boolean mShowActions = true;
-
- /**
- * Simple constructor to create a slice view from code.
- *
- * @param context The context the view is running in.
- */
- public SliceView(Context context) {
- super(context);
- setOrientation(LinearLayout.VERTICAL);
- mActions = new ActionRow(mContext, true);
- mActions.setBackground(new ColorDrawable(0xffeeeeee));
- mCurrentView = new LargeTemplateView(mContext);
- addView(mCurrentView);
- addView(mActions);
- }
-
- /**
- * @hide
- */
- public void bindSlice(Intent intent) {
- // TODO
- }
-
- /**
- * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
- */
- public void bindSlice(Uri sliceUri) {
- validate(sliceUri);
- Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
- bindSlice(s);
- }
-
- /**
- * Binds this view to the provided {@link Slice}.
- */
- public void bindSlice(Slice slice) {
- mCurrentSlice = slice;
- if (mCurrentSlice != null) {
- reinflate();
- }
- }
-
- /**
- * Call to clean up the view.
- */
- public void unbindSlice() {
- mCurrentSlice = null;
- }
-
- /**
- * Set the {@link SliceMode} this view should present in.
- */
- public void setMode(@SliceMode String mode) {
- setMode(mode, false /* animate */);
- }
-
- /**
- * @hide
- */
- public void setMode(@SliceMode String mode, boolean animate) {
- if (animate) {
- Log.e(TAG, "Animation not supported yet");
- }
- mMode = mode;
- reinflate();
- }
-
- /**
- * @return the {@link SliceMode} this view is presenting in.
- */
- public @SliceMode String getMode() {
- if (mMode.equals(MODE_AUTO)) {
- return MODE_LARGE;
- }
- return mMode;
- }
-
- /**
- * @hide
- *
- * Whether this view should show a row of actions with it.
- */
- public void setShowActionRow(boolean show) {
- mShowActions = show;
- reinflate();
- }
-
- private SliceModeView createView(String mode) {
- switch (mode) {
- case MODE_SHORTCUT:
- return new ShortcutView(getContext());
- case MODE_SMALL:
- return new SmallTemplateView(getContext());
- }
- return new LargeTemplateView(getContext());
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- unbindSlice();
- }
-
- private void reinflate() {
- if (mCurrentSlice == null) {
- return;
- }
- // TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List<SliceItem> items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
- Slice.HINT_ALT);
- String mode = getMode();
- if (!mode.equals(mCurrentView.getMode())) {
- removeAllViews();
- mCurrentView = createView(mode);
- addView(mCurrentView);
- addView(mActions);
- }
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
- mCurrentView.setVisibility(View.VISIBLE);
- mCurrentView.setSlice(mCurrentSlice);
- } else {
- mCurrentView.setVisibility(View.GONE);
- }
-
- boolean showActions = mShowActions && actionRow != null
- && !mode.equals(MODE_SHORTCUT);
- if (showActions) {
- mActions.setActions(actionRow, color);
- mActions.setVisibility(View.VISIBLE);
- } else {
- mActions.setVisibility(View.GONE);
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // TODO -- may need to rethink for AGSA
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- requestDisallowInterceptTouchEvent(true);
- }
- return super.onInterceptTouchEvent(ev);
- }
-
- private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- if (sliceUri.getPathSegments().size() == 0) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- }
-}
diff --git a/android/app/slice/views/ActionRow.java b/android/app/slice/widget/ActionRow.java
index c7d99f7f..c96e6304 100644
--- a/android/app/slice/views/ActionRow.java
+++ b/android/app/slice/widget/ActionRow.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
diff --git a/android/app/slice/views/GridView.java b/android/app/slice/widget/GridView.java
index 6f30c507..67a3c671 100644
--- a/android/app/slice/views/GridView.java
+++ b/android/app/slice/widget/GridView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/app/slice/widget/LargeSliceAdapter.java
index 6794ff98..267fff6a 100644
--- a/android/app/slice/views/LargeSliceAdapter.java
+++ b/android/app/slice/widget/LargeSliceAdapter.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
+import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
import android.content.Context;
import android.util.ArrayMap;
import android.view.LayoutInflater;
@@ -71,7 +71,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
@Override
public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = inflateforType(viewType);
+ View v = inflateForType(viewType);
v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
return new SliceViewHolder(v);
}
@@ -104,7 +104,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
}
}
- private View inflateforType(int viewType) {
+ private View inflateForType(int viewType) {
switch (viewType) {
case TYPE_REMOTE_VIEWS:
return new FrameLayout(mContext);
diff --git a/android/app/slice/views/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
index 9e225162..f45b2a8f 100644
--- a/android/app/slice/views/LargeTemplateView.java
+++ b/android/app/slice/widget/LargeTemplateView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.util.TypedValue;
@@ -35,11 +35,13 @@ import java.util.List;
* @hide
*/
public class LargeTemplateView extends SliceModeView {
+
private final LargeSliceAdapter mAdapter;
private final RecyclerView mRecyclerView;
private final int mDefaultHeight;
private final int mMaxHeight;
private Slice mSlice;
+ private boolean mIsScrollable;
public LargeTemplateView(Context context) {
super(context);
@@ -49,9 +51,6 @@ public class LargeTemplateView extends SliceModeView {
mAdapter = new LargeSliceAdapter(context);
mRecyclerView.setAdapter(mAdapter);
addView(mRecyclerView);
- int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
- getResources().getDisplayMetrics());
- setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
getResources().getDisplayMetrics());
mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
@@ -68,7 +67,7 @@ public class LargeTemplateView extends SliceModeView {
mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+ || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
mRecyclerView.getLayoutParams().height = mDefaultHeight;
} else {
mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
@@ -112,4 +111,12 @@ public class LargeTemplateView extends SliceModeView {
sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
items.addAll(sliceItems);
}
+
+ /**
+ * Whether or not the content in this template should be scrollable.
+ */
+ public void setScrollable(boolean isScrollable) {
+ // TODO -- restrict / enable how much this view can show
+ mIsScrollable = isScrollable;
+ }
}
diff --git a/android/app/slice/views/MessageView.java b/android/app/slice/widget/MessageView.java
index 77252bf2..3124398e 100644
--- a/android/app/slice/views/MessageView.java
+++ b/android/app/slice/widget/MessageView.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
diff --git a/android/app/slice/views/RemoteInputView.java b/android/app/slice/widget/RemoteInputView.java
index e53cb1ea..6eff5afb 100644
--- a/android/app/slice/views/RemoteInputView.java
+++ b/android/app/slice/widget/RemoteInputView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.animation.Animator;
import android.app.Notification;
diff --git a/android/app/slice/views/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
index b6790c7d..0bca8ce2 100644
--- a/android/app/slice/views/ShortcutView.java
+++ b/android/app/slice/widget/ShortcutView.java
@@ -14,21 +14,20 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
-import android.view.ViewGroup;
import com.android.internal.R;
@@ -46,17 +45,14 @@ public class ShortcutView extends SliceModeView {
public ShortcutView(Context context) {
super(context);
- mLargeIconSize = getContext().getResources()
- .getDimensionPixelSize(R.dimen.slice_shortcut_size);
mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
}
@Override
public void setSlice(Slice slice) {
removeAllViews();
SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
- SliceItem iconItem = slice.getPrimaryIcon();
+ SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
SliceItem textItem = sliceItem != null
? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
: SliceQuery.find(slice, SliceItem.TYPE_TEXT);
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
new file mode 100644
index 00000000..5bafbc03
--- /dev/null
+++ b/android/app/slice/widget/SliceView.java
@@ -0,0 +1,402 @@
+/*
+ * 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.app.slice.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
+ * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * <p>
+ * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
+ * observer will be set for that uri and the view will update if there are any changes to the slice.
+ * To use this the app must have a special permission to bind to the slice (see
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * v.setSlice(sliceUri);
+ * </pre>
+ */
+public class SliceView extends ViewGroup {
+
+ private static final String TAG = "SliceView";
+
+ /**
+ * @hide
+ */
+ public abstract static class SliceModeView extends FrameLayout {
+
+ public SliceModeView(Context context) {
+ super(context);
+ }
+
+ /**
+ * @return the mode of the slice being presented.
+ */
+ public abstract String getMode();
+
+ /**
+ * @param slice the slice to show in this view.
+ */
+ public abstract void setSlice(Slice slice);
+ }
+
+ /**
+ * @hide
+ */
+ @StringDef({
+ MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+ })
+ public @interface SliceMode {}
+
+ /**
+ * Mode indicating this slice should be presented in small template format.
+ */
+ public static final String MODE_SMALL = "SLICE_SMALL";
+ /**
+ * Mode indicating this slice should be presented in large template format.
+ */
+ public static final String MODE_LARGE = "SLICE_LARGE";
+ /**
+ * Mode indicating this slice should be presented as an icon.
+ */
+ public static final String MODE_SHORTCUT = "SLICE_ICON";
+
+ /**
+ * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+ * that selection.
+ */
+ private static final String MODE_AUTO = "auto";
+
+ private String mMode = MODE_AUTO;
+ private SliceModeView mCurrentView;
+ private final ActionRow mActions;
+ private Slice mCurrentSlice;
+ private boolean mShowActions = true;
+ private boolean mIsScrollable;
+ private SliceObserver mObserver;
+ private final int mShortcutSize;
+
+ public SliceView(Context context) {
+ this(context, null);
+ }
+
+ public SliceView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+ mActions = new ActionRow(mContext, true);
+ mActions.setBackground(new ColorDrawable(0xffeeeeee));
+ mCurrentView = new LargeTemplateView(mContext);
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ mShortcutSize = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int actionHeight = mActions.getVisibility() != View.GONE
+ ? mActions.getMeasuredHeight()
+ : 0;
+ int newHeightSpec = MeasureSpec.makeMeasureSpec(
+ mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ setMeasuredDimension(width, newHeightSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
+ t + mCurrentView.getMeasuredHeight());
+ if (mActions.getVisibility() != View.GONE) {
+ mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
+ mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void showSlice(Intent intent) {
+ // TODO
+ }
+
+ /**
+ * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
+ * this method your app must have the permission
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+ * updated when the slice identified by the provided URI changes. The lifecycle of this observer
+ * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
+ * To unregister this observer outside of that you can call {@link #clearSlice}.
+ *
+ * @return true if the a slice was found for the provided uri.
+ * @see #clearSlice
+ */
+ public boolean setSlice(@NonNull Uri sliceUri) {
+ Preconditions.checkNotNull(sliceUri,
+ "Uri cannot be null, to remove the slice use clearSlice()");
+ if (sliceUri == null) {
+ clearSlice();
+ return false;
+ }
+ validate(sliceUri);
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
+ if (s != null) {
+ mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+ if (isAttachedToWindow()) {
+ registerSlice(sliceUri);
+ }
+ showSlice(s);
+ }
+ return s != null;
+ }
+
+ /**
+ * Populates this view to the provided {@link Slice}.
+ * <p>
+ * This does not register a content observer on the URI that the slice is backed by so it will
+ * not update if the content changes. To have the view update when the content changes use
+ * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
+ * any special permissions.
+ */
+ public void showSlice(@NonNull Slice slice) {
+ Preconditions.checkNotNull(slice,
+ "Slice cannot be null, to remove the slice use clearSlice()");
+ clearSlice();
+ mCurrentSlice = slice;
+ reinflate();
+ }
+
+ /**
+ * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
+ * done automatically during {@link #onDetachedFromWindow()}.
+ * <p>
+ * It is safe to call this method multiple times.
+ */
+ public void clearSlice() {
+ mCurrentSlice = null;
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
+ /**
+ * Set the mode this view should present in.
+ */
+ public void setMode(@SliceMode String mode) {
+ setMode(mode, false /* animate */);
+ }
+
+ /**
+ * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+ */
+ public void setScrollable(boolean isScrollable) {
+ mIsScrollable = isScrollable;
+ reinflate();
+ }
+
+ /**
+ * @hide
+ */
+ public void setMode(@SliceMode String mode, boolean animate) {
+ if (animate) {
+ Log.e(TAG, "Animation not supported yet");
+ }
+ mMode = mode;
+ reinflate();
+ }
+
+ /**
+ * @return the mode this view is presenting in.
+ */
+ public @SliceMode String getMode() {
+ if (mMode.equals(MODE_AUTO)) {
+ return MODE_LARGE;
+ }
+ return mMode;
+ }
+
+ /**
+ * @hide
+ *
+ * Whether this view should show a row of actions with it.
+ */
+ public void setShowActionRow(boolean show) {
+ mShowActions = show;
+ reinflate();
+ }
+
+ private SliceModeView createView(String mode) {
+ switch (mode) {
+ case MODE_SHORTCUT:
+ return new ShortcutView(getContext());
+ case MODE_SMALL:
+ return new SmallTemplateView(getContext());
+ }
+ return new LargeTemplateView(getContext());
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ mObserver = null;
+ }
+ }
+
+ private void registerSlice(Uri sliceUri) {
+ if (sliceUri == null || mObserver == null) {
+ return;
+ }
+ mContext.getContentResolver().registerContentObserver(sliceUri,
+ false /* notifyForDescendants */, mObserver);
+ }
+
+ private void reinflate() {
+ if (mCurrentSlice == null) {
+ return;
+ }
+ // TODO: Smarter mapping here from one state to the next.
+ SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+ List<SliceItem> items = mCurrentSlice.getItems();
+ SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+ Slice.HINT_ACTIONS,
+ Slice.HINT_ALT);
+ String mode = getMode();
+ if (!mode.equals(mCurrentView.getMode())) {
+ removeAllViews();
+ mCurrentView = createView(mode);
+ addView(mCurrentView, getChildLp(mCurrentView));
+ addView(mActions, getChildLp(mActions));
+ }
+ if (mode.equals(MODE_LARGE)) {
+ ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+ }
+ if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+ mCurrentView.setVisibility(View.VISIBLE);
+ mCurrentView.setSlice(mCurrentSlice);
+ } else {
+ mCurrentView.setVisibility(View.GONE);
+ }
+
+ boolean showActions = mShowActions && actionRow != null
+ && !mode.equals(MODE_SHORTCUT);
+ if (showActions) {
+ mActions.setActions(actionRow, color);
+ mActions.setVisibility(View.VISIBLE);
+ } else {
+ mActions.setVisibility(View.GONE);
+ }
+ }
+
+ private LayoutParams getChildLp(View child) {
+ if (child instanceof ShortcutView) {
+ return new LayoutParams(mShortcutSize, mShortcutSize);
+ } else {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ private static void validate(Uri sliceUri) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ if (sliceUri.getPathSegments().size() == 0) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ }
+
+ private class SliceObserver extends ContentObserver {
+ SliceObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
+ mCurrentSlice = s;
+ reinflate();
+ }
+ }
+}
diff --git a/android/app/slice/views/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
index 19e8e7c9..03669983 100644
--- a/android/app/slice/views/SliceViewUtil.java
+++ b/android/app/slice/widget/SliceViewUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.annotation.ColorInt;
import android.content.Context;
diff --git a/android/app/slice/views/SmallTemplateView.java b/android/app/slice/widget/SmallTemplateView.java
index 42b2d213..1c4c5df2 100644
--- a/android/app/slice/views/SmallTemplateView.java
+++ b/android/app/slice/widget/SmallTemplateView.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package android.app.slice.views;
+package android.app.slice.widget;
import android.app.PendingIntent.CanceledException;
import android.app.slice.Slice;
import android.app.slice.SliceItem;
import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.os.AsyncTask;
import android.view.View;
diff --git a/android/app/usage/AppStandby.java b/android/app/usage/AppStandby.java
new file mode 100644
index 00000000..6f9fc2fa
--- /dev/null
+++ b/android/app/usage/AppStandby.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets
+ * that affect how frequently they can run in the background or perform other battery-consuming
+ * actions. Buckets will be assigned based on how frequently or when the system thinks the user
+ * is likely to use the app.
+ * @hide
+ */
+public class AppStandby {
+
+ /** The app was used very recently, currently in use or likely to be used very soon. */
+ public static final int STANDBY_BUCKET_ACTIVE = 0;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app was used recently and/or likely to be used in the next few hours */
+ public static final int STANDBY_BUCKET_WORKING_SET = 3;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app was used in the last few days and/or likely to be used in the next few days */
+ public static final int STANDBY_BUCKET_FREQUENT = 6;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app has not be used for several days and/or is unlikely to be used for several days */
+ public static final int STANDBY_BUCKET_RARE = 9;
+
+ // Leave some gap in case we want to increase the number of buckets
+
+ /** The app has never been used. */
+ public static final int STANDBY_BUCKET_NEVER = 12;
+
+ /** Reason for bucketing -- default initial state */
+ public static final String REASON_DEFAULT = "default";
+
+ /** Reason for bucketing -- timeout */
+ public static final String REASON_TIMEOUT = "timeout";
+
+ /** Reason for bucketing -- usage */
+ public static final String REASON_USAGE = "usage";
+
+ /** Reason for bucketing -- forced by user / shell command */
+ public static final String REASON_FORCED = "forced";
+
+ /**
+ * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
+ * be appended.
+ */
+ public static final String REASON_PREDICTED = "predicted";
+
+ @IntDef(flag = false, value = {
+ STANDBY_BUCKET_ACTIVE,
+ STANDBY_BUCKET_WORKING_SET,
+ STANDBY_BUCKET_FREQUENT,
+ STANDBY_BUCKET_RARE,
+ STANDBY_BUCKET_NEVER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StandbyBuckets {}
+}
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index fd579fce..c827432a 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -19,6 +19,7 @@ package android.app.usage;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
@@ -247,6 +248,29 @@ public final class UsageStatsManager {
}
/**
+ * @hide
+ */
+ public @StandbyBuckets int getAppStandbyBucket(String packageName) {
+ try {
+ return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(),
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ }
+ return AppStandby.STANDBY_BUCKET_ACTIVE;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
+ try {
+ mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
+ } catch (RemoteException e) {
+ // Nothing to do
+ }
+ }
+
+ /**
* {@hide}
* Temporarily whitelist the specified app for a short duration. This is to allow an app
* receiving a high priority message to be able to access the network and acquire wakelocks
diff --git a/android/appwidget/AppWidgetHostView.java b/android/appwidget/AppWidgetHostView.java
index dc9970a7..ab0eb92e 100644
--- a/android/appwidget/AppWidgetHostView.java
+++ b/android/appwidget/AppWidgetHostView.java
@@ -19,8 +19,6 @@ package android.appwidget;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Color;
@@ -66,11 +64,8 @@ public class AppWidgetHostView extends FrameLayout {
// When we're inflating the initialLayout for a AppWidget, we only allow
// views that are allowed in RemoteViews.
- static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
- public boolean onLoadClass(Class clazz) {
- return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
- }
- };
+ private static final LayoutInflater.Filter INFLATER_FILTER =
+ (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
Context mContext;
Context mRemoteContext;
@@ -136,13 +131,19 @@ public class AppWidgetHostView extends FrameLayout {
mAppWidgetId = appWidgetId;
mInfo = info;
+ // We add padding to the AppWidgetHostView if necessary
+ Rect padding = getDefaultPadding();
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
// Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
// a widget, eg. for some widgets in safe mode.
if (info != null) {
- // We add padding to the AppWidgetHostView if necessary
- Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- updateContentDescription(info);
+ String description = info.loadLabel(getContext().getPackageManager());
+ if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
+ description = Resources.getSystem().getString(
+ com.android.internal.R.string.suspended_widget_accessibility, description);
+ }
+ setContentDescription(description);
}
}
@@ -164,23 +165,23 @@ public class AppWidgetHostView extends FrameLayout {
*/
public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
Rect padding) {
- PackageManager packageManager = context.getPackageManager();
- ApplicationInfo appInfo;
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ // if we can't find the package, ignore
+ }
+ return getDefaultPaddingForWidget(context, appInfo, padding);
+ }
+ private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
+ Rect padding) {
if (padding == null) {
padding = new Rect(0, 0, 0, 0);
} else {
padding.set(0, 0, 0, 0);
}
-
- try {
- appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
- } catch (NameNotFoundException e) {
- // if we can't find the package, return 0 padding
- return padding;
- }
-
- if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Resources r = context.getResources();
padding.left = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_left);
@@ -194,6 +195,11 @@ public class AppWidgetHostView extends FrameLayout {
return padding;
}
+ private Rect getDefaultPadding() {
+ return getDefaultPaddingForWidget(mContext,
+ mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
+ }
+
public int getAppWidgetId() {
return mAppWidgetId;
}
@@ -284,10 +290,7 @@ public class AppWidgetHostView extends FrameLayout {
newOptions = new Bundle();
}
- Rect padding = new Rect();
- if (mInfo != null) {
- padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
- }
+ Rect padding = getDefaultPadding();
float density = getResources().getDisplayMetrics().density;
int xPaddingDips = (int) ((padding.left + padding.right) / density);
@@ -361,7 +364,7 @@ public class AppWidgetHostView extends FrameLayout {
* initial layout.
*/
void resetAppWidget(AppWidgetProviderInfo info) {
- mInfo = info;
+ setAppWidget(mAppWidgetId, info);
mViewMode = VIEW_MODE_NOINIT;
updateAppWidget(null);
}
@@ -433,7 +436,6 @@ public class AppWidgetHostView extends FrameLayout {
}
applyContent(content, recycled, exception);
- updateContentDescription(mInfo);
}
private void applyContent(View content, boolean recycled, Exception exception) {
@@ -460,27 +462,6 @@ public class AppWidgetHostView extends FrameLayout {
}
}
- private void updateContentDescription(AppWidgetProviderInfo info) {
- if (info != null) {
- LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
- ApplicationInfo appInfo = null;
- try {
- appInfo = launcherApps.getApplicationInfo(
- info.provider.getPackageName(), 0, info.getProfile());
- } catch (NameNotFoundException e) {
- // ignore -- use null.
- }
- if (appInfo != null &&
- (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
- setContentDescription(
- Resources.getSystem().getString(
- com.android.internal.R.string.suspended_widget_accessibility, info.label));
- } else {
- setContentDescription(info.label);
- }
- }
- }
-
private void inflateAsync(RemoteViews remoteViews) {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
@@ -614,7 +595,7 @@ public class AppWidgetHostView extends FrameLayout {
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
- inflater.setFilter(sInflaterFilter);
+ inflater.setFilter(INFLATER_FILTER);
AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f1352446..1ddcb1a9 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.ArchTaskExecutor;
+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
+ ArchTaskExecutor.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.
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ }
+ }
+ }
+ };
+
+ /**
+ * Invalidates the LiveData.
+ * <p>
+ * When there are active observers, this will trigger a call to {@link #compute()}.
+ */
+ public void invalidate() {
+ ArchTaskExecutor.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..5b09c32f 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,410 @@
-//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.ArchTaskExecutor;
+import android.arch.core.internal.SafeIterableMap;
+import android.arch.lifecycle.Lifecycle.State;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+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 held 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())) {
+ observer.activeStateChanged(false);
+ 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(@NonNull LifecycleOwner owner, @NonNull 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);
+ }
+
+ /**
+ * 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(@NonNull 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(@NonNull 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(@NonNull 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;
+ }
+ ArchTaskExecutor.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 GenericLifecycleObserver {
+ 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;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ 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 (!ArchTaskExecutor.getInstance().isMainThread()) {
+ throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
+ + " thread");
+ }
+ }
}
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index be9da200..414c4ffe 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -18,55 +18,44 @@ package android.arch.paging;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
import java.util.List;
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
+abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
@Override
boolean isContiguous() {
return true;
}
- void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
- PageResult.Receiver<Key, Value> receiver) {
- NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders);
- if (initial != null) {
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT,
- new Page<Key, Value>(initial.mList),
- initial.getLeadingNullCount(),
- initial.getTrailingNullCount(),
- initial.getPositionOffset()));
- } else {
- receiver.onPageResult(new PageResult<Key, Value>(
- PageResult.INIT, null, 0, 0, 0));
- }
- }
+ abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
+ @NonNull PageResult.Receiver<Key, Value> receiver);
void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- PageResult.Receiver<Key, Value> receiver) {
- List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize);
-
- Page<Key, Value> page = list != null
- ? new Page<Key, Value>(list) : null;
-
- receiver.postOnPageResult(new PageResult<>(
- PageResult.APPEND, page, 0, 0, 0));
+ @NonNull PageResult.Receiver<Key, Value> receiver) {
+ if (!isInvalid()) {
+ List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
+
+ if (list != null && !isInvalid()) {
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0));
+ return;
+ }
+ }
+ receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND));
}
void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- PageResult.Receiver<Key, Value> receiver) {
- List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
-
- Page<Key, Value> page = list != null
- ? new Page<Key, Value>(list) : null;
-
- receiver.postOnPageResult(new PageResult<>(
- PageResult.PREPEND, page, 0, 0, 0));
+ @NonNull PageResult.Receiver<Key, Value> receiver) {
+ if (!isInvalid()) {
+ List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
+
+ if (list != null && !isInvalid()) {
+ receiver.postOnPageResult(new PageResult<>(
+ PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0));
+ return;
+ }
+ }
+ receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND));
}
/**
@@ -84,44 +73,4 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
@Nullable
abstract List<Value> loadBeforeImpl(int currentBeginIndex,
@NonNull Value currentBeginItem, int pageSize);
-
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Nullable
- public abstract NullPaddedList<Value> loadInitial(
- Key key, int initialLoadSize, boolean enablePlaceholders);
-
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Nullable
- public final List<Value> loadAfter(int currentEndIndex,
- @NonNull Value currentEndItem, int pageSize) {
- if (isInvalid()) {
- return null;
- }
- List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
- if (isInvalid()) {
- return null;
- }
- return list;
- }
-
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Nullable
- public final List<Value> loadBefore(int currentBeginIndex,
- @NonNull Value currentBeginItem, int pageSize) {
- if (isInvalid()) {
- return null;
- }
- List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
- if (isInvalid()) {
- return null;
- }
- return list;
-
- }
}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index 2a5cd42f..cdff391d 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -47,7 +47,9 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
});
}
- @MainThread
+ // Creation thread for initial synchronous load, otherwise main thread
+ // Safe to access main thread only state - no other thread has reference during construction
+ @AnyThread
@Override
public void onPageResult(@NonNull PageResult<K, V> pageResult) {
if (pageResult.page == null) {
@@ -70,6 +72,17 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
} else if (pageResult.type == PageResult.PREPEND) {
mKeyedStorage.prependPage(page, ContiguousPagedList.this);
}
+
+ if (mBoundaryCallback != null) {
+ boolean deferEmpty = mStorage.size() == 0;
+ boolean deferBegin = !deferEmpty
+ && pageResult.type == PageResult.PREPEND
+ && pageResult.page.items.size() == 0;
+ boolean deferEnd = !deferEmpty
+ && pageResult.type == PageResult.APPEND
+ && pageResult.page.items.size() == 0;
+ deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+ }
}
};
@@ -77,9 +90,11 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
+ @Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key) {
- super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
+ super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor,
+ boundaryCallback, config);
mDataSource = dataSource;
// blocking init just triggers the initial load on the construction thread -
@@ -168,7 +183,7 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're prepending
- final V item = mStorage.getFirstContiguousItem();
+ final V item = mStorage.getFirstLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -191,7 +206,7 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
+ mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
// safe to access first item here - mStorage can't be empty if we're appending
- final V item = mStorage.getLastContiguousItem();
+ final V item = mStorage.getLastLoadedItem();
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -234,6 +249,8 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal
// finally dispatch callbacks, after prepend may have already been scheduled
notifyChanged(leadingNulls, changedCount);
notifyInserted(0, addedCount);
+
+ offsetBoundaryAccessIndices(addedCount);
}
@MainThread
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 524e570a..ff44521e 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -48,6 +48,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
public abstract class DataSource<Key, Value> {
+ public interface Factory<Key, Value> {
+ DataSource<Key, Value> create();
+ }
+
// Since we currently rely on implementation details of two implementations,
// prevent external subclassing, except through exposed subclasses
DataSource() {
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 0d452946..3214a4ef 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -19,7 +19,6 @@ package android.arch.paging;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.ArrayList;
@@ -124,9 +123,33 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
return list;
}
+
+ @Override
+ void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
+ @NonNull PageResult.Receiver<Key, Value> receiver) {
+
+ PageResult<Key, Value> pageResult =
+ loadInitialInternal(key, initialLoadSize, enablePlaceholders);
+ if (pageResult == null) {
+ // loading failed, return empty page
+ receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT));
+ } else {
+ receiver.onPageResult(pageResult);
+ }
+ }
+
+ /**
+ * Try initial load, and either return the successful initial load to the receiver,
+ * or null if unsuccessful.
+ */
@Nullable
- private NullPaddedList<Value> loadInitialInternal(
+ private PageResult<Key, Value> loadInitialInternal(
@Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
+ // check if invalid at beginning, and before returning a valid list
+ if (isInvalid()) {
+ return null;
+ }
+
List<Value> list;
if (key == null) {
// no key, so load initial.
@@ -171,9 +194,14 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
}
}
+ final Page<Key, Value> page = new Page<>(list);
+
if (list.isEmpty()) {
- // wasn't able to load any items, so publish an unpadded empty list.
- return new NullPaddedList<>(0, Collections.<Value>emptyList());
+ if (isInvalid()) {
+ return null;
+ }
+ // wasn't able to load any items, but not invalid - return an empty page.
+ return new PageResult<>(PageResult.INIT, page, 0, 0, 0);
}
int itemsBefore = COUNT_UNDEFINED;
@@ -181,31 +209,21 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
if (enablePlaceholders) {
itemsBefore = countItemsBefore(getKey(list.get(0)));
itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1)));
- if (isInvalid()) {
- return null;
- }
- }
- if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
- return new NullPaddedList<>(0, list, 0);
- } else {
- return new NullPaddedList<>(itemsBefore, list, itemsAfter);
}
- }
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Override
- public NullPaddedList<Value> loadInitial(
- @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
if (isInvalid()) {
return null;
}
- NullPaddedList<Value> list = loadInitialInternal(key, initialLoadSize, enablePlaceholders);
- if (list == null || isInvalid()) {
- return null;
+ if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
+ itemsBefore = 0;
+ itemsAfter = 0;
}
- return list;
+ return new PageResult<>(
+ PageResult.INIT,
+ page,
+ itemsBefore,
+ itemsAfter,
+ 0);
}
/**
diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java
new file mode 100644
index 00000000..d3a171e5
--- /dev/null
+++ b/android/arch/paging/ListDataSource.java
@@ -0,0 +1,39 @@
+/*
+ * 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.paging;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListDataSource<T> extends TiledDataSource<T> {
+ private final List<T> mList;
+
+ public ListDataSource(List<T> list) {
+ mList = new ArrayList<>(list);
+ }
+
+ @Override
+ public int countItems() {
+ return mList.size();
+ }
+
+ @Override
+ public List<T> loadRange(int startPosition, int count) {
+ int endExclusive = Math.min(mList.size(), startPosition + count);
+ return mList.subList(startPosition, endExclusive);
+ }
+}
diff --git a/android/arch/paging/LivePagedListBuilder.java b/android/arch/paging/LivePagedListBuilder.java
new file mode 100644
index 00000000..ee1810b5
--- /dev/null
+++ b/android/arch/paging/LivePagedListBuilder.java
@@ -0,0 +1,159 @@
+/*
+ * 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.paging;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+public class LivePagedListBuilder<Key, Value> {
+ private Key mInitialLoadKey;
+ private PagedList.Config mConfig;
+ private DataSource.Factory<Key, Value> mDataSourceFactory;
+ private PagedList.BoundaryCallback mBoundaryCallback;
+ private Executor mMainThreadExecutor;
+ private Executor mBackgroundThreadExecutor;
+
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
+ mInitialLoadKey = key;
+ return this;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) {
+ mConfig = config;
+ return this;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) {
+ mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build();
+ return this;
+ }
+
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setDataSourceFactory(
+ @NonNull DataSource.Factory<Key, Value> dataSourceFactory) {
+ mDataSourceFactory = dataSourceFactory;
+ return this;
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setBoundaryCallback(
+ @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) {
+ mBoundaryCallback = boundaryCallback;
+ return this;
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setMainThreadExecutor(
+ @NonNull Executor mainThreadExecutor) {
+ mMainThreadExecutor = mainThreadExecutor;
+ return this;
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
+ @NonNull Executor backgroundThreadExecutor) {
+ mBackgroundThreadExecutor = backgroundThreadExecutor;
+ return this;
+ }
+
+ @NonNull
+ public LiveData<PagedList<Value>> build() {
+ if (mConfig == null) {
+ throw new IllegalArgumentException("PagedList.Config must be provided");
+ }
+ if (mDataSourceFactory == null) {
+ throw new IllegalArgumentException("DataSource.Factory must be provided");
+ }
+ if (mMainThreadExecutor == null) {
+ mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
+ }
+ if (mBackgroundThreadExecutor == null) {
+ mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
+ }
+
+ return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
+ mMainThreadExecutor, mBackgroundThreadExecutor);
+ }
+
+ @AnyThread
+ @NonNull
+ public static <Key, Value> LiveData<PagedList<Value>> create(
+ @Nullable final Key initialLoadKey,
+ @NonNull final PagedList.Config config,
+ @Nullable final PagedList.BoundaryCallback boundaryCallback,
+ @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull final Executor mainThreadExecutor,
+ @NonNull final Executor backgroundThreadExecutor) {
+ 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 = dataSourceFactory.create();
+ mDataSource.addInvalidatedCallback(mCallback);
+
+ mList = new PagedList.Builder<Key, Value>()
+ .setDataSource(mDataSource)
+ .setMainThreadExecutor(mainThreadExecutor)
+ .setBackgroundThreadExecutor(backgroundThreadExecutor)
+ .setBoundaryCallback(boundaryCallback)
+ .setConfig(config)
+ .setInitialKey(initializeKey)
+ .build();
+ } while (mList.isDetached());
+ return mList;
+ }
+ }.getLiveData();
+ }
+}
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index 07dd84bf..b7c68dd6 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,133 +16,5 @@
package android.arch.paging;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-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.
- * <p>
- * If you're using Room, it can generate a LivePagedListProvider from a query:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
- * }</pre>
- * In the above sample, {@code Integer} is used because it is the {@code Key} type of
- * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
- * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
- * here lets you pass an initial loading position as an integer.
- * <p>
- * In the future, Room plans to offer other key types to support paging content with a
- * {@link KeyedDataSource}.
- *
- * @param <Key> Type 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.
- */
- @AnyThread
- @NonNull
- 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.
- */
- @AnyThread
- @NonNull
- 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(ArchTaskExecutor.getMainThreadExecutor())
- .setBackgroundThreadExecutor(
- ArchTaskExecutor.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/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
deleted file mode 100644
index c7b0b231..00000000
--- a/android/arch/paging/NullPaddedList.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.paging;
-
-import android.support.annotation.RestrictTo;
-
-import java.util.AbstractList;
-import java.util.List;
-
-/**
- * NullPaddedList is a simple list, with optional null padding on the beginning and end.
- *
- * @param <Type> The type of the entries in the list.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends AbstractList<Type> {
- List<Type> mList;
- private int mTrailingNullCount;
- private int mLeadingNullCount;
- private int mPositionOffset;
-
- @Override
- public String toString() {
- return "NullPaddedList " + mLeadingNullCount
- + ", " + mList.size()
- + ", " + mTrailingNullCount;
- }
-
- /**
- * Create a static, immutable NullPaddedList with the specified list,
- *
- * @param leadingNullCount Number of empty items in advance of the passed list.
- * @param list List of items.
- * @param trailingNullCount Number of empty items following the passed list.
- */
- NullPaddedList(int leadingNullCount, List<Type> list, int trailingNullCount) {
- if (leadingNullCount < 0 || trailingNullCount < 0) {
- throw new IllegalArgumentException("leading/trailing null count must be non-negative");
- }
- if (list == null) {
- throw new IllegalArgumentException("list must be non-null");
- }
- mList = list;
- mLeadingNullCount = leadingNullCount;
- mTrailingNullCount = trailingNullCount;
- }
-
- NullPaddedList(int leadingNullCount, int totalCount, List<Type> list) {
- if (list == null) {
- throw new IllegalArgumentException("list must be non-null");
- }
-
- int trailingNullCount = totalCount - (leadingNullCount) - list.size();
-
- mList = list;
- mLeadingNullCount = leadingNullCount;
- mTrailingNullCount = trailingNullCount;
- }
-
- NullPaddedList(int positionOffset, List<Type> list) {
- if (list == null) {
- throw new IllegalArgumentException("list must be non-null");
- }
-
- mList = list;
- mPositionOffset = positionOffset;
- }
-
- // --------------- PagedList API ---------------
-
- @Override
- public Type get(int index) {
- if (index < 0 || index >= size()) {
- throw new IndexOutOfBoundsException();
- }
-
- index -= mLeadingNullCount;
- if (index < 0) {
- return null;
- }
- if (index >= mList.size()) {
- return null;
- }
- return mList.get(index);
- }
-
- @Override
- public final int size() {
- return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount();
- }
-
- // --------------- Contiguous API ---------------
-
- public int getPositionOffset() {
- return mPositionOffset;
- }
-
- /**
- * Number of loaded items. This does not account for leading or trailing null padding.
- *
- * @return Number of loaded items.
- */
- public int getLoadedCount() {
- return mList.size();
- }
-
- /**
- * Number of empty, unloaded items ahead of the loaded item region.
- *
- * @return Number of nulls before the loaded list.
- */
- public int getLeadingNullCount() {
- return mLeadingNullCount;
- }
-
- /**
- * Number of empty, unloaded items behind the loaded item region.
- *
- * @return Number of nulls after the loaded list.
- */
- public int getTrailingNullCount() {
- return mTrailingNullCount;
- }
-}
diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java
index a4090f61..55d5fb76 100644
--- a/android/arch/paging/PageResult.java
+++ b/android/arch/paging/PageResult.java
@@ -47,6 +47,14 @@ class PageResult<K, V> {
this.positionOffset = positionOffset;
}
+ PageResult(int type) {
+ this.type = type;
+ this.page = null;
+ this.leadingNulls = 0;
+ this.trailingNulls = 0;
+ this.positionOffset = 0;
+ }
+
interface Receiver<K, V> {
@AnyThread
void postOnPageResult(@NonNull PageResult<K, V> pageResult);
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 51f524af..f18e108c 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -16,8 +16,10 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.lang.ref.WeakReference;
@@ -97,6 +99,8 @@ public abstract class PagedList<T> extends AbstractList<T> {
final Executor mMainThreadExecutor;
@NonNull
final Executor mBackgroundThreadExecutor;
+ @Nullable
+ final BoundaryCallback<T> mBoundaryCallback;
@NonNull
final Config mConfig;
@NonNull
@@ -105,6 +109,16 @@ public abstract class PagedList<T> extends AbstractList<T> {
int mLastLoad = 0;
T mLastItem = null;
+ // if set to true, mBoundaryCallback is non-null, and should
+ // be dispatched when nearby load has occurred
+ private boolean mBoundaryCallbackBeginDeferred = false;
+ private boolean mBoundaryCallbackEndDeferred = false;
+
+ // lowest and highest index accessed by loadAround. Used to
+ // decide when mBoundaryCallback should be dispatched
+ private int mLowestIndexAccessed = Integer.MAX_VALUE;
+ private int mHighestIndexAccessed = Integer.MIN_VALUE;
+
private final AtomicBoolean mDetached = new AtomicBoolean(false);
protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@@ -112,10 +126,12 @@ public abstract class PagedList<T> extends AbstractList<T> {
PagedList(@NonNull PagedStorage<?, T> storage,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
+ @Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config) {
mStorage = storage;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadExecutor = backgroundThreadExecutor;
+ mBoundaryCallback = boundaryCallback;
mConfig = config;
}
@@ -129,6 +145,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
* Generally, this is the UI/main thread.
* @param backgroundThreadExecutor Data loading will be done via this executor - should be a
* background thread.
+ * @param boundaryCallback Optional boundary callback to attach to the list.
* @param config PagedList Config, which defines how the PagedList will load data.
* @param <K> Key type that indicates to the DataSource what data to load.
* @param <T> Type of items to be held and loaded by the PagedList.
@@ -139,6 +156,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
+ @Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
@Nullable K key) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
@@ -150,12 +168,14 @@ public abstract class PagedList<T> extends AbstractList<T> {
return new ContiguousPagedList<>(contigDataSource,
mainThreadExecutor,
backgroundThreadExecutor,
+ boundaryCallback,
config,
key);
} else {
return new TiledPagedList<>((TiledDataSource<T>) dataSource,
mainThreadExecutor,
backgroundThreadExecutor,
+ boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
@@ -186,6 +206,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
private DataSource<Key, Value> mDataSource;
private Executor mMainThreadExecutor;
private Executor mBackgroundThreadExecutor;
+ private BoundaryCallback mBoundaryCallback;
private Config mConfig;
private Key mInitialKey;
@@ -229,6 +250,14 @@ public abstract class PagedList<T> extends AbstractList<T> {
return this;
}
+ @NonNull
+ public Builder<Key, Value> setBoundaryCallback(
+ @Nullable BoundaryCallback boundaryCallback) {
+ mBoundaryCallback = boundaryCallback;
+ return this;
+ }
+
+
/**
* The Config defining how the PagedList should load from the DataSource.
*
@@ -284,10 +313,12 @@ public abstract class PagedList<T> extends AbstractList<T> {
throw new IllegalArgumentException("Config required");
}
+ //noinspection unchecked
return PagedList.create(
mDataSource,
mMainThreadExecutor,
mBackgroundThreadExecutor,
+ mBoundaryCallback,
mConfig,
mInitialKey);
}
@@ -312,7 +343,6 @@ public abstract class PagedList<T> extends AbstractList<T> {
return item;
}
-
/**
* Load adjacent items to passed index.
*
@@ -321,8 +351,122 @@ public abstract class PagedList<T> extends AbstractList<T> {
public void loadAround(int index) {
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
+
+ mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
+ mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
+
+ /*
+ * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
+ * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
+ * and accesses happen near the boundaries.
+ *
+ * Note: we post here, since RecyclerView may want to add items in response, and this
+ * call occurs in PagedListAdapter bind.
+ */
+ tryDispatchBoundaryCallbacks(true);
+ }
+
+ // Creation thread for initial synchronous load, otherwise main thread
+ // Safe to access main thread only state - no other thread has reference during construction
+ @AnyThread
+ void deferBoundaryCallbacks(final boolean deferEmpty,
+ final boolean deferBegin, final boolean deferEnd) {
+ if (mBoundaryCallback == null) {
+ throw new IllegalStateException("Computing boundary");
+ }
+
+ /*
+ * If lowest/highest haven't been initialized, set them to storage size,
+ * since placeholders must already be computed by this point.
+ *
+ * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
+ * if the initial load size is smaller than the prefetch window (see
+ * TiledPagedListTest#boundaryCallback_immediate())
+ */
+ if (mLowestIndexAccessed == Integer.MAX_VALUE) {
+ mLowestIndexAccessed = mStorage.size();
+ }
+ if (mHighestIndexAccessed == Integer.MIN_VALUE) {
+ mHighestIndexAccessed = 0;
+ }
+
+ if (deferEmpty || deferBegin || deferEnd) {
+ // Post to the main thread, since we may be on creation thread currently
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ // on is dispatched immediately, since items won't be accessed
+ //noinspection ConstantConditions
+ if (deferEmpty) {
+ mBoundaryCallback.onZeroItemsLoaded();
+ }
+
+ // for other callbacks, mark deferred, and only dispatch if loadAround
+ // has been called near to the position
+ if (deferBegin) {
+ mBoundaryCallbackBeginDeferred = true;
+ }
+ if (deferEnd) {
+ mBoundaryCallbackEndDeferred = true;
+ }
+ tryDispatchBoundaryCallbacks(false);
+ }
+ });
+ }
}
+ /**
+ * Call this when mLowest/HighestIndexAccessed are changed, or
+ * mBoundaryCallbackBegin/EndDeferred is set.
+ */
+ private void tryDispatchBoundaryCallbacks(boolean post) {
+ final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
+ && mLowestIndexAccessed <= mConfig.prefetchDistance;
+ final boolean dispatchEnd = mBoundaryCallbackEndDeferred
+ && mHighestIndexAccessed >= size() - mConfig.prefetchDistance;
+
+ if (!dispatchBegin && !dispatchEnd) {
+ return;
+ }
+
+ if (dispatchBegin) {
+ mBoundaryCallbackBeginDeferred = false;
+ }
+ if (dispatchEnd) {
+ mBoundaryCallbackEndDeferred = false;
+ }
+ if (post) {
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+ }
+ });
+ } else {
+ dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+ }
+ }
+
+ private void dispatchBoundaryCallbacks(boolean begin, boolean end) {
+ // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
+ if (begin) {
+ //noinspection ConstantConditions
+ mBoundaryCallback.onItemAtFrontLoaded(
+ snapshot(), mStorage.getFirstLoadedItem(), mStorage.size());
+ }
+ if (end) {
+ //noinspection ConstantConditions
+ mBoundaryCallback.onItemAtEndLoaded(
+ snapshot(), mStorage.getLastLoadedItem(), mStorage.size());
+ }
+ }
+
+ /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ void offsetBoundaryAccessIndices(int offset) {
+ mLowestIndexAccessed += offset;
+ mHighestIndexAccessed += offset;
+ }
/**
* Returns size of the list, including any not-yet-loaded null padding.
@@ -351,6 +495,7 @@ public abstract class PagedList<T> extends AbstractList<T> {
*
* @return Immutable snapshot of PagedList data.
*/
+ @SuppressWarnings("WeakerAccess")
@NonNull
public List<T> snapshot() {
if (isImmutable()) {
@@ -726,4 +871,15 @@ public abstract class PagedList<T> extends AbstractList<T> {
}
}
}
+
+ /**
+ * WIP API for load-more-into-local-storage callbacks
+ */
+ public abstract static class BoundaryCallback<T> {
+ public abstract void onZeroItemsLoaded();
+ public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot,
+ @NonNull T itemAtFront, int pagedListSize);
+ public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot,
+ @NonNull T itemAtEnd, int pagedListSize);
+ }
}
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 93c02ea3..89b9c2ee 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -113,6 +113,13 @@ import android.support.v7.widget.RecyclerView;
public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private final PagedListAdapterHelper<T> mHelper;
+ private final PagedListAdapterHelper.PagedListListener<T> mListener =
+ new PagedListAdapterHelper.PagedListListener<T>() {
+ @Override
+ public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+ PagedListAdapter.this.onCurrentListChanged(currentList);
+ }
+ };
/**
* Creates a PagedListAdapter with default threading and
@@ -125,11 +132,13 @@ public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
*/
protected PagedListAdapter(@NonNull DiffCallback<T> diffCallback) {
mHelper = new PagedListAdapterHelper<>(this, diffCallback);
+ mHelper.mListener = mListener;
}
@SuppressWarnings("unused, WeakerAccess")
protected PagedListAdapter(@NonNull ListAdapterConfig<T> config) {
mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config);
+ mHelper.mListener = mListener;
}
/**
@@ -167,4 +176,22 @@ public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
public PagedList<T> getCurrentList() {
return mHelper.getCurrentList();
}
+
+ /**
+ * Called when the current PagedList is updated.
+ * <p>
+ * This may be dispatched as part of {@link #setList(PagedList)} if a background diff isn't
+ * needed (such as when the first list is passed, or the list is cleared). In either case,
+ * PagedListAdapter will simply call
+ * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
+ * <p>
+ * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
+ * to a snapshot version of the PagedList during a diff. This means you cannot observe each
+ * PagedList via this method.
+ *
+ * @param currentList new PagedList being displayed, may be null.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+ }
}
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index abcff415..51a6e37f 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -123,6 +123,14 @@ public class PagedListAdapterHelper<T> {
private final ListUpdateCallback mUpdateCallback;
private final ListAdapterConfig<T> mConfig;
+ // TODO: REAL API
+ interface PagedListListener<T> {
+ void onCurrentListChanged(@Nullable PagedList<T> currentList);
+ }
+
+ @Nullable
+ PagedListListener<T> mListener;
+
private boolean mIsContiguous;
private PagedList<T> mPagedList;
@@ -247,6 +255,9 @@ public class PagedListAdapterHelper<T> {
}
// dispatch update callback after updating mPagedList/mSnapshot
mUpdateCallback.onRemoved(0, removedCount);
+ if (mListener != null) {
+ mListener.onCurrentListChanged(null);
+ }
return;
}
@@ -257,6 +268,10 @@ public class PagedListAdapterHelper<T> {
// dispatch update callback after updating mPagedList/mSnapshot
mUpdateCallback.onInserted(0, pagedList.size());
+
+ if (mListener != null) {
+ mListener.onCurrentListChanged(pagedList);
+ }
return;
}
@@ -311,6 +326,9 @@ public class PagedListAdapterHelper<T> {
previousSnapshot.mStorage, newList.mStorage, diffResult);
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
+ if (mListener != null) {
+ mListener.onCurrentListChanged(mPagedList);
+ }
}
/**
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
index 7f91290d..b857462b 100644
--- a/android/arch/paging/PagedStorage.java
+++ b/android/arch/paging/PagedStorage.java
@@ -230,13 +230,13 @@ final class PagedStorage<K, V> extends AbstractList<V> {
// ---------------- Contiguous API -------------------
- V getFirstContiguousItem() {
+ V getFirstLoadedItem() {
// safe to access first page's first item here:
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
return mPages.get(0).items.get(0);
}
- V getLastContiguousItem() {
+ V getLastLoadedItem() {
// safe to access last page's last item here:
// If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
Page<K, V> page = mPages.get(mPages.size() - 1);
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index c538cb60..fa2932ad 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -18,7 +18,6 @@ package android.arch.paging;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.List;
@@ -38,10 +37,8 @@ import java.util.List;
* backend or data store doesn't require
* <p>
* @param <Value> Value type of items being loaded by the DataSource.
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
/**
* Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
@@ -66,13 +63,10 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
return loadBefore(currentBeginIndex - 1, pageSize);
}
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- @WorkerThread
- @Nullable
@Override
- public NullPaddedList<Value> loadInitial(
- Integer position, int initialLoadSize, boolean enablePlaceholders) {
+ void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders,
+ @NonNull PageResult.Receiver<Integer, Value> receiver) {
+
final int convertPosition = position == null ? 0 : position;
final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2));
@@ -81,11 +75,23 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I
count = countItems();
}
List<Value> data = loadAfter(loadPosition, initialLoadSize);
- if (count == COUNT_UNDEFINED) {
- return new NullPaddedList<>(loadPosition, data);
- } else {
- return new NullPaddedList<>(loadPosition, count, data);
+
+ if (data == null) {
+ receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT));
+ return;
}
+
+ final boolean uncounted = count == COUNT_UNDEFINED;
+ int leadingNullCount = uncounted ? 0 : loadPosition;
+ int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size();
+ int positionOffset = uncounted ? loadPosition : 0;
+
+ receiver.onPageResult(new PageResult<>(
+ PageResult.INIT,
+ new Page<Integer, Value>(data),
+ leadingNullCount,
+ trailingNullCount,
+ positionOffset));
}
/**
diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java
index 7e965a0f..6a8a748c 100644
--- a/android/arch/paging/SnapshotPagedList.java
+++ b/android/arch/paging/SnapshotPagedList.java
@@ -27,6 +27,7 @@ class SnapshotPagedList<T> extends PagedList<T> {
super(pagedList.mStorage.snapshot(),
pagedList.mMainThreadExecutor,
pagedList.mBackgroundThreadExecutor,
+ null,
pagedList.mConfig);
mContiguous = pagedList.isContiguous();
mLastKey = pagedList.getLastKey();
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 61dead3a..0ea94286 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -87,6 +87,8 @@ import java.util.List;
*/
public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
+ private int mItemCount;
+
/**
* Number of items that this DataSource can provide in total.
*
@@ -123,6 +125,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
*/
void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
PageResult.Receiver<Integer, Type> receiver) {
+ mItemCount = itemCount;
if (itemCount == 0) {
// no data to load, just immediately return empty
@@ -132,7 +135,6 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
return;
}
-
List<Type> list = loadRangeWrapper(startPosition, count);
count = Math.min(count, itemCount - startPosition);
@@ -167,9 +169,15 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
List<Type> list = loadRangeWrapper(startPosition, count);
- Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null;
+ Page<Integer, Type> page = null;
+ int trailingNulls = mItemCount - startPosition;
+
+ if (list != null) {
+ page = new Page<Integer, Type>(list);
+ trailingNulls -= list.size();
+ }
receiver.postOnPageResult(new PageResult<>(
- PageResult.TILE, page, startPosition, 0, 0));
+ PageResult.TILE, page, startPosition, trailingNulls, 0));
}
private List<Type> loadRangeWrapper(int startPosition, int count) {
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index 934a0dd0..76bb682d 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -17,7 +17,6 @@
package android.arch.paging;
import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
@@ -46,7 +45,9 @@ class TiledPagedList<T> extends PagedList<T>
});
}
- @MainThread
+ // Creation thread for initial synchronous load, otherwise main thread
+ // Safe to access main thread only state - no other thread has reference during construction
+ @AnyThread
@Override
public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
if (pageResult.page == null) {
@@ -67,6 +68,13 @@ class TiledPagedList<T> extends PagedList<T>
mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
TiledPagedList.this);
}
+
+ if (mBoundaryCallback != null) {
+ boolean deferEmpty = mStorage.size() == 0;
+ boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0;
+ boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0;
+ deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+ }
}
};
@@ -74,15 +82,17 @@ class TiledPagedList<T> extends PagedList<T>
TiledPagedList(@NonNull TiledDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
+ @Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
int position) {
- super(new PagedStorage<Integer, T>(),
- mainThreadExecutor, backgroundThreadExecutor, config);
+ super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor,
+ boundaryCallback, config);
mDataSource = dataSource;
final int pageSize = mConfig.pageSize;
final int itemCount = mDataSource.countItems();
+
final int firstLoadSize = Math.min(itemCount,
(Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
final int firstLoadPosition = computeFirstLoadPosition(
diff --git a/android/arch/paging/integration/testapp/PagedListItemAdapter.java b/android/arch/paging/integration/testapp/PagedListItemAdapter.java
index 3522e437..d1ae5aba 100644
--- a/android/arch/paging/integration/testapp/PagedListItemAdapter.java
+++ b/android/arch/paging/integration/testapp/PagedListItemAdapter.java
@@ -23,7 +23,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
/**
- * Sample NullPaddedList adapter, which uses a PagedListAdapterHelper.
+ * Sample PagedList item Adapter, which uses a PagedListAdapterHelper.
*/
class PagedListItemAdapter extends PagedListAdapter<Item, RecyclerView.ViewHolder> {
diff --git a/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
index 237cc140..974eab95 100644
--- a/android/arch/paging/integration/testapp/PagedListItemViewModel.java
+++ b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
@@ -19,7 +19,7 @@ package android.arch.paging.integration.testapp;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
/**
@@ -41,16 +41,19 @@ public class PagedListItemViewModel extends ViewModel {
LiveData<PagedList<Item>> getLivePagedList() {
if (mLivePagedList == null) {
- mLivePagedList = new LivePagedListProvider<Integer, Item>() {
- @Override
- protected DataSource<Integer, Item> createDataSource() {
- ItemDataSource newDataSource = new ItemDataSource();
- synchronized (mDataSourceLock) {
- mDataSource = newDataSource;
- return mDataSource;
- }
- }
- }.create(0, 20);
+ mLivePagedList = new LivePagedListBuilder<Integer, Item>()
+ .setPagingConfig(20)
+ .setDataSourceFactory(new DataSource.Factory<Integer, Item>() {
+ @Override
+ public DataSource<Integer, Item> create() {
+ ItemDataSource newDataSource = new ItemDataSource();
+ synchronized (mDataSourceLock) {
+ mDataSource = newDataSource;
+ return mDataSource;
+ }
+ }
+ })
+ .build();
}
return mLivePagedList;
diff --git a/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
index 5d0117d7..f1f233f5 100644
--- a/android/arch/paging/integration/testapp/PagedListSampleActivity.java
+++ b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
@@ -16,7 +16,6 @@
package android.arch.paging.integration.testapp;
-import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.arch.paging.PagedList;
@@ -28,7 +27,7 @@ import android.view.View;
import android.widget.Button;
/**
- * Sample NullPaddedList activity with artificial data source.
+ * Sample PagedList activity with artificial data source.
*/
public class PagedListSampleActivity extends AppCompatActivity {
@@ -56,11 +55,4 @@ public class PagedListSampleActivity extends AppCompatActivity {
}
});
}
-
- private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
- @Override
- public LifecycleRegistry getLifecycle() {
- return mLifecycleRegistry;
- }
}
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 320b2cdd..89d16b7e 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -21,7 +21,7 @@ import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.database.Customer;
@@ -81,30 +81,30 @@ public class CustomerViewModel extends AndroidViewModel {
});
}
+ private static <K> LiveData<PagedList<Customer>> getLivePagedList(
+ K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
+ return new LivePagedListBuilder<K, Customer>()
+ .setInitialLoadKey(initialLoadKey)
+ .setPagingConfig(new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build())
+ .setDataSourceFactory(dataSourceFactory)
+ .build();
+ }
+
LiveData<PagedList<Customer>> getLivePagedList(int position) {
if (mLiveCustomerList == null) {
- mLiveCustomerList = mDatabase.getCustomerDao()
- .loadPagedAgeOrder().create(position,
- new PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(false)
- .build());
+ mLiveCustomerList =
+ getLivePagedList(position, mDatabase.getCustomerDao().loadPagedAgeOrder());
}
return mLiveCustomerList;
}
LiveData<PagedList<Customer>> getLivePagedList(String key) {
if (mLiveCustomerList == null) {
- mLiveCustomerList = new LivePagedListProvider<String, Customer>() {
- @Override
- protected DataSource<String, Customer> createDataSource() {
- return new LastNameAscCustomerDataSource(mDatabase);
- }
- }.create(key,
- new PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(false)
- .build());
+ mLiveCustomerList =
+ getLivePagedList(key, LastNameAscCustomerDataSource.factory(mDatabase));
}
return mLiveCustomerList;
}
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 665a1aeb..cb2bb034 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -17,6 +17,7 @@
package android.arch.persistence.room.integration.testapp.dao;
import android.arch.lifecycle.LiveData;
+import android.arch.paging.DataSource;
import android.arch.paging.LivePagedListProvider;
import android.arch.paging.TiledDataSource;
import android.arch.persistence.room.Dao;
@@ -184,7 +185,10 @@ public abstract class UserDao {
}
@Query("SELECT * FROM user where mAge > :age")
- public abstract LivePagedListProvider<Integer, User> loadPagedByAge(int age);
+ public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age);
+
+ @Query("SELECT * FROM user where mAge > :age")
+ public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age);
@Query("SELECT * FROM user ORDER BY mAge DESC")
public abstract TiledDataSource<User> loadUsersByAgeDesc();
@@ -200,67 +204,6 @@ public abstract class UserDao {
@Query("SELECT COUNT(*) from user")
public abstract Integer getUserCount();
-
- // QueryDataSourceTest - name desc
-
- // limit-offset
- @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset")
- public abstract List<User> userNameLimitOffset(int limit, int offset);
-
- // keyed
- @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit")
- public abstract List<User> userNameInitial(int limit);
-
- @Query("SELECT * from user WHERE mName < :key ORDER BY mName DESC LIMIT :limit")
- public abstract List<User> userNameLoadAfter(String key, int limit);
-
- @Query("SELECT COUNT(*) from user WHERE mName < :key ORDER BY mName DESC")
- public abstract int userNameCountAfter(String key);
-
- @Query("SELECT * from user WHERE mName > :key ORDER BY mName ASC LIMIT :limit")
- public abstract List<User> userNameLoadBefore(String key, int limit);
-
- @Query("SELECT COUNT(*) from user WHERE mName > :key ORDER BY mName ASC")
- public abstract int userNameCountBefore(String key);
-
-
-
- // ComplexQueryDataSourceTest - last desc, first asc, id desc
-
- // limit-offset
- @Query("SELECT * from user"
- + " ORDER BY mLastName DESC, mName ASC, mId DESC"
- + " LIMIT :limit OFFSET :offset")
- public abstract List<User> userComplexLimitOffset(int limit, int offset);
-
- // keyed
- @Query("SELECT * from user"
- + " ORDER BY mLastName DESC, mName ASC, mId DESC"
- + " LIMIT :limit")
- public abstract List<User> userComplexInitial(int limit);
-
- @Query("SELECT * from user"
- + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))"
- + " ORDER BY mLastName DESC, mName ASC, mId DESC"
- + " LIMIT :limit")
- public abstract List<User> userComplexLoadAfter(String lastName, String name, int id, int limit);
-
- @Query("SELECT COUNT(*) from user"
- + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))"
- + " ORDER BY mLastName DESC, mName ASC, mId DESC")
- public abstract int userComplexCountAfter(String lastName, String name, int id);
-
- @Query("SELECT * from user"
- + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
- + " ORDER BY mLastName ASC, mName DESC, mId ASC"
- + " LIMIT :limit")
- public abstract List<User> userComplexLoadBefore(String lastName, String name, int id, int limit);
-
- @Query("SELECT COUNT(*) from user"
- + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
- + " ORDER BY mLastName ASC, mName DESC, mId ASC")
- public abstract int userComplexCountBefore(String lastName, String name, int id);
-
@Transaction
public void insertBothByAnnotation(final User a, final User b) {
insert(a);
diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index b5df914a..db45dc46 100644
--- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -16,7 +16,7 @@
package android.arch.persistence.room.integration.testapp.database;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.DataSource;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
@@ -44,12 +44,11 @@ public interface CustomerDao {
void insertAll(Customer[] customers);
/**
- * @return LivePagedListProvider of customers, ordered by last name. Call
- * {@link LivePagedListProvider#create(Object, android.arch.paging.PagedList.Config)} to
- * get a LiveData of PagedLists.
+ * @return DataSource.Factory of customers, ordered by last name. Use
+ * {@link android.arch.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
*/
@Query("SELECT * FROM customer ORDER BY mLastName ASC")
- LivePagedListProvider<Integer, Customer> loadPagedAgeOrder();
+ DataSource.Factory<Integer, Customer> loadPagedAgeOrder();
/**
* @return number of customers
diff --git a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
index 1bc731a5..a38d6aed 100644
--- a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -15,6 +15,7 @@
*/
package android.arch.persistence.room.integration.testapp.database;
+import android.arch.paging.DataSource;
import android.arch.paging.KeyedDataSource;
import android.arch.persistence.room.InvalidationTracker;
import android.support.annotation.NonNull;
@@ -32,10 +33,19 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo
private final InvalidationTracker.Observer mObserver;
private SampleDatabase mDb;
+ public static Factory<String, Customer> factory(final SampleDatabase db) {
+ return new Factory<String, Customer>() {
+ @Override
+ public DataSource<String, Customer> create() {
+ return new LastNameAscCustomerDataSource(db);
+ }
+ };
+ }
+
/**
* Create a DataSource from the customer table of the given database
*/
- public LastNameAscCustomerDataSource(SampleDatabase db) {
+ private LastNameAscCustomerDataSource(SampleDatabase db) {
mDb = db;
mCustomerDao = db.getCustomerDao();
mObserver = new InvalidationTracker.Observer("customer") {
diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
index df70a170..c5465314 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
@@ -28,6 +28,7 @@ import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
import android.arch.persistence.room.integration.testapp.test.TestUtil;
@@ -46,15 +47,54 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+@LargeTest
@RunWith(AndroidJUnit4.class)
-public class LivePagedListProviderTest extends TestDatabaseTest {
+public class DataSourceFactoryTest extends TestDatabaseTest {
@Rule
public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+ private interface LivePagedListFactory {
+ LiveData<PagedList<User>> create();
+ }
+
@Test
- @LargeTest
public void getUsersAsPagedList()
throws InterruptedException, ExecutionException, TimeoutException {
+ validateUsersAsPagedList(new LivePagedListFactory() {
+ @Override
+ public LiveData<PagedList<User>> create() {
+ return new LivePagedListBuilder<Integer, User>()
+ .setPagingConfig(new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setPrefetchDistance(1)
+ .setInitialLoadSizeHint(10).build())
+ .setDataSourceFactory(mUserDao.loadPagedByAge(3))
+ .build();
+ }
+ });
+ }
+
+
+ // TODO: delete this and factory abstraction when LivePagedListProvider is removed
+ @Test
+ public void getUsersAsPagedList_legacyLivePagedListProvider()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ validateUsersAsPagedList(new LivePagedListFactory() {
+ @Override
+ public LiveData<PagedList<User>> create() {
+ return mUserDao.loadPagedByAge_legacy(3).create(
+ 0,
+ new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setPrefetchDistance(1)
+ .setInitialLoadSizeHint(10)
+ .build());
+ }
+ });
+ }
+
+ private void validateUsersAsPagedList(LivePagedListFactory factory)
+ throws InterruptedException, ExecutionException, TimeoutException {
mDatabase.beginTransaction();
try {
for (int i = 0; i < 100; i++) {
@@ -67,12 +107,8 @@ public class LivePagedListProviderTest extends TestDatabaseTest {
mDatabase.endTransaction();
}
assertThat(mUserDao.count(), is(100));
- final LiveData<PagedList<User>> livePagedUsers = mUserDao.loadPagedByAge(3).create(
- 0,
- new PagedList.Config.Builder()
- .setPageSize(10)
- .setPrefetchDistance(1)
- .setInitialLoadSizeHint(10).build());
+
+ final LiveData<PagedList<User>> livePagedUsers = factory.create();
final TestLifecycleOwner testOwner = new TestLifecycleOwner();
testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
diff --git a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java
deleted file mode 100644
index 4d58512a..00000000
--- a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.persistence.room.integration.testapp.test;
-
-import android.arch.paging.BoundedDataSource;
-import android.arch.paging.KeyedDataSource;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ComplexQueryDataSourceTest extends TestDatabaseTest {
-
- @SuppressWarnings("WeakerAccess")
- public class LastFirstIdKey {
- public final String lastName;
- public final String name;
- public final int id;
-
- public LastFirstIdKey(String lastName, String name, int id) {
- this.lastName = lastName;
- this.name = name;
- this.id = id;
- }
- }
-
- /**
- * Proper, keyed implementation.
- */
- public class KeyedUserQueryDataSource extends KeyedDataSource<LastFirstIdKey, User> {
-
- @NonNull
- @Override
- public LastFirstIdKey getKey(@NonNull User user) {
- return new LastFirstIdKey(
- user.getLastName(),
- user.getName(),
- user.getId());
- }
-
- @Override
- public int countItemsBefore(@NonNull LastFirstIdKey key) {
- return mUserDao.userComplexCountBefore(
- key.lastName,
- key.name,
- key.id);
- }
-
- @Override
- public int countItemsAfter(@NonNull LastFirstIdKey key) {
- return mUserDao.userComplexCountAfter(
- key.lastName,
- key.name,
- key.id);
- }
-
- @Nullable
- @Override
- public List<User> loadInitial(int pageSize) {
- return mUserDao.userComplexInitial(pageSize);
- }
-
- @Nullable
- @Override
- public List<User> loadBefore(@NonNull LastFirstIdKey key, int pageSize) {
- return mUserDao.userComplexLoadBefore(
- key.lastName,
- key.name,
- key.id,
- pageSize);
- }
-
- @Nullable
- @Override
- public List<User> loadAfter(@Nullable LastFirstIdKey key, int pageSize) {
- return mUserDao.userComplexLoadAfter(
- key.lastName,
- key.name,
- key.id,
- pageSize);
- }
- }
-
- /**
- * Lazy, LIMIT/OFFSET implementation.
- */
- public class OffsetUserQueryDataSource extends BoundedDataSource<User> {
-
- @Override
- public int countItems() {
- return mUserDao.getUserCount();
- }
-
- @Nullable
- @Override
- public List<User> loadRange(int startPosition, int loadCount) {
- return mUserDao.userComplexLimitOffset(loadCount, startPosition);
- }
- }
-
- private static final User[] USERS_BY_LAST_FIRST_ID = new User[100];
-
- @BeforeClass
- public static void setupClass() {
- String[] lastNames = new String[10];
-
- String[] firstNames = new String[10];
- for (int i = 0; i < 10; i++) {
- lastNames[i] = "f" + (char) ('a' + i);
- firstNames[i] = "l" + (char) ('a' + i);
- }
-
- for (int i = 0; i < USERS_BY_LAST_FIRST_ID.length; i++) {
- User user = new User();
- user.setId(i);
- user.setName(firstNames[i % 10]);
- user.setLastName(lastNames[(i / 10) % 10]);
- user.setAge((int) (10 + Math.random() * 50));
- user.setCustomField(UUID.randomUUID().toString());
- user.setBirthday(new Date());
- USERS_BY_LAST_FIRST_ID[i] = user;
- }
- }
-
- @Before
- public void setup() {
- mUserDao.insertAll(USERS_BY_LAST_FIRST_ID);
-
- Arrays.sort(USERS_BY_LAST_FIRST_ID, new Comparator<User>() {
- @Override
- public int compare(User o1, User o2) {
- int diff = o2.getLastName().compareTo(o1.getLastName());
- if (diff != 0) {
- return diff;
- }
- diff = o2.getName().compareTo(o1.getName());
- if (diff != 0) {
- return -diff; // Note: 'mName' is ASC, therefore diff reversed
- }
-
- return o2.getId() - o1.getId();
- }
- });
- }
-
- @Test
- public void testKeyedQueryDataSource() {
- QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID,
- new KeyedUserQueryDataSource());
- }
-
- @Test
- public void testIndexedQueryDataSourceFull() {
- QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID,
- new OffsetUserQueryDataSource());
- }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
deleted file mode 100644
index 2735c05a..00000000
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.persistence.room.integration.testapp.test;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.arch.paging.BoundedDataSource;
-import android.arch.paging.ContiguousDataSource;
-import android.arch.paging.KeyedDataSource;
-import android.arch.paging.NullPaddedList;
-import android.arch.paging.PositionalDataSource;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class QueryDataSourceTest extends TestDatabaseTest {
- /**
- * Proper, keyed implementation.
- */
- public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> {
- @NonNull
- @Override
- public String getKey(@NonNull User item) {
- return item.getName();
- }
-
- @Override
- public int countItemsBefore(@NonNull String userName) {
- return mUserDao.userNameCountBefore(userName);
- }
-
- @Override
- public int countItemsAfter(@NonNull String userName) {
- return mUserDao.userNameCountAfter(userName);
- }
-
- @Nullable
- @Override
- public List<User> loadInitial(int pageSize) {
- return mUserDao.userNameInitial(pageSize);
- }
-
- @Nullable
- @Override
- public List<User> loadBefore(@NonNull String userName, int pageSize) {
- return mUserDao.userNameLoadBefore(userName, pageSize);
- }
-
- @Nullable
- @Override
- public List<User> loadAfter(@Nullable String userName, int pageSize) {
- return mUserDao.userNameLoadAfter(userName, pageSize);
- }
- }
-
- /**
- * Lazy, LIMIT/OFFSET implementation.
- */
- public class OffsetUserQueryDataSource extends BoundedDataSource<User> {
- @Override
- public int countItems() {
- return mUserDao.getUserCount();
- }
-
- @Nullable
- @Override
- public List<User> loadRange(int startPosition, int loadCount) {
- return mUserDao.userNameLimitOffset(loadCount, startPosition);
- }
- }
-
- private static final User[] USERS_BY_NAME = new User[50];
-
- @BeforeClass
- public static void setupClass() {
- for (int i = 0; i < USERS_BY_NAME.length; i++) {
- USERS_BY_NAME[i] = TestUtil.createUser(i);
- }
- }
-
- @Before
- public void setup() {
- mUserDao.insertAll(USERS_BY_NAME);
-
- Arrays.sort(USERS_BY_NAME, new Comparator<User>() {
- @Override
- public int compare(User o1, User o2) {
- return o2.getName().compareTo(o1.getName());
- }
- });
- }
-
- @Test
- public void testKeyedQueryDataSource() {
- verifyUserDataSource(USERS_BY_NAME, new KeyedUserQueryDataSource());
- }
-
- @Test
- public void testIndexedQueryDataSourceFull() {
- verifyUserDataSource(USERS_BY_NAME, new OffsetUserQueryDataSource());
- }
-
-
- public static <Key> void verifyUserDataSource(User[] expected,
- ContiguousDataSource<Key, User> dataSource) {
- List<User> list = new ArrayList<>();
-
- Object key;
- if (dataSource instanceof PositionalDataSource) {
- // start at 15 by loading 10 items around key 20
- key = 20;
- } else {
- // start at 15 by loading 10 items around key 19 (note, keyed is exclusive, pos isn't)
- KeyedDataSource<String, User> keyedDataSource =
- (KeyedDataSource<String, User>) dataSource;
- key = keyedDataSource.getKey(expected[19]);
- }
- @SuppressWarnings("unchecked")
- NullPaddedList<User> initial = dataSource.loadInitial((Key) key, 10, true);
-
- assertNotNull(initial);
- assertEquals(15, initial.getLeadingNullCount());
- assertEquals(expected.length - 25, initial.getTrailingNullCount());
- assertEquals(expected.length, initial.size());
-
- for (int i = 15; i < initial.size() - initial.getTrailingNullCount(); i++) {
- list.add(initial.get(i));
- }
-
- assertArrayEquals(Arrays.copyOfRange(expected, 15, 25), list.toArray());
- List<User> p = dataSource.loadAfter(24, list.get(list.size() - 1), 10);
- assertNotNull(p);
- list.addAll(p);
-
- assertArrayEquals(Arrays.copyOfRange(expected, 15, 35), list.toArray());
-
- p = dataSource.loadBefore(15, list.get(0), 10);
- assertNotNull(p);
- list.addAll(0, p);
-
- assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
-
- p = dataSource.loadBefore(5, list.get(0), 10);
- assertNotNull(p);
- list.addAll(0, p);
-
- assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray());
- }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
index 854c8627..291cfd67 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -25,7 +25,8 @@ import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.DataSource;
+import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
import android.arch.paging.TiledDataSource;
import android.arch.persistence.room.Dao;
@@ -202,7 +203,10 @@ public class QueryTransactionTest {
@Test
public void pagedList() {
- LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+ LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>()
+ .setDataSourceFactory(mDao.pagedList())
+ .setPagingConfig(10)
+ .build();
observeForever(pagedList);
drain();
assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
@@ -366,7 +370,7 @@ public class QueryTransactionTest {
List<Entity1WithChildren> withRelation();
- LivePagedListProvider<Integer, Entity1> pagedList();
+ DataSource.Factory<Integer, Entity1> pagedList();
TiledDataSource<Entity1> dataSource();
@@ -406,7 +410,7 @@ public class QueryTransactionTest {
@Override
@Query(SELECT_ALL)
- LivePagedListProvider<Integer, Entity1> pagedList();
+ DataSource.Factory<Integer, Entity1> pagedList();
@Override
@Query(SELECT_ALL)
@@ -448,7 +452,7 @@ public class QueryTransactionTest {
@Override
@Transaction
@Query(SELECT_ALL)
- LivePagedListProvider<Integer, Entity1> pagedList();
+ DataSource.Factory<Integer, Entity1> pagedList();
@Override
@Transaction
diff --git a/android/bluetooth/BluetoothGattCharacteristic.java b/android/bluetooth/BluetoothGattCharacteristic.java
index 2c124034..243ad359 100644
--- a/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/android/bluetooth/BluetoothGattCharacteristic.java
@@ -120,7 +120,7 @@ public class BluetoothGattCharacteristic implements Parcelable {
public static final int WRITE_TYPE_DEFAULT = 0x02;
/**
- * Wrtite characteristic without requiring a response by the remote device
+ * Write characteristic without requiring a response by the remote device
*/
public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java
index 179f36da..e3d763ab 100644
--- a/android/bluetooth/BluetoothHidDevice.java
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -31,7 +31,14 @@ import java.util.Arrays;
import java.util.List;
/**
- * @hide
+ * Provides the public APIs to control the Bluetooth HID Device
+ * profile.
+ *
+ * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
+ * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidDevice proxy object.
+ *
+ * {@hide}
*/
public final class BluetoothHidDevice implements BluetoothProfile {
@@ -62,7 +69,9 @@ public final class BluetoothHidDevice implements BluetoothProfile {
/**
* Constants representing device subclass.
*
- * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)
+ * @see #registerApp
+ * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+ * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
*/
public static final byte SUBCLASS1_NONE = (byte) 0x00;
public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
@@ -80,9 +89,9 @@ public final class BluetoothHidDevice implements BluetoothProfile {
/**
* Constants representing report types.
*
- * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
- * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
- * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
+ * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
+ * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
*/
public static final byte REPORT_TYPE_INPUT = (byte) 1;
public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -91,7 +100,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
/**
* Constants representing error response for Set Report.
*
- * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+ * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
*/
public static final byte ERROR_RSP_SUCCESS = (byte) 0;
public static final byte ERROR_RSP_NOT_READY = (byte) 1;
@@ -104,7 +113,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
* Constants representing protocol mode used set by host. Default is always
* {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
*
- * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
+ * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
*/
public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
@@ -169,18 +178,7 @@ public final class BluetoothHidDevice implements BluetoothProfile {
public void onBluetoothStateChange(boolean up) {
Log.d(TAG, "onBluetoothStateChange: up=" + up);
synchronized (mConnection) {
- if (!up) {
- Log.d(TAG, "Unbinding service...");
- if (mService != null) {
- mService = null;
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "onBluetoothStateChange: could not unbind service:",
- e);
- }
- }
- } else {
+ if (up) {
try {
if (mService == null) {
Log.d(TAG, "Binding HID Device service...");
@@ -189,14 +187,15 @@ public final class BluetoothHidDevice implements BluetoothProfile {
} catch (IllegalStateException e) {
Log.e(TAG,
"onBluetoothStateChange: could not bind to HID Dev "
- + "service: ",
- e);
+ + "service: ", e);
} catch (SecurityException e) {
Log.e(TAG,
"onBluetoothStateChange: could not bind to HID Dev "
- + "service: ",
- e);
+ + "service: ", e);
}
+ } else {
+ Log.d(TAG, "Unbinding service...");
+ doUnbind();
}
}
}
@@ -252,6 +251,18 @@ public final class BluetoothHidDevice implements BluetoothProfile {
return true;
}
+ void doUnbind() {
+ Log.d(TAG, "Unbinding HidDevService");
+ if (mService != null) {
+ mService = null;
+ try {
+ mContext.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to unbind HidDevService", e);
+ }
+ }
+ }
+
void close() {
Log.v(TAG, "close()");
@@ -265,16 +276,8 @@ public final class BluetoothHidDevice implements BluetoothProfile {
}
synchronized (mConnection) {
- if (mService != null) {
- mService = null;
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "close: could not unbind HID Dev service: ", e);
- }
- }
+ doUnbind();
}
-
mServiceListener = null;
}
@@ -388,7 +391,9 @@ public final class BluetoothHidDevice implements BluetoothProfile {
/**
* Unregisters application. Active connection will be disconnected and no
* new connections will be allowed until registered again using
- * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
+ * {@link #registerApp
+ * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+ * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
*
* @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
* BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
diff --git a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
index 2731935a..d1efa2d6 100644
--- a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
+++ b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -21,7 +21,16 @@ import android.os.Parcelable;
import java.util.Random;
-/** @hide */
+/**
+ * Represents the app configuration for a Bluetooth HID Device application.
+ *
+ * The app needs a BluetoothHidDeviceAppConfiguration token to unregister
+ * the Bluetooth HID Device service.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
private final long mHash;
diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 1f80ed78..ccc3ef40 100644
--- a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -19,7 +19,17 @@ package android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
+ * application.
+ *
+ * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
+ * app during registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
public final int serviceType;
@@ -36,8 +46,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
public static final int MAX = (int) 0xffffffff;
public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
- int peakBandwidth,
- int latency, int delayVariation) {
+ int peakBandwidth, int latency, int delayVariation) {
this.serviceType = serviceType;
this.tokenRate = tokenRate;
this.tokenBucketSize = tokenBucketSize;
@@ -66,10 +75,13 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
@Override
public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
- return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(),
+ return new BluetoothHidDeviceAppQosSettings(
+ in.readInt(),
+ in.readInt(),
+ in.readInt(),
in.readInt(),
in.readInt(),
- in.readInt(), in.readInt());
+ in.readInt());
}
@Override
@@ -90,7 +102,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
/** @return an int array representation of this instance */
public int[] toArray() {
- return new int[]{
+ return new int[] {
serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
};
}
diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index d21d5061..f01c4932 100644
--- a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,7 +19,18 @@ package android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
+ * HID Device application.
+ *
+ * The BluetoothHidDevice framework adds the SDP record during app
+ * registration, so that the Android device can be discovered as a Bluetooth
+ * HID Device.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
public final String name;
@@ -57,8 +68,12 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
@Override
public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
- return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
- in.readString(), in.readByte(), in.createByteArray());
+ return new BluetoothHidDeviceAppSdpSettings(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readByte(),
+ in.createByteArray());
}
@Override
diff --git a/android/bluetooth/BluetoothHidDeviceCallback.java b/android/bluetooth/BluetoothHidDeviceCallback.java
index 3d407a6c..5ccda0dc 100644
--- a/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -18,16 +18,24 @@ package android.bluetooth;
import android.util.Log;
-/** @hide */
+/**
+ * The template class that applications use to call callback functions on
+ * events from the HID host. Callback functions are wrapped in this class and
+ * registered to the Android system during app registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
public abstract class BluetoothHidDeviceCallback {
- private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+ private static final String TAG = "BluetoothHidDevCallback";
/**
* Callback called when application registration state changes. Usually it's
* called due to either
- * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[],
- * BluetoothHidDeviceCallback)}
+ * {@link BluetoothHidDevice#registerApp
+ * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
* or
* {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
* , but can be also unsolicited in case e.g. Bluetooth was turned off in
@@ -79,7 +87,7 @@ public abstract class BluetoothHidDeviceCallback {
/**
* Callback called when SET_REPORT is received from remote host. In case
* received data are invalid, application shall respond with
- * {@link BluetoothHidDevice#reportError(BluetoothDevice)}.
+ * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
*
* @param type Report Type.
* @param id Report Id.
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
index d73f8526..20342807 100644
--- a/android/content/pm/ApplicationInfo.java
+++ b/android/content/pm/ApplicationInfo.java
@@ -375,7 +375,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
* traffic. Third-party libraries are encouraged to honor this flag as well.
*
- * <p>NOTE: {@code WebView} does not honor this flag.
+ * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
*
* <p>This flag is ignored on Android N and above if an Android Network Security Config is
* present.
@@ -1463,98 +1463,84 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
}
- /**
- * @hide
- */
- public boolean isForwardLocked() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+ /** @hide */
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return (privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
}
- /**
- * @hide
- */
- @TestApi
- public boolean isSystemApp() {
- return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ /** @hide */
+ public boolean isDirectBootAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
}
- /**
- * @hide
- */
- @TestApi
- public boolean isPrivilegedApp() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+ /** @hide */
+ public boolean isEncryptionAware() {
+ return isDirectBootAware() || isPartiallyDirectBootAware();
}
- /**
- * @hide
- */
- public boolean isUpdatedSystemApp() {
- return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ /** @hide */
+ public boolean isExternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
/** @hide */
- public boolean isInternal() {
- return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+ public boolean isExternalAsec() {
+ return TextUtils.isEmpty(volumeUuid) && isExternal();
}
/** @hide */
- public boolean isExternalAsec() {
- return TextUtils.isEmpty(volumeUuid)
- && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+ public boolean isForwardLocked() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
}
/** @hide */
- public boolean isDefaultToDeviceProtectedStorage() {
- return (privateFlags
- & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
+ public boolean isInstantApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
}
/** @hide */
- public boolean isDirectBootAware() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0;
+ public boolean isInternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
}
/** @hide */
- public boolean isPartiallyDirectBootAware() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
+ public boolean isOem() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
}
/** @hide */
- public boolean isEncryptionAware() {
- return isDirectBootAware() || isPartiallyDirectBootAware();
+ public boolean isPartiallyDirectBootAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
}
- /**
- * @hide
- */
- public boolean isInstantApp() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+ /** @hide */
+ @TestApi
+ public boolean isPrivilegedApp() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
- /**
- * @hide
- */
+ /** @hide */
public boolean isRequiredForSystemUser() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
}
- /**
- * Returns true if the app has declared in its manifest that it wants its split APKs to be
- * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
- * @hide
- */
- public boolean requestsIsolatedSplitLoading() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
- }
-
- /**
- * @hide
- */
+ /** @hide */
public boolean isStaticSharedLibrary() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
}
+ /** @hide */
+ @TestApi
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /** @hide */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
/**
* Returns whether or not this application was installed as a virtual preload.
*/
@@ -1563,10 +1549,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
/**
+ * Returns true if the app has declared in its manifest that it wants its split APKs to be
+ * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
* @hide
*/
- public boolean isOem() {
- return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+ public boolean requestsIsolatedSplitLoading() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
}
/**
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index 143c51da..14cf8557 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -337,6 +337,12 @@ public abstract class PackageManagerInternal {
public abstract boolean isPackagePersistent(String packageName);
/**
+ * Returns whether or not the given package represents a legacy system application released
+ * prior to runtime permissions.
+ */
+ public abstract boolean isLegacySystemApp(PackageParser.Package pkg);
+
+ /**
* Get all overlay packages for a user.
* @param userId The user for which to get the overlays.
* @return A list of overlay packages. An empty list is returned if the
@@ -467,7 +473,4 @@ public abstract class PackageManagerInternal {
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
- /** Returns a PermissionGroup. */
- public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
- @NonNull String groupName);
}
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index ad36139a..b48829cf 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -6221,48 +6221,48 @@ public class PackageParser {
return false;
}
- /**
- * @hide
- */
+ /** @hide */
+ public boolean isExternal() {
+ return applicationInfo.isExternal();
+ }
+
+ /** @hide */
public boolean isForwardLocked() {
return applicationInfo.isForwardLocked();
}
- /**
- * @hide
- */
- public boolean isSystemApp() {
- return applicationInfo.isSystemApp();
+ /** @hide */
+ public boolean isOem() {
+ return applicationInfo.isOem();
}
- /**
- * @hide
- */
- public boolean isPrivilegedApp() {
+ /** @hide */
+ public boolean isPrivileged() {
return applicationInfo.isPrivilegedApp();
}
- /**
- * @hide
- */
+ /** @hide */
+ public boolean isSystem() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /** @hide */
public boolean isUpdatedSystemApp() {
return applicationInfo.isUpdatedSystemApp();
}
- /**
- * @hide
- */
+ /** @hide */
public boolean canHaveOatDir() {
// The following app types CANNOT have oat directory
// - non-updated system apps
// - forward-locked apps or apps installed in ASEC containers
- return (!isSystemApp() || isUpdatedSystemApp())
+ return (!isSystem() || isUpdatedSystemApp())
&& !isForwardLocked() && !applicationInfo.isExternalAsec();
}
public boolean isMatch(int flags) {
if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
- return isSystemApp();
+ return isSystem();
}
return true;
}
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
index c1e9cd36..d9c97fe2 100644
--- a/android/content/res/Resources_Delegate.java
+++ b/android/content/res/Resources_Delegate.java
@@ -651,6 +651,26 @@ public class Resources_Delegate {
}
@LayoutlibDelegate
+ static float getFloat(Resources resources, int id) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ @LayoutlibDelegate
static boolean getBoolean(Resources resources, int id) throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
diff --git a/android/hardware/location/ContextHubClient.java b/android/hardware/location/ContextHubClient.java
new file mode 100644
index 00000000..b7e353a4
--- /dev/null
+++ b/android/hardware/location/ContextHubClient.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.annotation.RequiresPermission;
+import android.os.Handler;
+
+import java.io.Closeable;
+
+/**
+ * A class describing a client of the Context Hub Service.
+ *
+ * Clients can send messages to nanoapps at a Context Hub through this object.
+ *
+ * @hide
+ */
+public class ContextHubClient implements Closeable {
+ /*
+ * The ContextHubClient interface associated with this client.
+ */
+ // TODO: Implement this interface and associate with ContextHubClient object
+ // private final IContextHubClient mClientInterface;
+
+ /*
+ * The listening callback associated with this client.
+ */
+ private ContextHubClientCallback mCallback;
+
+ /*
+ * The Context Hub that this client is attached to.
+ */
+ private ContextHubInfo mAttachedHub;
+
+ /*
+ * The handler to invoke mCallback.
+ */
+ private Handler mCallbackHandler;
+
+ ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
+ mCallback = callback;
+ mCallbackHandler = handler;
+ mAttachedHub = hubInfo;
+ }
+
+ /**
+ * Returns the hub that this client is attached to.
+ *
+ * @return the ContextHubInfo of the attached hub
+ */
+ public ContextHubInfo getAttachedHub() {
+ return mAttachedHub;
+ }
+
+ /**
+ * Closes the connection for this client and the Context Hub Service.
+ *
+ * When this function is invoked, the messaging associated with this client is invalidated.
+ * All futures messages targeted for this client are dropped at the service.
+ */
+ public void close() {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Sends a message to a nanoapp through the Context Hub Service.
+ *
+ * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but
+ * does not guarantee delivery of the message to the target nanoapp.
+ *
+ * @param message the message object to send
+ *
+ * @return the result of sending the message defined as in ContextHubTransaction.Result
+ *
+ * @see NanoAppMessage
+ * @see ContextHubTransaction.Result
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ @ContextHubTransaction.Result
+ public int sendMessageToNanoApp(NanoAppMessage message) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+}
diff --git a/android/hardware/location/ContextHubClientCallback.java b/android/hardware/location/ContextHubClientCallback.java
new file mode 100644
index 00000000..ab19d547
--- /dev/null
+++ b/android/hardware/location/ContextHubClientCallback.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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.hardware.location;
+
+/**
+ * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to
+ * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is
+ * attached to.
+ *
+ * This callback is registered through the
+ * {@link android.hardware.location.ContextHubManager#createClient() creation} of
+ * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are
+ * invoked in the following ways:
+ * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted
+ * or targeted to a specific client.
+ * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and
+ * the client can choose to ignore the event by filtering through the parameters.
+ *
+ * @hide
+ */
+public class ContextHubClientCallback {
+ /**
+ * Callback invoked when receiving a message from a nanoapp.
+ *
+ * The message contents of this callback may either be broadcasted or targeted to the
+ * client receiving the invocation.
+ *
+ * @param message the message sent by the nanoapp
+ */
+ public void onMessageFromNanoApp(NanoAppMessage message) {}
+
+ /**
+ * Callback invoked when the attached Context Hub has reset.
+ */
+ public void onHubReset() {}
+
+ /**
+ * Callback invoked when a nanoapp aborts at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had aborted
+ * @param abortCode the reason for nanoapp's abort, specific to each nanoapp
+ */
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {}
+
+ /**
+ * Callback invoked when a nanoapp is loaded at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been loaded
+ */
+ public void onNanoAppLoaded(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been unloaded
+ */
+ public void onNanoAppUnloaded(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is enabled at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been enabled
+ */
+ public void onNanoAppEnabled(long nanoAppId) {}
+
+ /**
+ * Callback invoked when a nanoapp is disabled at the attached Context Hub.
+ *
+ * @param nanoAppId the ID of the nanoapp that had been disabled
+ */
+ public void onNanoAppDisabled(long nanoAppId) {}
+}
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 60500463..7cbb436c 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -27,6 +28,8 @@ import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
+import java.util.List;
+
/**
* A class that exposes the Context hubs on a device to applications.
*
@@ -38,7 +41,6 @@ import android.util.Log;
@SystemApi
@SystemService(Context.CONTEXTHUB_SERVICE)
public final class ContextHubManager {
-
private static final String TAG = "ContextHubManager";
private final Looper mMainLooper;
@@ -256,6 +258,100 @@ public final class ContextHubManager {
}
/**
+ * Returns the list of context hubs in the system.
+ *
+ * @return the list of context hub informations
+ *
+ * @see ContextHubInfo
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public List<ContextHubInfo> getContextHubs() {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Loads a nanoapp at the specified Context Hub.
+ *
+ * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
+ * the enabled state.
+ *
+ * @param hubInfo the hub to load the nanoapp on
+ * @param appBinary The app binary to load
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @see NanoAppBinary
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> loadNanoApp(
+ ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Unloads a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to unload the nanoapp from
+ * @param nanoAppId the app to unload
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Enables a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to enable the nanoapp on
+ * @param nanoAppId the app to enable
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Disables a nanoapp at the specified Context Hub.
+ *
+ * @param hubInfo the hub to disable the nanoapp on
+ * @param nanoAppId the app to disable
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Requests a query for nanoapps loaded at the specified Context Hub.
+ *
+ * @param hubInfo the hub to query a list of nanoapps from
+ *
+ * @return the ContextHubTransaction of the request
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
* Set a callback to receive messages from the context hub
*
* @param callback Callback object
@@ -307,6 +403,29 @@ public final class ContextHubManager {
}
/**
+ * Creates and registers a client and its callback with the Context Hub Service.
+ *
+ * A client is registered with the Context Hub Service for a specified Context Hub. When the
+ * registration succeeds, the client can send messages to nanoapps through the returned
+ * {@link ContextHubClient} object, and receive notifications through the provided callback.
+ *
+ * @param callback the notification callback to register
+ * @param hubInfo the hub to attach this client to
+ * @param handler the handler to invoke the callback, if null uses the current thread Looper
+ *
+ * @return the registered client object
+ *
+ * @see ContextHubClientCallback
+ *
+ * @hide
+ */
+ public ContextHubClient createClient(
+ ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
+ throw new UnsupportedOperationException(
+ "TODO: Implement this, and throw an exception on error");
+ }
+
+ /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java
new file mode 100644
index 00000000..4877d38b
--- /dev/null
+++ b/android/hardware/location/ContextHubTransaction.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class describing a request sent to the Context Hub Service.
+ *
+ * This object is generated as a result of an asynchronous request sent to the Context Hub
+ * through the ContextHubManager APIs. The caller can either retrieve the result
+ * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
+ * asynchronously through a user-defined callback
+ * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
+ *
+ * A transaction can be invalidated if the caller of the transaction is no longer active
+ * and the reference to this object is lost, or if timeout period has passed in
+ * {@link #waitForResponse(long, TimeUnit)}.
+ *
+ * @param <T> the type of the contents in the transaction response
+ *
+ * @hide
+ */
+public class ContextHubTransaction<T> {
+ /**
+ * Constants describing the type of a transaction through the Context Hub Service.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TYPE_LOAD_NANOAPP,
+ TYPE_UNLOAD_NANOAPP,
+ TYPE_ENABLE_NANOAPP,
+ TYPE_DISABLE_NANOAPP,
+ TYPE_QUERY_NANOAPPS})
+ public @interface Type {}
+ public static final int TYPE_LOAD_NANOAPP = 0;
+ public static final int TYPE_UNLOAD_NANOAPP = 1;
+ public static final int TYPE_ENABLE_NANOAPP = 2;
+ public static final int TYPE_DISABLE_NANOAPP = 3;
+ public static final int TYPE_QUERY_NANOAPPS = 4;
+
+ /**
+ * Constants describing the result of a transaction or request through the Context Hub Service.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TRANSACTION_SUCCESS,
+ TRANSACTION_FAILED_UNKNOWN,
+ TRANSACTION_FAILED_BAD_PARAMS,
+ TRANSACTION_FAILED_UNINITIALIZED,
+ TRANSACTION_FAILED_PENDING,
+ TRANSACTION_FAILED_AT_HUB,
+ TRANSACTION_FAILED_TIMEOUT})
+ public @interface Result {}
+ public static final int TRANSACTION_SUCCESS = 0;
+ /**
+ * Generic failure mode.
+ */
+ public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+ /**
+ * Failure mode when the request parameters were not valid.
+ */
+ public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+ /**
+ * Failure mode when the Context Hub is not initialized.
+ */
+ public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+ /**
+ * Failure mode when there are too many transactions pending.
+ */
+ public static final int TRANSACTION_FAILED_PENDING = 4;
+ /**
+ * Failure mode when the request went through, but failed asynchronously at the hub.
+ */
+ public static final int TRANSACTION_FAILED_AT_HUB = 5;
+ /**
+ * Failure mode when the transaction has timed out.
+ */
+ public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+
+ /**
+ * A class describing the response for a ContextHubTransaction.
+ *
+ * @param <R> the type of the contents in the response
+ */
+ public static class Response<R> {
+ /*
+ * The result of the transaction.
+ */
+ @ContextHubTransaction.Result
+ private int mResult;
+
+ /*
+ * The contents of the response from the Context Hub.
+ */
+ private R mContents;
+
+ Response(@ContextHubTransaction.Result int result, R contents) {
+ mResult = result;
+ mContents = contents;
+ }
+
+ @ContextHubTransaction.Result
+ public int getResult() {
+ return mResult;
+ }
+
+ public R getContents() {
+ return mContents;
+ }
+ }
+
+ /**
+ * An interface describing the callback to be invoked when a transaction completes.
+ *
+ * @param <C> the type of the contents in the transaction response
+ */
+ @FunctionalInterface
+ public interface Callback<C> {
+ /**
+ * The callback to invoke when the transaction completes.
+ *
+ * @param transaction the transaction that this callback was attached to.
+ * @param response the response of the transaction.
+ */
+ void onComplete(
+ ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+ }
+
+ /*
+ * The unique identifier representing the transaction.
+ */
+ private int mTransactionId;
+
+ /*
+ * The type of the transaction.
+ */
+ @Type
+ private int mTransactionType;
+
+ /*
+ * The response of the transaction.
+ */
+ private ContextHubTransaction.Response<T> mResponse;
+
+ /*
+ * The handler to invoke the aynsc response supplied by onComplete.
+ */
+ private Handler mHandler = null;
+
+ /*
+ * The callback to invoke when the transaction completes.
+ */
+ private ContextHubTransaction.Callback<T> mCallback = null;
+
+ ContextHubTransaction(int id, @Type int type) {
+ mTransactionId = id;
+ mTransactionType = type;
+ }
+
+ /**
+ * @return the type of the transaction
+ */
+ @Type
+ public int getType() {
+ return mTransactionType;
+ }
+
+ /**
+ * Waits to receive the asynchronous transaction result.
+ *
+ * This function blocks until the Context Hub Service has received a response
+ * for the transaction represented by this object by the Context Hub, or a
+ * specified timeout period has elapsed.
+ *
+ * If the specified timeout has passed, the transaction represented by this object
+ * is invalidated by the Context Hub Service (resulting in a timeout failure in the
+ * response).
+ *
+ * @param timeout the timeout duration
+ * @param unit the unit of the timeout
+ *
+ * @return the transaction response
+ */
+ public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ /**
+ * Sets a callback to be invoked when the transaction completes.
+ *
+ * This function provides an asynchronous approach to retrieve the result of the
+ * transaction. When the transaction response has been provided by the Context Hub,
+ * the given callback will be posted by the provided handler.
+ *
+ * If the transaction has already completed at the time of invocation, the callback
+ * will be immediately posted by the handler. If the transaction has been invalidated,
+ * the callback will never be invoked.
+ *
+ * @param callback the callback to be invoked upon completion
+ * @param handler the handler to post the callback
+ */
+ public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
+ throw new UnsupportedOperationException("TODO: Implement this");
+ }
+
+ private void setResponse(ContextHubTransaction.Response<T> response) {
+ mResponse = response;
+ throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+ }
+}
diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java
new file mode 100644
index 00000000..54542277
--- /dev/null
+++ b/android/hardware/location/NanoAppBinary.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @hide
+ */
+public final class NanoAppBinary implements Parcelable {
+ private static final String TAG = "NanoAppBinary";
+
+ /*
+ * The contents of the app binary.
+ */
+ private byte[] mNanoAppBinary;
+
+ /*
+ * Contents of the nanoapp binary header.
+ *
+ * Only valid if mHasValidHeader is true.
+ * See nano_app_binary_t in context_hub.h for details.
+ */
+ private int mHeaderVersion;
+ private int mMagic;
+ private long mNanoAppId;
+ private int mNanoAppVersion;
+ private int mFlags;
+ private long mHwHubType;
+ private byte mTargetChreApiMajorVersion;
+ private byte mTargetChreApiMinorVersion;
+
+ private boolean mHasValidHeader = false;
+
+ /*
+ * The header version used to parse the binary in parseBinaryHeader().
+ */
+ private static final int EXPECTED_HEADER_VERSION = 1;
+
+ /*
+ * The magic value expected in the header.
+ */
+ private static final int EXPECTED_MAGIC_VALUE =
+ (((int) 'N' << 0) | ((int) 'A' << 8) | ((int) 'N' << 16) | ((int) 'O' << 24));
+
+ /*
+ * Byte order established in context_hub.h
+ */
+ private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+
+ public NanoAppBinary(byte[] appBinary) {
+ mNanoAppBinary = appBinary;
+ parseBinaryHeader();
+ }
+
+ /*
+ * Parses the binary header and populates its field using mNanoAppBinary.
+ */
+ private void parseBinaryHeader() {
+ ByteBuffer buf = ByteBuffer.wrap(mNanoAppBinary).order(HEADER_ORDER);
+
+ mHasValidHeader = false;
+ try {
+ mHeaderVersion = buf.getInt();
+ if (mHeaderVersion != EXPECTED_HEADER_VERSION) {
+ Log.e(TAG, "Unexpected header version " + mHeaderVersion + " while parsing header"
+ + " (expected " + EXPECTED_HEADER_VERSION + ")");
+ return;
+ }
+
+ mMagic = buf.getInt();
+ mNanoAppId = buf.getLong();
+ mNanoAppVersion = buf.getInt();
+ mFlags = buf.getInt();
+ mHwHubType = buf.getLong();
+ mTargetChreApiMajorVersion = buf.get();
+ mTargetChreApiMinorVersion = buf.get();
+ } catch (BufferUnderflowException e) {
+ Log.e(TAG, "Not enough contents in nanoapp header");
+ return;
+ }
+
+ if (mMagic != EXPECTED_MAGIC_VALUE) {
+ Log.e(TAG, "Unexpected magic value " + String.format("0x%08X", mMagic)
+ + "while parsing header (expected "
+ + String.format("0x%08X", EXPECTED_MAGIC_VALUE) + ")");
+ } else {
+ mHasValidHeader = true;
+ }
+ }
+
+ /**
+ * @return the app binary byte array
+ */
+ public byte[] getNanoAppBinary() {
+ return mNanoAppBinary;
+ }
+
+ /**
+ * @return {@code true} if the header is valid, {@code false} otherwise
+ */
+ public boolean hasValidHeader() {
+ return mHasValidHeader;
+ }
+
+ /**
+ * @return the header version
+ */
+ public int getHeaderVersion() {
+ return mHeaderVersion;
+ }
+
+ /**
+ * @return the app ID parsed from the nanoapp header
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the app version parsed from the nanoapp header
+ */
+ public int getNanoAppVersion() {
+ return mNanoAppVersion;
+ }
+
+ /**
+ * @return the compile target hub type parsed from the nanoapp header
+ */
+ public long getHwHubType() {
+ return mHwHubType;
+ }
+
+ /**
+ * @return the target CHRE API major version parsed from the nanoapp header
+ */
+ public byte getTargetChreApiMajorVersion() {
+ return mTargetChreApiMajorVersion;
+ }
+
+ /**
+ * @return the target CHRE API minor version parsed from the nanoapp header
+ */
+ public byte getTargetChreApiMinorVersion() {
+ return mTargetChreApiMinorVersion;
+ }
+
+ private NanoAppBinary(Parcel in) {
+ int binaryLength = in.readInt();
+ mNanoAppBinary = new byte[binaryLength];
+ in.readByteArray(mNanoAppBinary);
+
+ parseBinaryHeader();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mNanoAppBinary.length);
+ out.writeByteArray(mNanoAppBinary);
+ }
+
+ public static final Creator<NanoAppBinary> CREATOR =
+ new Creator<NanoAppBinary>() {
+ @Override
+ public NanoAppBinary createFromParcel(Parcel in) {
+ return new NanoAppBinary(in);
+ }
+
+ @Override
+ public NanoAppBinary[] newArray(int size) {
+ return new NanoAppBinary[size];
+ }
+ };
+}
diff --git a/android/hardware/location/NanoAppMessage.java b/android/hardware/location/NanoAppMessage.java
new file mode 100644
index 00000000..20286749
--- /dev/null
+++ b/android/hardware/location/NanoAppMessage.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing messages send to or from nanoapps through the Context Hub Service.
+ *
+ * The basis of the class is in the IContextHub.hal ContextHubMsg definition.
+ *
+ * @hide
+ */
+public final class NanoAppMessage implements Parcelable {
+ private long mNanoAppId;
+ private int mMessageType;
+ private byte[] mMessageBody;
+ private boolean mIsBroadcasted;
+
+ private NanoAppMessage(
+ long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+ mNanoAppId = nanoAppId;
+ mMessageType = messageType;
+ mMessageBody = messageBody;
+ mIsBroadcasted = broadcasted;
+ }
+
+ /**
+ * Creates a NanoAppMessage object to send to a nanoapp.
+ *
+ * This factory method can be used to generate a NanoAppMessage object to be used in
+ * the ContextHubClient.sendMessageToNanoApp API.
+ *
+ * @param targetNanoAppId the ID of the nanoapp to send the message to
+ * @param messageType the nanoapp-dependent message type
+ * @param messageBody the byte array message contents
+ *
+ * @return the NanoAppMessage object
+ */
+ public static NanoAppMessage createMessageToNanoApp(
+ long targetNanoAppId, int messageType, byte[] messageBody) {
+ return new NanoAppMessage(
+ targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+ }
+
+ /**
+ * Creates a NanoAppMessage object sent from a nanoapp.
+ *
+ * This factory method is intended only to be used by the Context Hub Service when delivering
+ * messages from a nanoapp to clients.
+ *
+ * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+ * @param messageType the nanoapp-dependent message type
+ * @param messageBody the byte array message contents
+ * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+ *
+ * @return the NanoAppMessage object
+ */
+ public static NanoAppMessage createMessageFromNanoApp(
+ long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+ return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+ }
+
+ /**
+ * @return the ID of the source or destination nanoapp
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the type of the message that is nanoapp-dependent
+ */
+ public int getMessageType() {
+ return mMessageType;
+ }
+
+ /**
+ * @return the byte array contents of the message
+ */
+ public byte[] getMessageBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * @return {@code true} if the message is broadcasted, {@code false} otherwise
+ */
+ public boolean isBroadcastMessage() {
+ return mIsBroadcasted;
+ }
+
+ private NanoAppMessage(Parcel in) {
+ mNanoAppId = in.readLong();
+ mIsBroadcasted = (in.readInt() == 1);
+ mMessageType = in.readInt();
+
+ int msgSize = in.readInt();
+ mMessageBody = new byte[msgSize];
+ in.readByteArray(mMessageBody);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mNanoAppId);
+ out.writeInt(mIsBroadcasted ? 1 : 0);
+ out.writeInt(mMessageType);
+
+ out.writeInt(mMessageBody.length);
+ out.writeByteArray(mMessageBody);
+ }
+
+ public static final Creator<NanoAppMessage> CREATOR =
+ new Creator<NanoAppMessage>() {
+ @Override
+ public NanoAppMessage createFromParcel(Parcel in) {
+ return new NanoAppMessage(in);
+ }
+
+ @Override
+ public NanoAppMessage[] newArray(int size) {
+ return new NanoAppMessage[size];
+ }
+ };
+}
diff --git a/android/hardware/location/NanoAppState.java b/android/hardware/location/NanoAppState.java
new file mode 100644
index 00000000..644031b0
--- /dev/null
+++ b/android/hardware/location/NanoAppState.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing the nanoapp state information resulting from a query to a Context Hub.
+ *
+ * @hide
+ */
+public final class NanoAppState implements Parcelable {
+ private long mNanoAppId;
+ private int mNanoAppVersion;
+ private boolean mIsEnabled;
+
+ public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
+ mNanoAppId = nanoAppId;
+ mNanoAppVersion = appVersion;
+ mIsEnabled = enabled;
+ }
+
+ /**
+ * @return the NanoAppInfo for this app
+ */
+ public long getNanoAppId() {
+ return mNanoAppId;
+ }
+
+ /**
+ * @return the app version
+ */
+ public long getNanoAppVersion() {
+ return mNanoAppVersion;
+ }
+
+ /**
+ * @return {@code true} if the app is enabled at the Context Hub, {@code false} otherwise
+ */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ private NanoAppState(Parcel in) {
+ mNanoAppId = in.readLong();
+ mNanoAppVersion = in.readInt();
+ mIsEnabled = (in.readInt() == 1);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mNanoAppId);
+ out.writeInt(mNanoAppVersion);
+ out.writeInt(mIsEnabled ? 1 : 0);
+ }
+
+ public static final Creator<NanoAppState> CREATOR =
+ new Creator<NanoAppState>() {
+ @Override
+ public NanoAppState createFromParcel(Parcel in) {
+ return new NanoAppState(in);
+ }
+
+ @Override
+ public NanoAppState[] newArray(int size) {
+ return new NanoAppState[size];
+ }
+ };
+}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 595d8571..996824d4 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@ public class UsbManager {
"android.hardware.usb.action.USB_PORT_CHANGED";
/**
- * Broadcast Action: A broadcast for USB device attached event.
+ * Activity intent sent when a USB device is attached.
*
* This intent is sent when a USB device is attached to the USB bus when in host mode.
* <ul>
@@ -128,9 +128,8 @@ public class UsbManager {
"android.hardware.usb.action.USB_DEVICE_DETACHED";
/**
- * Broadcast Action: A broadcast for USB accessory attached event.
+ * Activity intent sent when a USB accessory is attached.
*
- * This intent is sent when a USB accessory is attached.
* <ul>
* <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
* for the attached accessory
diff --git a/android/location/GnssClock.java b/android/location/GnssClock.java
index 25d247de..671c57cb 100644
--- a/android/location/GnssClock.java
+++ b/android/location/GnssClock.java
@@ -332,6 +332,9 @@ public final class GnssClock implements Parcelable {
/**
* Gets the clock's Drift in nanoseconds per second.
*
+ * <p>This value is the instantaneous time-derivative of the value provided by
+ * {@link #getBiasNanos()}.
+ *
* <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master
* clock) frequency. The error estimate for this reported drift is
* {@link #getDriftUncertaintyNanosPerSecond()}.
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index 4bb88440..db12dd97 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +24,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -77,6 +80,31 @@ public final class NetworkCapabilities implements Parcelable {
*/
private long mNetworkCapabilities;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+ NET_CAPABILITY_MMS,
+ NET_CAPABILITY_SUPL,
+ NET_CAPABILITY_DUN,
+ NET_CAPABILITY_FOTA,
+ NET_CAPABILITY_IMS,
+ NET_CAPABILITY_CBS,
+ NET_CAPABILITY_WIFI_P2P,
+ NET_CAPABILITY_IA,
+ NET_CAPABILITY_RCS,
+ NET_CAPABILITY_XCAP,
+ NET_CAPABILITY_EIMS,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_FOREGROUND,
+ })
+ public @interface NetCapability { }
+
/**
* Indicates this is a network that has the ability to reach the
* carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@ public final class NetworkCapabilities implements Parcelable {
* Multiple capabilities may be applied sequentially. Note that when searching
* for a network to satisfy a request, all capabilities requested must be satisfied.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+ * @param capability the capability to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addCapability(int capability) {
+ public NetworkCapabilities addCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -275,11 +303,11 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Removes (if found) the given capability from this {@code NetworkCapability} instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+ * @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeCapability(int capability) {
+ public NetworkCapabilities removeCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -290,21 +318,20 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Gets all the capabilities set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
- * for this instance.
+ * @return an array of capability values for this instance.
* @hide
*/
- public int[] getCapabilities() {
+ public @NetCapability int[] getCapabilities() {
return BitUtils.unpackBits(mNetworkCapabilities);
}
/**
* Tests for the presence of a capabilitity on this instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+ * @param capability the capabilities to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasCapability(int capability) {
+ public boolean hasCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
return false;
}
@@ -385,6 +412,19 @@ public final class NetworkCapabilities implements Parcelable {
*/
private long mTransportTypes;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ TRANSPORT_CELLULAR,
+ TRANSPORT_WIFI,
+ TRANSPORT_BLUETOOTH,
+ TRANSPORT_ETHERNET,
+ TRANSPORT_VPN,
+ TRANSPORT_WIFI_AWARE,
+ TRANSPORT_LOWPAN,
+ })
+ public @interface Transport { }
+
/**
* Indicates this network uses a Cellular transport.
*/
@@ -426,7 +466,7 @@ public final class NetworkCapabilities implements Parcelable {
public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
/** @hide */
- public static boolean isValidTransport(int transportType) {
+ public static boolean isValidTransport(@Transport int transportType) {
return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
}
@@ -449,11 +489,11 @@ public final class NetworkCapabilities implements Parcelable {
* to be selected. This is logically different than
* {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+ * @param transportType the transport type to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addTransportType(int transportType) {
+ public NetworkCapabilities addTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes |= 1 << transportType;
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Removes (if found) the given transport from this {@code NetworkCapability} instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+ * @param transportType the transport type to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeTransportType(int transportType) {
+ public NetworkCapabilities removeTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes &= ~(1 << transportType);
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@ public final class NetworkCapabilities implements Parcelable {
/**
* Gets all the transports set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
- * for this instance.
+ * @return an array of transport type values for this instance.
* @hide
*/
- public int[] getTransportTypes() {
+ public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
/**
* Tests for the presence of a transport on this instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+ * @param transportType the transport type to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasTransport(int transportType) {
+ public boolean hasTransport(@Transport int transportType) {
return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
}
@@ -896,7 +935,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* @hide
*/
- public static String capabilityNamesOf(int[] capabilities) {
+ public static String capabilityNamesOf(@NetCapability int[] capabilities) {
StringJoiner joiner = new StringJoiner("|");
if (capabilities != null) {
for (int c : capabilities) {
@@ -909,7 +948,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* @hide
*/
- public static String capabilityNameOf(int capability) {
+ public static String capabilityNameOf(@NetCapability int capability) {
switch (capability) {
case NET_CAPABILITY_MMS: return "MMS";
case NET_CAPABILITY_SUPL: return "SUPL";
@@ -937,7 +976,7 @@ public final class NetworkCapabilities implements Parcelable {
/**
* @hide
*/
- public static String transportNamesOf(int[] types) {
+ public static String transportNamesOf(@Transport int[] types) {
StringJoiner joiner = new StringJoiner("|");
if (types != null) {
for (int t : types) {
@@ -950,14 +989,14 @@ public final class NetworkCapabilities implements Parcelable {
/**
* @hide
*/
- public static String transportNameOf(int transport) {
+ public static String transportNameOf(@Transport int transport) {
if (!isValidTransport(transport)) {
return "UNKNOWN";
}
return TRANSPORT_NAMES[transport];
}
- private static void checkValidTransportType(int transport) {
+ private static void checkValidTransportType(@Transport int transport) {
Preconditions.checkArgument(
isValidTransport(transport), "Invalid TransportType " + transport);
}
diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java
index 95a8bb47..25b17052 100644
--- a/android/net/NetworkRequest.java
+++ b/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@ public class NetworkRequest implements Parcelable {
* Add the given capability requirement to this builder. These represent
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
- * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
- * definitions.
+ * satisfied.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+ * @param capability The capability to add.
* @return The builder to facilitate chaining
* {@code builder.addCapability(...).addCapability();}.
*/
- public Builder addCapability(int capability) {
+ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
return this;
}
@@ -170,10 +169,10 @@ public class NetworkRequest implements Parcelable {
/**
* Removes (if found) the given capability from this builder instance.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+ * @param capability The capability to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeCapability(int capability) {
+ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
return this;
}
@@ -208,13 +207,12 @@ public class NetworkRequest implements Parcelable {
* Adds the given transport requirement to this builder. These represent
* the set of allowed transports for the request. Only networks using one
* of these transports will satisfy the request. If no particular transports
- * are required, none should be specified here. See {@link NetworkCapabilities}
- * for {@code TRANSPORT_*} definitions.
+ * are required, none should be specified here.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+ * @param transportType The transport type to add.
* @return The builder to facilitate chaining.
*/
- public Builder addTransportType(int transportType) {
+ public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.addTransportType(transportType);
return this;
}
@@ -222,10 +220,10 @@ public class NetworkRequest implements Parcelable {
/**
* Removes (if found) the given transport from this builder instance.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+ * @param transportType The transport type to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeTransportType(int transportType) {
+ public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.removeTransportType(transportType);
return this;
}
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
index 28cf42f2..eb61c153 100644
--- a/android/net/metrics/DefaultNetworkEvent.java
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -16,73 +16,43 @@
package android.net.metrics;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.net.NetworkCapabilities;
-import android.os.Parcel;
-import android.os.Parcelable;
/**
* An event recorded by ConnectivityService when there is a change in the default network.
* {@hide}
*/
-public final class DefaultNetworkEvent implements Parcelable {
+public class DefaultNetworkEvent {
+
// The ID of the network that has become the new default or NETID_UNSET if none.
- public final int netId;
+ public int netId = NETID_UNSET;
// The list of transport types of the new default network, for example TRANSPORT_WIFI, as
// defined in NetworkCapabilities.java.
- public final int[] transportTypes;
+ public int[] transportTypes = new int[0];
// The ID of the network that was the default before or NETID_UNSET if none.
- public final int prevNetId;
+ public int prevNetId = NETID_UNSET;
// Whether the previous network had IPv4/IPv6 connectivity.
- public final boolean prevIPv4;
- public final boolean prevIPv6;
-
- public DefaultNetworkEvent(int netId, int[] transportTypes,
- int prevNetId, boolean prevIPv4, boolean prevIPv6) {
- this.netId = netId;
- this.transportTypes = transportTypes;
- this.prevNetId = prevNetId;
- this.prevIPv4 = prevIPv4;
- this.prevIPv6 = prevIPv6;
- }
-
- private DefaultNetworkEvent(Parcel in) {
- this.netId = in.readInt();
- this.transportTypes = in.createIntArray();
- this.prevNetId = in.readInt();
- this.prevIPv4 = (in.readByte() > 0);
- this.prevIPv6 = (in.readByte() > 0);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
- out.writeIntArray(transportTypes);
- out.writeInt(prevNetId);
- out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0);
- out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
+ public boolean prevIPv4;
+ public boolean prevIPv6;
@Override
public String toString() {
- String prevNetwork = String.valueOf(prevNetId);
- String newNetwork = String.valueOf(netId);
- if (prevNetId != 0) {
- prevNetwork += ":" + ipSupport();
- }
- if (netId != 0) {
- newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
- }
- return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+ String prevNetwork = String.valueOf(prevNetId);
+ String newNetwork = String.valueOf(netId);
+ if (prevNetId != 0) {
+ prevNetwork += ":" + ipSupport();
+ }
+ if (netId != 0) {
+ newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+ }
+ return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
}
private String ipSupport() {
if (prevIPv4 && prevIPv6) {
- return "DUAL";
+ return "IPv4v6";
}
if (prevIPv6) {
return "IPv6";
@@ -92,15 +62,4 @@ public final class DefaultNetworkEvent implements Parcelable {
}
return "NONE";
}
-
- public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR
- = new Parcelable.Creator<DefaultNetworkEvent>() {
- public DefaultNetworkEvent createFromParcel(Parcel in) {
- return new DefaultNetworkEvent(in);
- }
-
- public DefaultNetworkEvent[] newArray(int size) {
- return new DefaultNetworkEvent[size];
- }
- };
}
diff --git a/android/net/util/VersionedBroadcastListener.java b/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 00000000..107c4049
--- /dev/null
+++ b/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+ private static final boolean DBG = false;
+
+ public interface IntentCallback {
+ public void run(Intent intent);
+ }
+
+ private final String mTag;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IntentFilter mFilter;
+ private final Consumer<Intent> mCallback;
+ private final AtomicInteger mGenerationNumber;
+ private BroadcastReceiver mReceiver;
+
+ public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+ IntentFilter filter, Consumer<Intent> callback) {
+ mTag = tag;
+ mContext = ctx;
+ mHandler = handler;
+ mFilter = filter;
+ mCallback = callback;
+ mGenerationNumber = new AtomicInteger(0);
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(mTag, "startListening");
+ if (mReceiver != null) return;
+
+ mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+ mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+ }
+
+ public void stopListening() {
+ if (DBG) Log.d(mTag, "stopListening");
+ if (mReceiver == null) return;
+
+ mGenerationNumber.incrementAndGet();
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ public final String tag;
+ public final AtomicInteger atomicGenerationNumber;
+ public final Consumer<Intent> callback;
+ // Used to verify this receiver is still current.
+ public final int generationNumber;
+
+ public Receiver(
+ String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+ this.tag = tag;
+ this.atomicGenerationNumber = atomicGenerationNumber;
+ this.callback = callback;
+ generationNumber = atomicGenerationNumber.incrementAndGet();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = atomicGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(tag, "receiver generationNumber=" + generationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (generationNumber != currentGenerationNumber) return;
+
+ callback.accept(intent);
+ }
+ }
+}
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index 649b0cef..c2959d5e 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -68,6 +68,7 @@ import java.util.concurrent.CountDownLatch;
* leaks within the calling process.
* <p>
* It deals with several categories of items:
+ * </p>
* <ul>
* <li>The list of configured networks. The list can be viewed and updated, and
* attributes of individual entries can be modified.</li>
@@ -79,9 +80,11 @@ import java.util.concurrent.CountDownLatch;
* <li>It defines the names of various Intent actions that are broadcast upon
* any sort of change in Wi-Fi state.
* </ul>
+ * <p>
* This is the API to use when performing Wi-Fi specific operations. To perform
* operations that pertain to network connectivity at an abstract level, use
* {@link android.net.ConnectivityManager}.
+ * </p>
*/
@SystemService(Context.WIFI_SERVICE)
public class WifiManager {
@@ -1575,7 +1578,21 @@ public class WifiManager {
* Request a scan for access points. Returns immediately. The availability
* of the results is made known later by means of an asynchronous event sent
* on completion of the scan.
- * @return {@code true} if the operation succeeded, i.e., the scan was initiated
+ * <p>
+ * To initiate a Wi-Fi scan, declare the
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission in the manifest, and perform these steps:
+ * </p>
+ * <ol style="1">
+ * <li>Invoke the following method:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+ * <li>
+ * Register a BroadcastReceiver to listen to
+ * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+ * <li>When a broadcast is received, call:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+ * </ol>
+ * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
*/
public boolean startScan() {
return startScan(null);
@@ -1877,32 +1894,6 @@ public class WifiManager {
}
/**
- * This call is deprecated and removed. It is no longer used to
- * start WiFi Tethering. Please use {@link ConnectivityManager#startTethering(int, boolean,
- * ConnectivityManager#OnStartTetheringCallback)} if
- * the caller has proper permissions. Callers can also use the LocalOnlyHotspot feature for a
- * hotspot capable of communicating with co-located devices {@link
- * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
- *
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @return {@code false}
- *
- * @hide
- * @deprecated This API is nolonger supported.
- * @removed
- */
- @SystemApi
- @Deprecated
- @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- String packageName = mContext.getOpPackageName();
-
- Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
- return false;
- }
-
- /**
* Call allowing ConnectivityService to update WifiService with interface mode changes.
*
* The possible modes include: {@link IFACE_IP_MODE_TETHERED},
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 8682c01e..a8bd9403 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -1986,7 +1986,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
/**
- * Returns the number of times that the devie has started idling.
+ * Returns the number of times that the device has started idling.
*
* {@hide}
*/
@@ -6453,7 +6453,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println();
}
}
- if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+ if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
pw.println("Daily stats:");
pw.print(" Current start time: ");
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
diff --git a/android/os/Binder.java b/android/os/Binder.java
index e9e695bb..2bfb0138 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -27,12 +27,14 @@ import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
+import java.util.ArrayList;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -90,6 +92,20 @@ public class Binder implements IBinder {
*/
private static volatile TransactionTracker sTransactionTracker = null;
+ /**
+ * Guestimate of native memory associated with a Binder.
+ */
+ private static final int NATIVE_ALLOCATION_SIZE = 500;
+
+ private static native long getNativeFinalizer();
+
+ // Use a Holder to allow static initialization of Binder in the boot image, and
+ // possibly to avoid some initialization ordering issues.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ }
+
// Transaction tracking code.
/**
@@ -167,7 +183,7 @@ public class Binder implements IBinder {
try {
if (binder instanceof BinderProxy) {
((BinderProxy) binder).mWarnOnBlocking = false;
- } else if (binder != null
+ } else if (binder != null && binder.getInterfaceDescriptor() != null
&& binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
Log.w(TAG, "Unable to allow blocking on interface " + binder);
}
@@ -188,8 +204,11 @@ public class Binder implements IBinder {
}
}
- /* mObject is used by native code, do not remove or rename */
- private long mObject;
+ /**
+ * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+ */
+ private final long mObject;
+
private IInterface mOwner;
private String mDescriptor;
@@ -360,7 +379,8 @@ public class Binder implements IBinder {
* Default constructor initializes the object.
*/
public Binder() {
- init();
+ mObject = getNativeBBinderHolder();
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Binder> klass = getClass();
@@ -414,7 +434,7 @@ public class Binder implements IBinder {
* descriptor.
*/
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
- if (mDescriptor.equals(descriptor)) {
+ if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
@@ -643,14 +663,6 @@ public class Binder implements IBinder {
return true;
}
- protected void finalize() throws Throwable {
- try {
- destroyBinder();
- } finally {
- super.finalize();
- }
- }
-
static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) {
if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) {
// Trying to send > 800k, this is way too much
@@ -674,8 +686,8 @@ public class Binder implements IBinder {
}
}
- private native final void init();
- private native final void destroyBinder();
+ private static native long getNativeBBinderHolder();
+ private static native long getFinalizer();
// Entry point from android_util_Binder.cpp's onTransact
private boolean execTransact(int code, long dataObj, long replyObj,
@@ -736,11 +748,207 @@ public class Binder implements IBinder {
*/
final class BinderProxy implements IBinder {
// See android_util_Binder.cpp for the native half of this.
- // TODO: Consider using NativeAllocationRegistry instead of finalization.
// Assume the process-wide default value when created
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
+ /*
+ * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+ * We roll our own only because we need to lazily remove WeakReferences during accesses
+ * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+ * because we want weak values, not keys.
+ * Our hash table is never resized, but the number of entries is unlimited;
+ * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+ * Not thread-safe. Client ensures there's a single access at a time.
+ */
+ private static final class ProxyMap {
+ private static final int LOG_MAIN_INDEX_SIZE = 8;
+ private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
+ private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+
+ /**
+ * We next warn when we exceed this bucket size.
+ */
+ private int mWarnBucketSize = 20;
+
+ /**
+ * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+ */
+ private static final int WARN_INCREMENT = 10;
+
+ /**
+ * Hash function tailored to native pointers.
+ * Returns a value < MAIN_INDEX_SIZE.
+ */
+ private static int hash(long arg) {
+ return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+ }
+
+ /**
+ * Return the total number of pairs in the map.
+ */
+ int size() {
+ int size = 0;
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ size += a.size();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Remove ith entry from the hash bucket indicated by hash.
+ */
+ private void remove(int hash, int index) {
+ Long[] keyArray = mMainIndexKeys[hash];
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+ int size = valueArray.size(); // KeyArray may have extra elements.
+ // Move last entry into empty slot, and truncate at end.
+ if (index != size - 1) {
+ keyArray[index] = keyArray[size - 1];
+ valueArray.set(index, valueArray.get(size - 1));
+ }
+ valueArray.remove(size - 1);
+ // Just leave key array entry; it's unused. We only trust the valueArray size.
+ }
+
+ /**
+ * Look up the supplied key. If we have a non-cleared entry for it, return it.
+ */
+ BinderProxy get(long key) {
+ int myHash = hash(key);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray == null) {
+ return null;
+ }
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ int bucketSize = valueArray.size();
+ for (int i = 0; i < bucketSize; ++i) {
+ long foundKey = keyArray[i];
+ if (key == foundKey) {
+ WeakReference<BinderProxy> wr = valueArray.get(i);
+ BinderProxy bp = wr.get();
+ if (bp != null) {
+ return bp;
+ } else {
+ remove(myHash, i);
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+ /**
+ * Add the key-value pair to the map.
+ * Requires that the indicated key is not already in the map.
+ */
+ void set(long key, @NonNull BinderProxy value) {
+ int myHash = hash(key);
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ if (valueArray == null) {
+ valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+ mMainIndexKeys[myHash] = new Long[1];
+ }
+ int size = valueArray.size();
+ WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+ // First look for a cleared reference.
+ // This ensures that ArrayList size is bounded by the maximum occupancy of
+ // that bucket.
+ for (int i = 0; i < size; ++i) {
+ if (valueArray.get(i).get() == null) {
+ valueArray.set(i, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ keyArray[i] = key;
+ if (i < size - 1) {
+ // "Randomly" check one of the remaining entries in [i+1, size), so that
+ // needlessly long buckets are eventually pruned.
+ int rnd = Math.floorMod(++mRandom, size - (i + 1));
+ if (valueArray.get(i + 1 + rnd).get() == null) {
+ remove(myHash, i + 1 + rnd);
+ }
+ }
+ return;
+ }
+ }
+ valueArray.add(size, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray.length == size) {
+ // size >= 1, since we initially allocated one element
+ Long[] newArray = new Long[size + size / 2 + 2];
+ System.arraycopy(keyArray, 0, newArray, 0, size);
+ newArray[size] = key;
+ mMainIndexKeys[myHash] = newArray;
+ } else {
+ keyArray[size] = key;
+ }
+ if (size >= mWarnBucketSize) {
+ Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+ + " total = " + size());
+ mWarnBucketSize += WARN_INCREMENT;
+ }
+ }
+
+ // Corresponding ArrayLists in the following two arrays always have the same size.
+ // They contain no empty entries. However WeakReferences in the values ArrayLists
+ // may have been cleared.
+
+ // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+ // The values ArrayList has the proper size(), the corresponding keys array
+ // is always at least the same size, but may be larger.
+ // If either a particular keys array, or the corresponding values ArrayList
+ // are null, then they both are.
+ private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+ private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+ new ArrayList[MAIN_INDEX_SIZE];
+ }
+
+ private static ProxyMap sProxyMap = new ProxyMap();
+
+ /**
+ * Return a BinderProxy for IBinder.
+ * This method is thread-hostile! The (native) caller serializes getInstance() calls using
+ * gProxyLock.
+ * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+ * in use, then we return the same bp.
+ *
+ * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+ * Takes ownership of nativeData iff <result>.mNativeData == nativeData. Caller will usually
+ * delete nativeData if that's not the case.
+ * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+ */
+ private static BinderProxy getInstance(long nativeData, long iBinder) {
+ BinderProxy result = sProxyMap.get(iBinder);
+ if (result == null) {
+ result = new BinderProxy(nativeData);
+ sProxyMap.set(iBinder, result);
+ }
+ return result;
+ }
+
+ private BinderProxy(long nativeData) {
+ mNativeData = nativeData;
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData);
+ }
+
+ /**
+ * Guestimate of native memory associated with a BinderProxy.
+ * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+ * that points back to us. We guess high since it includes a GlobalRef, which
+ * may be in short supply.
+ */
+ private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+ // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+ // to avoid some initialization ordering issues.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+ }
+
public native boolean pingBinder();
public native boolean isBinderAlive();
@@ -776,6 +984,7 @@ final class BinderProxy implements IBinder {
}
}
+ private static native long getNativeFinalizer();
public native String getInterfaceDescriptor() throws RemoteException;
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
@@ -830,21 +1039,6 @@ final class BinderProxy implements IBinder {
}
}
- BinderProxy() {
- mSelf = new WeakReference(this);
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- private native final void destroy();
-
private static final void sendDeathNotice(DeathRecipient recipient) {
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
@@ -856,19 +1050,9 @@ final class BinderProxy implements IBinder {
}
}
- // This WeakReference to "this" is used only by native code to "attach" to the
- // native IBinder object.
- // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
- // non-null value after the BinderProxy is enqueued for finalization.
- // Used only once immediately after construction.
- // TODO: Consider making the extra native-to-java call to compute this on the fly.
- final private WeakReference mSelf;
-
- // Native pointer to the wrapped native IBinder object. Counted as strong reference.
- private long mObject;
-
- // Native pointer to native DeathRecipientList. Counted as strong reference.
- // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
- // hold a weak reference that can be temporarily promoted.
- private long mOrgue;
+ /**
+ * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+ * native IBinder object, and a DeathRecipientList.
+ */
+ private final long mNativeData;
}
diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java
index 87c65ecc..2176a785 100644
--- a/android/os/IServiceManager.java
+++ b/android/os/IServiceManager.java
@@ -45,13 +45,13 @@ public interface IServiceManager extends IInterface
* Place a new @a service called @a name into the service
* manager.
*/
- void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
- throws RemoteException;
+ void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags)
+ throws RemoteException;
/**
* Return a list of all currently running services.
*/
- String[] listServices(int dumpPriority) throws RemoteException;
+ String[] listServices(int dumpFlags) throws RemoteException;
/**
* Assign a permission controller to the service manager. After set, this
@@ -72,9 +72,13 @@ public interface IServiceManager extends IInterface
/*
* Must update values in IServiceManager.h
*/
- int DUMP_PRIORITY_CRITICAL = 1 << 0;
- int DUMP_PRIORITY_HIGH = 1 << 1;
- int DUMP_PRIORITY_NORMAL = 1 << 2;
- int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH
- | DUMP_PRIORITY_NORMAL;
+ /* Allows services to dump sections according to priorities. */
+ int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
+ int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
+ int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+ int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+ | DUMP_FLAG_PRIORITY_NORMAL;
+ /* Allows services to dump sections in protobuf format. */
+ int DUMP_FLAG_PROTO = 1 << 3;
+
}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index 857e8a60..c2cf3967 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -561,6 +561,22 @@ public final class Parcel {
mClassCookies = from.mClassCookies;
}
+ /** @hide */
+ public Map<Class, Object> copyClassCookies() {
+ return new ArrayMap<>(mClassCookies);
+ }
+
+ /** @hide */
+ public void putClassCookies(Map<Class, Object> cookies) {
+ if (cookies == null) {
+ return;
+ }
+ if (mClassCookies == null) {
+ mClassCookies = new ArrayMap<>();
+ }
+ mClassCookies.putAll(cookies);
+ }
+
/**
* Report whether the parcel contains any marshalled file descriptors.
*/
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index 34c78455..42ec315c 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,29 @@
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.
@@ -27,14 +47,32 @@ public final class ServiceManager {
* @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;
}
/**
- * Is not supposed to return null, but that is fine for layoutlib.
+ * Returns a reference to a service with the given name, or throws
+ * {@link NullPointerException} if none is found.
+ *
+ * @hide
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- throw new ServiceNotFoundException(name);
+ final IBinder binder = getService(name);
+ if (binder != null) {
+ return binder;
+ } else {
+ throw new ServiceNotFoundException(name);
+ }
}
/**
@@ -45,7 +83,39 @@ public final class ServiceManager {
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- // pass
+ addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+ }
+
+ /**
+ * 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) {
+ addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+ }
+
+ /**
+ * 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
+ * @param dumpPriority supported dump priority levels as a bitmask
+ * to access this service
+ */
+ public static void addService(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ try {
+ getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in addService", e);
+ }
}
/**
@@ -53,7 +123,17 @@ public final class ServiceManager {
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- return null;
+ 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;
+ }
}
/**
@@ -62,9 +142,12 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- // actual implementation returns null sometimes, so it's ok
- // to return null instead of an empty list.
- return null;
+ try {
+ return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in listServices", e);
+ return null;
+ }
}
/**
@@ -76,7 +159,10 @@ public final class ServiceManager {
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- // pass
+ if (sCache.size() != 0) {
+ throw new IllegalStateException("setServiceCache may only be called once");
+ }
+ sCache.putAll(cache);
}
/**
@@ -87,7 +173,6 @@ 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/ShellCommand.java b/android/os/ShellCommand.java
index e4a12e84..6223235e 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -17,6 +17,7 @@
package android.os;
import android.util.Slog;
+
import com.android.internal.util.FastPrintWriter;
import java.io.BufferedInputStream;
@@ -118,13 +119,33 @@ public abstract class ShellCommand {
mErrPrintWriter.flush();
}
if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
- mResultReceiver.send(res, null);
+ if (mResultReceiver != null) {
+ mResultReceiver.send(res, null);
+ }
}
if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
return res;
}
/**
+ * Adopt the ResultReceiver that was given to this shell command from it, taking
+ * it over. Primarily used to dispatch to another shell command. Once called,
+ * this shell command will no longer return its own result when done.
+ */
+ public ResultReceiver adoptResultReceiver() {
+ ResultReceiver rr = mResultReceiver;
+ mResultReceiver = null;
+ return rr;
+ }
+
+ /**
+ * Return the raw FileDescriptor for the output stream.
+ */
+ public FileDescriptor getOutFileDescriptor() {
+ return mOut;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's output data stream.
*/
public OutputStream getRawOutputStream() {
@@ -145,6 +166,13 @@ public abstract class ShellCommand {
}
/**
+ * Return the raw FileDescriptor for the error stream.
+ */
+ public FileDescriptor getErrFileDescriptor() {
+ return mErr;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's error output data stream.
*/
public OutputStream getRawErrorStream() {
@@ -168,6 +196,13 @@ public abstract class ShellCommand {
}
/**
+ * Return the raw FileDescriptor for the input stream.
+ */
+ public FileDescriptor getInFileDescriptor() {
+ return mIn;
+ }
+
+ /**
* Return direct raw access (not buffered) to the command's input data stream.
*/
public InputStream getRawInputStream() {
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index 826ec1eb..ee3e5bc9 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -20,7 +20,6 @@ import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -35,6 +34,7 @@ import android.util.Singleton;
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -48,8 +48,10 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Deque;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -352,8 +354,8 @@ public final class StrictMode {
} else {
msg = "StrictMode policy violation:";
}
- if (info.crashInfo != null) {
- Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+ if (info.hasStackTrace()) {
+ Log.d(TAG, msg + " " + info.getStackTrace());
} else {
Log.d(TAG, msg + " missing stack trace!");
}
@@ -1247,28 +1249,6 @@ public final class StrictMode {
}
}
- /** Like parsePolicyFromMessage(), but returns the violation. */
- private static int parseViolationFromMessage(String message) {
- if (message == null) {
- return 0;
- }
- int violationIndex = message.indexOf("violation=");
- if (violationIndex == -1) {
- return 0;
- }
- int numberStartIndex = violationIndex + "violation=".length();
- int numberEndIndex = message.indexOf(' ', numberStartIndex);
- if (numberEndIndex == -1) {
- numberEndIndex = message.length();
- }
- String violationString = message.substring(numberStartIndex, numberEndIndex);
- try {
- return Integer.parseInt(violationString);
- } catch (NumberFormatException e) {
- return 0;
- }
- }
-
private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
new ThreadLocal<ArrayList<ViolationInfo>>() {
@Override
@@ -1516,7 +1496,7 @@ public final class StrictMode {
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
void handleViolation(final ViolationInfo info) {
- if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+ if (info == null || !info.hasStackTrace()) {
Log.wtf(TAG, "unexpected null stacktrace");
return;
}
@@ -1530,7 +1510,7 @@ public final class StrictMode {
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
- if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+ if (info.getStackTrace().equals(previous.getStackTrace())) {
// Duplicate. Don't log.
return;
}
@@ -1576,8 +1556,7 @@ public final class StrictMode {
}
if (violationMaskSubset != 0) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- violationMaskSubset |= violationBit;
+ violationMaskSubset |= info.getViolationBit();
final int savedPolicyMask = getThreadPolicyMask();
final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
@@ -1622,8 +1601,7 @@ public final class StrictMode {
}
private static void executeDeathPenalty(ViolationInfo info) {
- int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
- throw new StrictModeViolation(info.policy, violationBit, null);
+ throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
}
/**
@@ -1670,7 +1648,7 @@ public final class StrictMode {
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
public void report(String message, Throwable allocationSite) {
- onVmPolicyViolation(message, allocationSite);
+ onVmPolicyViolation(allocationSite);
}
}
@@ -1709,7 +1687,7 @@ public final class StrictMode {
long instances = instanceCounts[i];
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr.getMessage(), tr);
+ onVmPolicyViolation(tr);
}
}
}
@@ -1833,22 +1811,24 @@ public final class StrictMode {
/** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
- onVmPolicyViolation(message, originStack);
+ Throwable t = new Throwable(message);
+ t.setStackTrace(originStack.getStackTrace());
+ onVmPolicyViolation(t);
}
/** @hide */
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
public static void onIntentReceiverLeaked(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
public static void onServiceConnectionLeaked(Throwable originStack) {
- onVmPolicyViolation(null, originStack);
+ onVmPolicyViolation(originStack);
}
/** @hide */
@@ -1857,7 +1837,7 @@ public final class StrictMode {
if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
throw new FileUriExposedException(message);
} else {
- onVmPolicyViolation(null, new Throwable(message));
+ onVmPolicyViolation(new Throwable(message));
}
}
@@ -1869,7 +1849,7 @@ public final class StrictMode {
+ location
+ " without permission grant flags; did you forget"
+ " FLAG_GRANT_READ_URI_PERMISSION?";
- onVmPolicyViolation(null, new Throwable(message));
+ onVmPolicyViolation(new Throwable(message));
}
/** @hide */
@@ -1899,10 +1879,9 @@ public final class StrictMode {
} catch (UnknownHostException ignored) {
}
}
-
+ msg += HexDump.dumpHexString(firstPacket).trim() + " ";
final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
- onVmPolicyViolation(
- HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath);
+ onVmPolicyViolation(new Throwable(msg), forceDeath);
}
/** @hide */
@@ -1912,24 +1891,23 @@ public final class StrictMode {
/** @hide */
public static void onUntaggedSocket() {
- onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+ onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
}
// Map from VM violation fingerprint to uptime millis.
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
/** @hide */
- public static void onVmPolicyViolation(String message, Throwable originStack) {
- onVmPolicyViolation(message, originStack, false);
+ public static void onVmPolicyViolation(Throwable originStack) {
+ onVmPolicyViolation(originStack, false);
}
/** @hide */
- public static void onVmPolicyViolation(
- String message, Throwable originStack, boolean forceDeath) {
+ public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
- final ViolationInfo info = new ViolationInfo(message, originStack, sVmPolicy.mask);
+ final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
// Erase stuff not relevant for process-wide violations
info.numAnimationsRunning = 0;
@@ -2027,21 +2005,14 @@ public final class StrictMode {
* read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
- // Our own stack trace to append
- StringWriter sw = new StringWriter();
- sw.append("# via Binder call with stack:\n");
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
- new LogStackTrace().printStackTrace(pw);
- pw.flush();
- String ourStack = sw.toString();
-
+ LogStackTrace localCallSite = new LogStackTrace();
final int policyMask = getThreadPolicyMask();
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
final int size = p.readInt();
for (int i = 0; i < size; i++) {
final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
- info.crashInfo.appendStackTrace(ourStack);
+ info.addLocalStack(localCallSite);
BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
if (policy instanceof AndroidBlockGuardPolicy) {
((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
@@ -2254,7 +2225,7 @@ public final class StrictMode {
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onUnbufferedIO();
+ policy.onUnbufferedIO();
}
/** @hide */
@@ -2264,7 +2235,7 @@ public final class StrictMode {
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onReadFromDisk();
+ policy.onReadFromDisk();
}
/** @hide */
@@ -2274,12 +2245,11 @@ public final class StrictMode {
// StrictMode not enabled.
return;
}
- ((AndroidBlockGuardPolicy) policy).onWriteToDisk();
+ policy.onWriteToDisk();
}
- // Guarded by StrictMode.class
- private static final HashMap<Class, Integer> sExpectedActivityInstanceCount =
- new HashMap<Class, Integer>();
+ @GuardedBy("StrictMode.class")
+ private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
/**
* Returns an object that is used to track instances of activites. The activity should store a
@@ -2354,7 +2324,7 @@ public final class StrictMode {
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr.getMessage(), tr);
+ onVmPolicyViolation(tr);
}
}
@@ -2366,10 +2336,15 @@ public final class StrictMode {
*/
@TestApi
public static final class ViolationInfo implements Parcelable {
- public final String message;
+ /** Stack and violation details. */
+ @Nullable private final Throwable mThrowable;
+
+ private final Deque<Throwable> mBinderStack = new ArrayDeque<>();
- /** Stack and other stuff info. */
- @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
+ /** Memoized stack trace of full violation. */
+ @Nullable private String mStackTrace;
+ /** Memoized violation bit. */
+ private int mViolationBit;
/** The strict mode policy mask at the time of violation. */
public final int policy;
@@ -2404,19 +2379,13 @@ public final class StrictMode {
/** Create an uninitialized instance of ViolationInfo */
public ViolationInfo() {
- message = null;
- crashInfo = null;
+ mThrowable = null;
policy = 0;
}
- public ViolationInfo(Throwable tr, int policy) {
- this(null, tr, policy);
- }
-
/** Create an instance of ViolationInfo initialized from an exception. */
- public ViolationInfo(String message, Throwable tr, int policy) {
- this.message = message;
- crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+ public ViolationInfo(Throwable tr, int policy) {
+ this.mThrowable = tr;
violationUptimeMillis = SystemClock.uptimeMillis();
this.policy = policy;
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
@@ -2446,11 +2415,91 @@ public final class StrictMode {
}
}
+ /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
+ public String getStackTrace() {
+ if (mThrowable != null && mStackTrace == null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ mThrowable.printStackTrace(pw);
+ for (Throwable t : mBinderStack) {
+ pw.append("# via Binder call with stack:\n");
+ t.printStackTrace(pw);
+ }
+ pw.flush();
+ pw.close();
+ mStackTrace = sw.toString();
+ }
+ return mStackTrace;
+ }
+
+ /**
+ * Optional message describing this violation.
+ *
+ * @hide
+ */
+ @TestApi
+ public String getViolationDetails() {
+ if (mThrowable != null) {
+ return mThrowable.getMessage();
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * If this violation has a useful stack trace.
+ *
+ * @hide
+ */
+ public boolean hasStackTrace() {
+ return mThrowable != null;
+ }
+
+ /**
+ * Add a {@link Throwable} from the current process that caused the underlying violation.
+ *
+ * @hide
+ */
+ void addLocalStack(Throwable t) {
+ mBinderStack.addFirst(t);
+ }
+
+ /**
+ * Retrieve the type of StrictMode violation.
+ *
+ * @hide
+ */
+ int getViolationBit() {
+ if (mThrowable == null || mThrowable.getMessage() == null) {
+ return 0;
+ }
+ if (mViolationBit != 0) {
+ return mViolationBit;
+ }
+ String message = mThrowable.getMessage();
+ int violationIndex = message.indexOf("violation=");
+ if (violationIndex == -1) {
+ return 0;
+ }
+ int numberStartIndex = violationIndex + "violation=".length();
+ int numberEndIndex = message.indexOf(' ', numberStartIndex);
+ if (numberEndIndex == -1) {
+ numberEndIndex = message.length();
+ }
+ String violationString = message.substring(numberStartIndex, numberEndIndex);
+ try {
+ mViolationBit = Integer.parseInt(violationString);
+ return mViolationBit;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
@Override
public int hashCode() {
int result = 17;
- if (crashInfo != null) {
- result = 37 * result + crashInfo.stackTrace.hashCode();
+ if (mThrowable != null) {
+ result = 37 * result + mThrowable.hashCode();
}
if (numAnimationsRunning != 0) {
result *= 37;
@@ -2478,11 +2527,10 @@ public final class StrictMode {
* should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
- message = in.readString();
- if (in.readInt() != 0) {
- crashInfo = new ApplicationErrorReport.CrashInfo(in);
- } else {
- crashInfo = null;
+ mThrowable = (Throwable) in.readSerializable();
+ int binderStackSize = in.readInt();
+ for (int i = 0; i < binderStackSize; i++) {
+ mBinderStack.add((Throwable) in.readSerializable());
}
int rawPolicy = in.readInt();
if (unsetGatheringBit) {
@@ -2502,12 +2550,10 @@ public final class StrictMode {
/** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(message);
- if (crashInfo != null) {
- dest.writeInt(1);
- crashInfo.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
+ dest.writeSerializable(mThrowable);
+ dest.writeInt(mBinderStack.size());
+ for (Throwable t : mBinderStack) {
+ dest.writeSerializable(t);
}
int start = dest.dataPosition();
dest.writeInt(policy);
@@ -2542,8 +2588,8 @@ public final class StrictMode {
/** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
- if (crashInfo != null) {
- crashInfo.dump(pw, prefix);
+ if (mThrowable != null) {
+ pw.println(prefix + "stackTrace: " + getStackTrace());
}
pw.println(prefix + "policy: " + policy);
if (durationMillis != -1) {
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 8c688713..c54b72d4 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -319,6 +319,23 @@ public class UserManager {
public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
/**
+ * Specifies if date, time and timezone configuring is disallowed.
+ *
+ * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+ * time and timezone setting on the entire device and all users will be affected. When it's set
+ * by profile owners, it's only applied to the managed user.
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+
+ /**
* Specifies if a user is disallowed from configuring Tethering
* & portable hotspots. This can only be set by device owners and profile owners on the
* primary user. The default value is <code>false</code>.
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index a27df3a7..62f4bf58 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -3096,6 +3096,12 @@ public final class Settings {
private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
/**
+ * The display color mode.
+ * @hide
+ */
+ public static final String DISPLAY_COLOR_MODE = "display_color_mode";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
diff --git a/android/security/NetworkSecurityPolicy.java b/android/security/NetworkSecurityPolicy.java
index 812c956f..0c4eedab 100644
--- a/android/security/NetworkSecurityPolicy.java
+++ b/android/security/NetworkSecurityPolicy.java
@@ -16,7 +16,6 @@
package android.security;
-import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.security.net.config.ApplicationConfig;
@@ -63,7 +62,8 @@ public class NetworkSecurityPolicy {
* traffic from applications is handled by higher-level network stacks/components which can
* honor this aspect of the policy.
*
- * <p>NOTE: {@link android.webkit.WebView} does not honor this flag.
+ * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level
+ * 26 and up.
*/
public boolean isCleartextTrafficPermitted() {
return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
diff --git a/android/security/net/config/NetworkSecurityConfig.java b/android/security/net/config/NetworkSecurityConfig.java
index b9e55054..52f48ef8 100644
--- a/android/security/net/config/NetworkSecurityConfig.java
+++ b/android/security/net/config/NetworkSecurityConfig.java
@@ -164,7 +164,8 @@ public final class NetworkSecurityConfig {
* <p>
* The default configuration has the following properties:
* <ol>
- * <li>Cleartext traffic is permitted for non-ephemeral apps.</li>
+ * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
+ * is allowed by default.</li>
* <li>Cleartext traffic is not permitted for ephemeral apps.</li>
* <li>HSTS is not enforced.</li>
* <li>No certificate pinning is used.</li>
@@ -183,7 +184,8 @@ public final class NetworkSecurityConfig {
// System certificate store, does not bypass static pins.
.addCertificatesEntryRef(
new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
- final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
+ final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
+ && info.targetSandboxVersion < 2;
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java
index fd30857d..b8e8b19f 100644
--- a/android/service/autofill/CustomDescription.java
+++ b/android/service/autofill/CustomDescription.java
@@ -32,7 +32,7 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
/**
- * Defines a custom description for the Save UI affordance.
+ * Defines a custom description for the autofill save UI.
*
* <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
* for example, when the screen contains a credit card, it could display a logo of the credit card
@@ -131,7 +131,7 @@ public final class CustomDescription implements Parcelable {
* <p><b>Note:</b> If any child view of presentation triggers a
* {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
* on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
- * it might not be triggered or the Save affordance might not be shown when its activity
+ * it might not be triggered or the autofill save UI might not be shown when its activity
* is finished:
* <ul>
* <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index d2033fa9..2f6342af 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -31,6 +31,8 @@ import android.os.Parcelable;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
+import com.android.internal.util.Preconditions;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -51,9 +53,16 @@ public final class FillResponse implements Parcelable {
*/
public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+ /**
+ * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill
+ * only for the activiy associated with the {@link FillResponse}, instead of the whole app.
+ */
+ public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+
/** @hide */
@IntDef(flag = true, value = {
- FLAG_TRACK_CONTEXT_COMMITED
+ FLAG_TRACK_CONTEXT_COMMITED,
+ FLAG_DISABLE_ACTIVITY_ONLY
})
@Retention(RetentionPolicy.SOURCE)
@interface FillResponseFlags {}
@@ -65,6 +74,7 @@ public final class FillResponse implements Parcelable {
private final @Nullable IntentSender mAuthentication;
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
+ private final long mDisableDuration;
private final int mFlags;
private int mRequestId;
@@ -76,6 +86,7 @@ public final class FillResponse implements Parcelable {
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
+ mDisableDuration = builder.mDisableDuration;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -116,6 +127,11 @@ public final class FillResponse implements Parcelable {
}
/** @hide */
+ public long getDisableDuration() {
+ return mDisableDuration;
+ }
+
+ /** @hide */
public int getFlags() {
return mFlags;
}
@@ -150,6 +166,7 @@ public final class FillResponse implements Parcelable {
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
+ private long mDisableDuration;
private int mFlags;
private boolean mDestroyed;
@@ -187,23 +204,25 @@ public final class FillResponse implements Parcelable {
* which is used to visualize visualize the response for triggering the authentication
* flow.
*
- * <p></><strong>Note:</strong> Do not make the provided pending intent
+ * <p><b>Note:</b> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
* platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
* @param presentation The presentation to visualize the response.
- * @param ids id of Views that when focused will display the authentication UI affordance.
+ * @param ids id of Views that when focused will display the authentication UI.
*
* @return This builder.
* @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
- * neither {@code authentication} nor {@code presentation} is non-{@code null}.
+ * both {@code authentication} and {@code presentation} are {@code null}, or if
+ * both {@code authentication} and {@code presentation} are non-{@code null}
*
* @see android.app.PendingIntent#getIntentSender()
*/
public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
@Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (ids == null || ids.length == 0) {
throw new IllegalArgumentException("ids cannot be null or empry");
}
@@ -226,6 +245,7 @@ public final class FillResponse implements Parcelable {
* text field representing the result of a Captcha challenge.
*/
public Builder setIgnoredIds(AutofillId...ids) {
+ throwIfDestroyed();
mIgnoredIds = ids;
return this;
}
@@ -246,6 +266,7 @@ public final class FillResponse implements Parcelable {
*/
public @NonNull Builder addDataset(@Nullable Dataset dataset) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
if (dataset == null) {
return this;
}
@@ -265,6 +286,7 @@ public final class FillResponse implements Parcelable {
*/
public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
throwIfDestroyed();
+ throwIfDisableAutofillCalled();
mSaveInfo = saveInfo;
return this;
}
@@ -295,30 +317,82 @@ public final class FillResponse implements Parcelable {
/**
* Sets flags changing the response behavior.
*
- * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+ * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
+ * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
*
* @return This builder.
*/
public Builder setFlags(@FillResponseFlags int flags) {
throwIfDestroyed();
- mFlags = flags;
+ mFlags = Preconditions.checkFlagsArgument(flags,
+ FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+ return this;
+ }
+
+ /**
+ * Disables autofill for the app or activity.
+ *
+ * <p>This method is useful to optimize performance in cases where the service knows it
+ * can not autofill an app&mdash;for example, when the service has a list of "blacklisted"
+ * apps such as office suites.
+ *
+ * <p>By default, it disables autofill for all activities in the app, unless the response is
+ * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
+ *
+ * <p>Autofill for the app or activity is automatically re-enabled after any of the
+ * following conditions:
+ *
+ * <ol>
+ * <li>{@code duration} milliseconds have passed.
+ * <li>The autofill service for the user has changed.
+ * <li>The device has rebooted.
+ * </ol>
+ *
+ * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
+ * disabled for autofill until they finish and restart.
+ *
+ * @param duration duration to disable autofill, in milliseconds.
+ *
+ * @return this builder
+ *
+ * @throws IllegalArgumentException if {@code duration} is not a positive number.
+ * @throws IllegalStateException if either {@link #addDataset(Dataset)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or
+ * {@link #setSaveInfo(SaveInfo)} was already called.
+ */
+ public Builder disableAutofill(long duration) {
+ throwIfDestroyed();
+ if (duration <= 0) {
+ throw new IllegalArgumentException("duration must be greater than 0");
+ }
+ if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+ throw new IllegalStateException("disableAutofill() must be the only method called");
+ }
+
+ mDisableDuration = duration;
return this;
}
/**
* Builds a new {@link FillResponse} instance.
*
- * <p>You must provide at least one dataset or some savable ids or an authentication with a
- * presentation view.
+ * @throws IllegalStateException if any of the following conditions occur:
+ * <ol>
+ * <li>{@link #build()} was already called.
+ * <li>No call was made to {@link #addDataset(Dataset)},
+ * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+ * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}.
+ * </ol>
*
* @return A built response.
*/
public FillResponse build() {
throwIfDestroyed();
- if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
- throw new IllegalArgumentException("need to provide at least one DataSet or a "
- + "SaveInfo or an authentication with a presentation");
+ if (mAuthentication == null && mDatasets == null && mSaveInfo == null
+ && mDisableDuration == 0) {
+ throw new IllegalStateException("need to provide at least one DataSet or a "
+ + "SaveInfo or an authentication with a presentation or disable autofill");
}
mDestroyed = true;
return new FillResponse(this);
@@ -329,6 +403,12 @@ public final class FillResponse implements Parcelable {
throw new IllegalStateException("Already called #build()");
}
}
+
+ private void throwIfDisableAutofillCalled() {
+ if (mDisableDuration > 0) {
+ throw new IllegalStateException("Already called #disableAutofill()");
+ }
+ }
}
/////////////////////////////////////
@@ -348,6 +428,7 @@ public final class FillResponse implements Parcelable {
.append(", hasAuthentication=").append(mAuthentication != null)
.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+ .append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
.append("]")
.toString();
@@ -371,6 +452,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mAuthentication, flags);
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
+ parcel.writeLong(mDisableDuration);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -402,6 +484,10 @@ public final class FillResponse implements Parcelable {
}
builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
+ final long disableDuration = parcel.readLong();
+ if (disableDuration > 0) {
+ builder.disableAutofill(disableDuration);
+ }
builder.setFlags(parcel.readInt());
final FillResponse response = builder.build();
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index fde2416f..9a1dcbb2 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -535,14 +535,15 @@ public final class SaveInfo implements Parcelable {
* 16 digits, or 15 digits starting with 108:
*
* <pre class="prettyprint">
- * import android.service.autofill.Validators;
+ * import static android.service.autofill.Validators.and;
+ * import static android.service.autofill.Validators.or;
*
* Validator validator =
* and(
* new LuhnChecksumValidator(ccNumberId),
* or(
- * new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")),
- * new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$"))
+ * new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
+ * new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
* )
* );
* </pre>
@@ -562,14 +563,14 @@ public final class SaveInfo implements Parcelable {
* 4 digits on each field:
*
* <pre class="prettyprint">
- * import android.service.autofill.Validators;
+ * import static android.service.autofill.Validators.and;
*
* Validator validator =
* and(
- * new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")),
- * new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$"))
+ * new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
+ * new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
* );
* </pre>
*
diff --git a/android/service/autofill/Transformation.java b/android/service/autofill/Transformation.java
index 4cef261d..aa8bc9b9 100644
--- a/android/service/autofill/Transformation.java
+++ b/android/service/autofill/Transformation.java
@@ -19,7 +19,7 @@ package android.service.autofill;
* Helper class used to change a child view of a {@link android.widget.RemoteViews presentation
* template} at runtime, using the values of fields contained in the screen.
*
- * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance.
+ * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI.
*/
public interface Transformation {
}
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
index edc32415..2684c58a 100644
--- a/android/support/car/widget/CarRecyclerView.java
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -18,8 +18,6 @@ package android.support.car.widget;
import android.content.Context;
import android.graphics.Canvas;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
@@ -27,9 +25,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
/**
* Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
*
@@ -37,11 +32,7 @@ import java.lang.reflect.InvocationTargetException;
* #setFadeLastItem(boolean)}.
*/
public class CarRecyclerView extends RecyclerView {
- private static final String PARCEL_CLASS = "android.os.Parcel";
- private static final String SAVED_STATE_CLASS =
- "android.support.v7.widget.RecyclerView.SavedState";
private boolean mFadeLastItem;
- private Constructor<?> mSavedStateConstructor;
/**
* If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
* called. However, we want to make sure that the list still snaps to the next page when this
@@ -64,30 +55,6 @@ public class CarRecyclerView extends RecyclerView {
}
@Override
- protected void onRestoreInstanceState(Parcelable state) {
- if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
- if (mSavedStateConstructor == null) {
- mSavedStateConstructor = getSavedStateConstructor();
- }
- // Class loader mismatch, recreate from parcel.
- Parcel obtain = Parcel.obtain();
- state.writeToParcel(obtain, 0);
- try {
- Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain);
- super.onRestoreInstanceState(newState);
- } catch (InstantiationException
- | IllegalAccessException
- | IllegalArgumentException
- | InvocationTargetException e) {
- // Fail loudy here.
- throw new RuntimeException(e);
- }
- } else {
- super.onRestoreInstanceState(state);
- }
- }
-
- @Override
public boolean fling(int velocityX, int velocityY) {
mWasFlingCalledForGesture = true;
return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
@@ -158,35 +125,6 @@ public class CarRecyclerView extends RecyclerView {
smoothScrollToPosition(pageDownPosition);
}
- /** Sets {@link #mSavedStateConstructor} to private SavedState constructor. */
- private Constructor<?> getSavedStateConstructor() {
- Class<?> savedStateClass = null;
- // Find package private subclass RecyclerView$SavedState.
- for (Class<?> c : RecyclerView.class.getDeclaredClasses()) {
- if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) {
- savedStateClass = c;
- break;
- }
- }
- if (savedStateClass == null) {
- throw new RuntimeException("RecyclerView$SavedState not found!");
- }
- // Find constructor that takes a {@link Parcel}.
- for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) {
- Class<?>[] parameterTypes = c.getParameterTypes();
- if (parameterTypes.length == 1
- && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) {
- mSavedStateConstructor = c;
- mSavedStateConstructor.setAccessible(true);
- break;
- }
- }
- if (mSavedStateConstructor == null) {
- throw new RuntimeException("RecyclerView$SavedState constructor not found!");
- }
- return mSavedStateConstructor;
- }
-
/**
* Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its
* children instead.
diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java
index 94de9b86..477a8d62 100644
--- a/android/support/design/widget/CoordinatorLayout.java
+++ b/android/support/design/widget/CoordinatorLayout.java
@@ -610,8 +610,8 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
- final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
- context.getClassLoader());
+ final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
+ .loadClass(fullName);
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index b0e3d5fe..a18bcf32 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java
index 8ef0a355..86024d90 100644
--- a/android/support/mediacompat/testlib/MediaBrowserConstants.java
+++ b/android/support/mediacompat/testlib/MediaBrowserConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
index 1de00efc..5fa086b3 100644
--- a/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index 79381e5e..95be1621 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 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.
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index f3773895..ae31c4fb 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -682,7 +682,6 @@ public class BrowseFragment extends BaseFragment {
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
- private PresenterSelector mWrappingPresenterSelector;
private int mHeadersState = HEADERS_ENABLED;
private int mBrandColor = Color.TRANSPARENT;
@@ -767,7 +766,11 @@ public class BrowseFragment extends BaseFragment {
* Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
* DividerRow and PageRow.
*/
- private void createAndSetWrapperPresenter() {
+ private void updateWrapperPresenter() {
+ if (mAdapter == null) {
+ mAdapterPresenter = null;
+ return;
+ }
final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
if (adapterPresenter == null) {
throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
@@ -812,18 +815,16 @@ public class BrowseFragment extends BaseFragment {
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
- createAndSetWrapperPresenter();
+ updateWrapperPresenter();
if (getView() == null) {
return;
}
- replaceMainFragment(mSelectedPosition);
- if (adapter != null) {
- if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
- }
- mHeadersFragment.setAdapter(adapter);
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(
+ adapter == null ? null : new ListRowDataAdapter(adapter));
}
+ mHeadersFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 03b3c8a6..4a2502a8 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -679,7 +679,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
- private PresenterSelector mWrappingPresenterSelector;
private int mHeadersState = HEADERS_ENABLED;
private int mBrandColor = Color.TRANSPARENT;
@@ -764,7 +763,11 @@ public class BrowseSupportFragment extends BaseSupportFragment {
* Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
* DividerRow and PageRow.
*/
- private void createAndSetWrapperPresenter() {
+ private void updateWrapperPresenter() {
+ if (mAdapter == null) {
+ mAdapterPresenter = null;
+ return;
+ }
final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
if (adapterPresenter == null) {
throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
@@ -809,18 +812,16 @@ public class BrowseSupportFragment extends BaseSupportFragment {
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
- createAndSetWrapperPresenter();
+ updateWrapperPresenter();
if (getView() == null) {
return;
}
- replaceMainFragment(mSelectedPosition);
- if (adapter != null) {
- if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
- }
- mHeadersSupportFragment.setAdapter(adapter);
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(
+ adapter == null ? null : new ListRowDataAdapter(adapter));
}
+ mHeadersSupportFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index af37f77a..dded0715 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2693,6 +2693,40 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
+ // Observer is registered on Adapter to invalidate saved instance state
+ final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ mChildrenStates.clear();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ if (DEBUG) {
+ Log.v(getTag(), "onItemRangeChanged positionStart "
+ + positionStart + " itemCount " + itemCount);
+ }
+ for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
+ mChildrenStates.remove(i);
+ }
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ mChildrenStates.clear();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ mChildrenStates.clear();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ mChildrenStates.clear();
+ }
+ };
+
@Override
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
@@ -2704,14 +2738,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
- mChildrenStates.clear();
}
@Override
public void onItemsChanged(RecyclerView recyclerView) {
if (DEBUG) Log.v(getTag(), "onItemsChanged");
mFocusPositionOffset = 0;
- mChildrenStates.clear();
}
@Override
@@ -2732,7 +2764,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
}
- mChildrenStates.clear();
}
@Override
@@ -2753,16 +2784,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
- if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
- + positionStart + " itemCount " + itemCount);
- for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
- mChildrenStates.remove(i);
- }
}
@Override
@@ -3460,12 +3481,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = NO_POSITION;
mFocusPositionOffset = 0;
mChildrenStates.clear();
+ oldAdapter.unregisterAdapterDataObserver(mObServer);
}
if (newAdapter instanceof FacetProviderAdapter) {
mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
} else {
mFacetProviderAdapter = null;
}
+ if (newAdapter != null) {
+ newAdapter.registerAdapterDataObserver(mObServer);
+ }
super.onAdapterChanged(oldAdapter, newAdapter);
}
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index cb3c59a6..614ff351 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -977,7 +977,11 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
continue;
}
fragment.mLifecycleRegistry.markState(state);
- markState(fragment.getChildFragmentManager(), state);
+
+ FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
+ if (childFragmentManager != null) {
+ markState(childFragmentManager, state);
+ }
}
}
}
diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java
index f707c8fc..dc20aa1f 100644
--- a/android/support/v7/widget/TooltipPopup.java
+++ b/android/support/v7/widget/TooltipPopup.java
@@ -56,7 +56,7 @@ class TooltipPopup {
TooltipPopup(Context context) {
mContext = context;
- mContentView = LayoutInflater.from(mContext).inflate(R.layout.tooltip, null);
+ mContentView = LayoutInflater.from(mContext).inflate(R.layout.abc_tooltip, null);
mMessageView = (TextView) mContentView.findViewById(R.id.message);
mLayoutParams.setTitle(getClass().getSimpleName());
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 99f8cfbf..6fc7d23a 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -210,6 +210,12 @@ public class CarrierConfigManager {
public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
/**
+ * Determine whether user can edit voicemail number in Settings.
+ */
+ public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL =
+ "editable_voicemail_number_setting_bool";
+
+ /**
* Since the default voicemail number is empty, if a SIM card does not have a voicemail number
* available the user cannot use voicemail. This flag allows the user to edit the voicemail
* number in such cases, and is false by default.
@@ -1615,6 +1621,13 @@ public class CarrierConfigManager {
public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
"skip_cf_fail_to_disable_dialog_bool";
+ /**
+ * List of the FAC (feature access codes) to dial as a normal call.
+ * @hide
+ */
+ public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+ "feature_access_codes_string_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1674,6 +1687,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+ sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
@@ -1887,6 +1901,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
+ sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
}
/**
diff --git a/android/telephony/CellIdentityCdma.java b/android/telephony/CellIdentityCdma.java
index b39b4c76..ddc938e6 100644
--- a/android/telephony/CellIdentityCdma.java
+++ b/android/telephony/CellIdentityCdma.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,6 +51,10 @@ public final class CellIdentityCdma implements Parcelable {
* to +90 degrees).
*/
private final int mLatitude;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
@@ -60,6 +65,8 @@ public final class CellIdentityCdma implements Parcelable {
mBasestationId = Integer.MAX_VALUE;
mLongitude = Integer.MAX_VALUE;
mLatitude = Integer.MAX_VALUE;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
@@ -75,19 +82,37 @@ public final class CellIdentityCdma implements Parcelable {
* @hide
*/
public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) {
+ this(nid, sid, bid, lon, lat, null, null);
+ }
+
+ /**
+ * public constructor
+ * @param nid Network Id 0..65535
+ * @param sid CDMA System Id 0..32767
+ * @param bid Base Station Id 0..65535
+ * @param lon Longitude is a decimal number ranges from -2592000
+ * to 2592000
+ * @param lat Latitude is a decimal number ranges from -1296000
+ * to 1296000
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @hide
+ */
+ public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal,
+ String alphas) {
mNetworkId = nid;
mSystemId = sid;
mBasestationId = bid;
mLongitude = lon;
mLatitude = lat;
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityCdma(CellIdentityCdma cid) {
- mNetworkId = cid.mNetworkId;
- mSystemId = cid.mSystemId;
- mBasestationId = cid.mBasestationId;
- mLongitude = cid.mLongitude;
- mLatitude = cid.mLatitude;
+ this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude,
+ cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityCdma copy() {
@@ -137,9 +162,26 @@ public final class CellIdentityCdma implements Parcelable {
return mLatitude;
}
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude);
+ return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude,
+ mAlphaLong, mAlphaShort);
}
@Override
@@ -153,11 +195,14 @@ public final class CellIdentityCdma implements Parcelable {
}
CellIdentityCdma o = (CellIdentityCdma) other;
+
return mNetworkId == o.mNetworkId &&
mSystemId == o.mSystemId &&
mBasestationId == o.mBasestationId &&
mLatitude == o.mLatitude &&
- mLongitude == o.mLongitude;
+ mLongitude == o.mLongitude &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
@@ -168,6 +213,8 @@ public final class CellIdentityCdma implements Parcelable {
sb.append(" mBasestationId="); sb.append(mBasestationId);
sb.append(" mLongitude="); sb.append(mLongitude);
sb.append(" mLatitude="); sb.append(mLatitude);
+ sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+ sb.append(" mAlphaShort="); sb.append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -188,15 +235,15 @@ public final class CellIdentityCdma implements Parcelable {
dest.writeInt(mBasestationId);
dest.writeInt(mLongitude);
dest.writeInt(mLatitude);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityCdma(Parcel in) {
- mNetworkId = in.readInt();
- mSystemId = in.readInt();
- mBasestationId = in.readInt();
- mLongitude = in.readInt();
- mLatitude = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(),
+ in.readString(), in.readString());
+
if (DBG) log("CellIdentityCdma(Parcel): " + toString());
}
diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java
index ec008e28..6276626a 100644
--- a/android/telephony/CellIdentityGsm.java
+++ b/android/telephony/CellIdentityGsm.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@ public final class CellIdentityGsm implements Parcelable {
private static final String LOG_TAG = "CellIdentityGsm";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 16-bit GSM Cell Identity described in TS 27.007, 0..65535
@@ -42,17 +39,27 @@ public final class CellIdentityGsm implements Parcelable {
private final int mArfcn;
// 6-bit Base Station Identity Code
private final int mBsic;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityGsm() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mArfcn = Integer.MAX_VALUE;
mBsic = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
* public constructor
@@ -64,7 +71,8 @@ public final class CellIdentityGsm implements Parcelable {
* @hide
*/
public CellIdentityGsm (int mcc, int mnc, int lac, int cid) {
- this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ String.valueOf(mcc), String.valueOf(mnc), null, null);
}
/**
@@ -79,39 +87,81 @@ public final class CellIdentityGsm implements Parcelable {
* @hide
*/
public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) {
- mMcc = mcc;
- mMnc = mnc;
+ this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ * public constructor
+ * @param lac 16-bit Location Area Code, 0..65535
+ * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity
+ * @param arfcn 16-bit GSM Absolute RF Channel Number
+ * @param bsic 6-bit Base Station Identity Code
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
+ String mncStr, String alphal, String alphas) {
mLac = lac;
mCid = cid;
mArfcn = arfcn;
- mBsic = bsic;
+ // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
+ // for inbound parcels
+ mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityGsm(CellIdentityGsm cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mLac = cid.mLac;
- mCid = cid.mCid;
- mArfcn = cid.mArfcn;
- mBsic = cid.mBsic;
+ this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityGsm copy() {
- return new CellIdentityGsm(this);
+ return new CellIdentityGsm(this);
}
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -144,6 +194,43 @@ public final class CellIdentityGsm implements Parcelable {
return mBsic;
}
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return Mobile Country Code in string format, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string format, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
/**
* @return Integer.MAX_VALUE, undefined for GSM
@@ -155,7 +242,7 @@ public final class CellIdentityGsm implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mLac, mCid);
+ return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort);
}
@Override
@@ -169,23 +256,27 @@ public final class CellIdentityGsm implements Parcelable {
}
CellIdentityGsm o = (CellIdentityGsm) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mLac == o.mLac &&
+ return mLac == o.mLac &&
mCid == o.mCid &&
mArfcn == o.mArfcn &&
- mBsic == o.mBsic;
+ mBsic == o.mBsic &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityGsm:{");
- sb.append(" mMcc=").append(mMcc);
- sb.append(" mMnc=").append(mMnc);
sb.append(" mLac=").append(mLac);
sb.append(" mCid=").append(mCid);
sb.append(" mArfcn=").append(mArfcn);
sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic));
+ sb.append(" mMcc=").append(mMccStr);
+ sb.append(" mMnc=").append(mMncStr);
+ sb.append(" mAlphaLong=").append(mAlphaLong);
+ sb.append(" mAlphaShort=").append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -201,26 +292,20 @@ public final class CellIdentityGsm implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mArfcn);
dest.writeInt(mBsic);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityGsm(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mLac = in.readInt();
- mCid = in.readInt();
- mArfcn = in.readInt();
- int bsic = in.readInt();
- // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
- // for inbound parcels
- if (bsic == 0xFF) bsic = Integer.MAX_VALUE;
- mBsic = bsic;
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
if (DBG) log("CellIdentityGsm(Parcel): " + toString());
}
@@ -229,16 +314,16 @@ public final class CellIdentityGsm implements Parcelable {
@SuppressWarnings("hiding")
public static final Creator<CellIdentityGsm> CREATOR =
new Creator<CellIdentityGsm>() {
- @Override
- public CellIdentityGsm createFromParcel(Parcel in) {
- return new CellIdentityGsm(in);
- }
+ @Override
+ public CellIdentityGsm createFromParcel(Parcel in) {
+ return new CellIdentityGsm(in);
+ }
- @Override
- public CellIdentityGsm[] newArray(int size) {
- return new CellIdentityGsm[size];
- }
- };
+ @Override
+ public CellIdentityGsm[] newArray(int size) {
+ return new CellIdentityGsm[size];
+ }
+ };
/**
* log
@@ -246,4 +331,4 @@ public final class CellIdentityGsm implements Parcelable {
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+} \ No newline at end of file
diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java
index ce743835..74d2966b 100644
--- a/android/telephony/CellIdentityLte.java
+++ b/android/telephony/CellIdentityLte.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@ public final class CellIdentityLte implements Parcelable {
private static final String LOG_TAG = "CellIdentityLte";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 28-bit cell identity
private final int mCi;
// physical cell id 0..503
@@ -42,17 +39,27 @@ public final class CellIdentityLte implements Parcelable {
private final int mTac;
// 18-bit Absolute RF Channel Number
private final int mEarfcn;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityLte() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mCi = Integer.MAX_VALUE;
mPci = Integer.MAX_VALUE;
mTac = Integer.MAX_VALUE;
mEarfcn = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
@@ -66,7 +73,7 @@ public final class CellIdentityLte implements Parcelable {
* @hide
*/
public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) {
- this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE);
+ this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
}
/**
@@ -81,21 +88,57 @@ public final class CellIdentityLte implements Parcelable {
* @hide
*/
public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
- mMcc = mcc;
- mMnc = mnc;
+ this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ *
+ * @param ci 28-bit Cell Identity
+ * @param pci Physical Cell Id 0..503
+ * @param tac 16-bit Tracking Area Code
+ * @param earfcn 18-bit LTE Absolute RF Channel Number
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
+ String mncStr, String alphal, String alphas) {
mCi = ci;
mPci = pci;
mTac = tac;
mEarfcn = earfcn;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityLte(CellIdentityLte cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mCi = cid.mCi;
- mPci = cid.mPci;
- mTac = cid.mTac;
- mEarfcn = cid.mEarfcn;
+ this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityLte copy() {
@@ -104,16 +147,20 @@ public final class CellIdentityLte implements Parcelable {
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -144,9 +191,46 @@ public final class CellIdentityLte implements Parcelable {
return mEarfcn;
}
+ /**
+ * @return Mobile Country Code in string format, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string format, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mCi, mPci, mTac);
+ return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort);
}
@Override
@@ -160,23 +244,27 @@ public final class CellIdentityLte implements Parcelable {
}
CellIdentityLte o = (CellIdentityLte) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mCi == o.mCi &&
+ return mCi == o.mCi &&
mPci == o.mPci &&
mTac == o.mTac &&
- mEarfcn == o.mEarfcn;
+ mEarfcn == o.mEarfcn &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityLte:{");
- sb.append(" mMcc="); sb.append(mMcc);
- sb.append(" mMnc="); sb.append(mMnc);
sb.append(" mCi="); sb.append(mCi);
sb.append(" mPci="); sb.append(mPci);
sb.append(" mTac="); sb.append(mTac);
sb.append(" mEarfcn="); sb.append(mEarfcn);
+ sb.append(" mMcc="); sb.append(mMccStr);
+ sb.append(" mMnc="); sb.append(mMncStr);
+ sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+ sb.append(" mAlphaShort="); sb.append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -192,22 +280,21 @@ public final class CellIdentityLte implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mCi);
dest.writeInt(mPci);
dest.writeInt(mTac);
dest.writeInt(mEarfcn);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityLte(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mCi = in.readInt();
- mPci = in.readInt();
- mTac = in.readInt();
- mEarfcn = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
+
if (DBG) log("CellIdentityLte(Parcel): " + toString());
}
@@ -215,16 +302,16 @@ public final class CellIdentityLte implements Parcelable {
@SuppressWarnings("hiding")
public static final Creator<CellIdentityLte> CREATOR =
new Creator<CellIdentityLte>() {
- @Override
- public CellIdentityLte createFromParcel(Parcel in) {
- return new CellIdentityLte(in);
- }
+ @Override
+ public CellIdentityLte createFromParcel(Parcel in) {
+ return new CellIdentityLte(in);
+ }
- @Override
- public CellIdentityLte[] newArray(int size) {
- return new CellIdentityLte[size];
- }
- };
+ @Override
+ public CellIdentityLte[] newArray(int size) {
+ return new CellIdentityLte[size];
+ }
+ };
/**
* log
@@ -232,4 +319,4 @@ public final class CellIdentityLte implements Parcelable {
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+} \ No newline at end of file
diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java
index 0d13efd2..51b11aa8 100644
--- a/android/telephony/CellIdentityWcdma.java
+++ b/android/telephony/CellIdentityWcdma.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@ public final class CellIdentityWcdma implements Parcelable {
private static final String LOG_TAG = "CellIdentityWcdma";
private static final boolean DBG = false;
- // 3-digit Mobile Country Code, 0..999
- private final int mMcc;
- // 2 or 3-digit Mobile Network Code, 0..999
- private final int mMnc;
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455
@@ -42,17 +39,27 @@ public final class CellIdentityWcdma implements Parcelable {
private final int mPsc;
// 16-bit UMTS Absolute RF Channel Number
private final int mUarfcn;
+ // 3-digit Mobile Country Code in string format
+ private final String mMccStr;
+ // 2 or 3-digit Mobile Network Code in string format
+ private final String mMncStr;
+ // long alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaLong;
+ // short alpha Operator Name String or Enhanced Operator Name String
+ private final String mAlphaShort;
/**
* @hide
*/
public CellIdentityWcdma() {
- mMcc = Integer.MAX_VALUE;
- mMnc = Integer.MAX_VALUE;
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mPsc = Integer.MAX_VALUE;
mUarfcn = Integer.MAX_VALUE;
+ mMccStr = null;
+ mMncStr = null;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
* public constructor
@@ -65,7 +72,8 @@ public final class CellIdentityWcdma implements Parcelable {
* @hide
*/
public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) {
- this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE);
+ this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+ null, null);
}
/**
@@ -80,39 +88,79 @@ public final class CellIdentityWcdma implements Parcelable {
* @hide
*/
public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) {
- mMcc = mcc;
- mMnc = mnc;
+ this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ }
+
+ /**
+ * public constructor
+ * @param lac 16-bit Location Area Code, 0..65535
+ * @param cid 28-bit UMTS Cell Identity
+ * @param psc 9-bit UMTS Primary Scrambling Code
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param mccStr 3-digit Mobile Country Code in string format
+ * @param mncStr 2 or 3-digit Mobile Network Code in string format
+ * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+ * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+ *
+ * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+ * not a 2 or 3-digit code.
+ *
+ * @hide
+ */
+ public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
+ String mccStr, String mncStr, String alphal, String alphas) {
mLac = lac;
mCid = cid;
mPsc = psc;
mUarfcn = uarfcn;
+
+ if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+ mMccStr = mccStr;
+ } else if (mccStr.isEmpty()) {
+ // If the mccStr parsed from Parcel is empty, set it as null.
+ mMccStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MCC format");
+ }
+
+ if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+ mMncStr = mncStr;
+ } else if (mncStr.isEmpty()) {
+ // If the mncStr parsed from Parcel is empty, set it as null.
+ mMncStr = null;
+ } else {
+ throw new IllegalArgumentException("invalid MNC format");
+ }
+
+ mAlphaLong = alphal;
+ mAlphaShort = alphas;
}
private CellIdentityWcdma(CellIdentityWcdma cid) {
- mMcc = cid.mMcc;
- mMnc = cid.mMnc;
- mLac = cid.mLac;
- mCid = cid.mCid;
- mPsc = cid.mPsc;
- mUarfcn = cid.mUarfcn;
+ this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr,
+ cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityWcdma copy() {
- return new CellIdentityWcdma(this);
+ return new CellIdentityWcdma(this);
}
/**
* @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMccStr} instead.
*/
+ @Deprecated
public int getMcc() {
- return mMcc;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
}
/**
* @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @deprecated Use {@link #getMncStr} instead.
*/
+ @Deprecated
public int getMnc() {
- return mMnc;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
}
/**
@@ -138,9 +186,46 @@ public final class CellIdentityWcdma implements Parcelable {
return mPsc;
}
+ /**
+ * @return Mobile Country Code in string version, null if unknown
+ */
+ public String getMccStr() {
+ return mMccStr;
+ }
+
+ /**
+ * @return Mobile Network Code in string version, null if unknown
+ */
+ public String getMncStr() {
+ return mMncStr;
+ }
+
+ /**
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ */
+ public String getMobileNetworkOperator() {
+ return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+ }
+
+ /**
+ * @return The long alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaLong() {
+ return mAlphaLong;
+ }
+
+ /**
+ * @return The short alpha tag associated with the current scan result (may be the operator
+ * name string or extended operator name string). May be null if unknown.
+ */
+ public CharSequence getOperatorAlphaShort() {
+ return mAlphaShort;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc);
+ return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort);
}
/**
@@ -161,23 +246,27 @@ public final class CellIdentityWcdma implements Parcelable {
}
CellIdentityWcdma o = (CellIdentityWcdma) other;
- return mMcc == o.mMcc &&
- mMnc == o.mMnc &&
- mLac == o.mLac &&
+ return mLac == o.mLac &&
mCid == o.mCid &&
mPsc == o.mPsc &&
- mUarfcn == o.mUarfcn;
+ mUarfcn == o.mUarfcn &&
+ TextUtils.equals(mMccStr, o.mMccStr) &&
+ TextUtils.equals(mMncStr, o.mMncStr) &&
+ TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+ TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("CellIdentityWcdma:{");
- sb.append(" mMcc=").append(mMcc);
- sb.append(" mMnc=").append(mMnc);
sb.append(" mLac=").append(mLac);
sb.append(" mCid=").append(mCid);
sb.append(" mPsc=").append(mPsc);
sb.append(" mUarfcn=").append(mUarfcn);
+ sb.append(" mMcc=").append(mMccStr);
+ sb.append(" mMnc=").append(mMncStr);
+ sb.append(" mAlphaLong=").append(mAlphaLong);
+ sb.append(" mAlphaShort=").append(mAlphaShort);
sb.append("}");
return sb.toString();
@@ -193,22 +282,21 @@ public final class CellIdentityWcdma implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
if (DBG) log("writeToParcel(Parcel, int): " + toString());
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mPsc);
dest.writeInt(mUarfcn);
+ dest.writeString(mMccStr);
+ dest.writeString(mMncStr);
+ dest.writeString(mAlphaLong);
+ dest.writeString(mAlphaShort);
}
/** Construct from Parcel, type has already been processed */
private CellIdentityWcdma(Parcel in) {
- mMcc = in.readInt();
- mMnc = in.readInt();
- mLac = in.readInt();
- mCid = in.readInt();
- mPsc = in.readInt();
- mUarfcn = in.readInt();
+ this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+ in.readString(), in.readString(), in.readString());
+
if (DBG) log("CellIdentityWcdma(Parcel): " + toString());
}
@@ -216,16 +304,16 @@ public final class CellIdentityWcdma implements Parcelable {
@SuppressWarnings("hiding")
public static final Creator<CellIdentityWcdma> CREATOR =
new Creator<CellIdentityWcdma>() {
- @Override
- public CellIdentityWcdma createFromParcel(Parcel in) {
- return new CellIdentityWcdma(in);
- }
+ @Override
+ public CellIdentityWcdma createFromParcel(Parcel in) {
+ return new CellIdentityWcdma(in);
+ }
- @Override
- public CellIdentityWcdma[] newArray(int size) {
- return new CellIdentityWcdma[size];
- }
- };
+ @Override
+ public CellIdentityWcdma[] newArray(int size) {
+ return new CellIdentityWcdma[size];
+ }
+ };
/**
* log
@@ -233,4 +321,4 @@ public final class CellIdentityWcdma implements Parcelable {
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+} \ No newline at end of file
diff --git a/android/telephony/DisconnectCause.java b/android/telephony/DisconnectCause.java
index 98fb6534..c3a2ceb1 100644
--- a/android/telephony/DisconnectCause.java
+++ b/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@ public class DisconnectCause {
* {@hide}
*/
public static final int EMERGENCY_PERM_FAILURE = 64;
+
+ /**
+ * This cause is used to report a normal event only when no other cause in the normal class
+ * applies.
+ * {@hide}
+ */
+ public static final int NORMAL_UNSPECIFIED = 65;
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@ public class DisconnectCause {
return "EMERGENCY_TEMP_FAILURE";
case EMERGENCY_PERM_FAILURE:
return "EMERGENCY_PERM_FAILURE";
+ case NORMAL_UNSPECIFIED:
+ return "NORMAL_UNSPECIFIED";
default:
return "INVALID: " + cause;
}
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index 9a9877a8..f392570e 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -74,6 +75,14 @@ public class MbmsDownloadSession implements AutoCloseable {
"android.telephony.action.EmbmsDownload";
/**
+ * Metadata key that specifies the component name of the service to bind to for file-download.
+ * @hide
+ */
+ @TestApi
+ public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA =
+ "mbms-download-service-override";
+
+ /**
* 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
diff --git a/android/telephony/MbmsStreamingSession.java b/android/telephony/MbmsStreamingSession.java
index a8c46079..fb2ff7b1 100644
--- a/android/telephony/MbmsStreamingSession.java
+++ b/android/telephony/MbmsStreamingSession.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
@@ -62,6 +63,14 @@ public class MbmsStreamingSession implements AutoCloseable {
public static final String MBMS_STREAMING_SERVICE_ACTION =
"android.telephony.action.EmbmsStreaming";
+ /**
+ * Metadata key that specifies the component name of the service to bind to for file-download.
+ * @hide
+ */
+ @TestApi
+ public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA =
+ "mbms-streaming-service-override";
+
private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 6029995f..98195ada 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -390,20 +390,23 @@ public final class SmsManager {
* Inject an SMS PDU into the android application framework.
*
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
- * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param pdu is the byte array of pdu to be injected into android application framework
- * @param format is the format of SMS pdu (3gpp or 3gpp2)
+ * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
+ * {@link SmsMessage#FORMAT_3GPP2})
* @param receivedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully received by the
* android application framework, or failed. This intent is broadcasted at
* the same time an SMS received from radio is acknowledged back.
- * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
- * <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+ * The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
+ * for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
+ * error.
*
- * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ * @throws IllegalArgumentException if the format is invalid.
*/
- public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ public void injectSmsPdu(
+ byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
// Format must be either 3gpp or 3gpp2.
throw new IllegalArgumentException(
diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java
index dcdda868..df412335 100644
--- a/android/telephony/SmsMessage.java
+++ b/android/telephony/SmsMessage.java
@@ -16,24 +16,25 @@
package android.telephony;
-import android.os.Binder;
-import android.os.Parcel;
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+import android.annotation.StringDef;
import android.content.res.Resources;
+import android.os.Binder;
import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
-import java.lang.Math;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-
/**
* A Short Message Service message.
@@ -81,15 +82,18 @@ public class SmsMessage {
*/
public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+ /** @hide */
+ @StringDef({FORMAT_3GPP, FORMAT_3GPP2})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Format {}
+
/**
* Indicates a 3GPP format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP = "3gpp";
/**
* Indicates a 3GPP2 format SMS message.
- * @hide pending API council approval
*/
public static final String FORMAT_3GPP2 = "3gpp2";
diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java
index d38d8a71..b4ad1d77 100644
--- a/android/telephony/mbms/MbmsUtils.java
+++ b/android/telephony/mbms/MbmsUtils.java
@@ -22,6 +22,8 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.*;
import android.content.pm.ServiceInfo;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.MbmsStreamingSession;
import android.util.Log;
import java.io.File;
@@ -48,24 +50,64 @@ public class MbmsUtils {
return new ComponentName(ci.packageName, ci.name);
}
+ private static ComponentName getOverrideServiceName(Context context, String serviceAction) {
+ String metaDataKey = null;
+ switch (serviceAction) {
+ case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION:
+ metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA;
+ break;
+ case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
+ metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
+ break;
+ }
+ if (metaDataKey == null) {
+ return null;
+ }
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = context.getPackageManager()
+ .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ if (appInfo.metaData == null) {
+ return null;
+ }
+ String serviceComponent = appInfo.metaData.getString(metaDataKey);
+ if (serviceComponent == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(serviceComponent);
+ }
+
public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
// Query for the proper service
PackageManager packageManager = context.getPackageManager();
Intent queryIntent = new Intent();
queryIntent.setAction(serviceAction);
- List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
- PackageManager.MATCH_SYSTEM_ONLY);
- if (downloadServices == null || downloadServices.size() == 0) {
- Log.w(LOG_TAG, "No download services found, cannot get service info");
+ ComponentName overrideService = getOverrideServiceName(context, serviceAction);
+ List<ResolveInfo> services;
+ if (overrideService == null) {
+ services = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } else {
+ queryIntent.setComponent(overrideService);
+ services = packageManager.queryIntentServices(queryIntent,
+ PackageManager.MATCH_ALL);
+ }
+
+ if (services == null || services.size() == 0) {
+ Log.w(LOG_TAG, "No MBMS services found, cannot get service info");
return null;
}
- if (downloadServices.size() > 1) {
- Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
+ if (services.size() > 1) {
+ Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service");
return null;
}
- return downloadServices.get(0).serviceInfo;
+ return services.get(0).serviceInfo;
}
public static int startBinding(Context context, String serviceAction,
diff --git a/android/telephony/mbms/StreamingServiceInfo.java b/android/telephony/mbms/StreamingServiceInfo.java
index c704f346..ef2a14aa 100644
--- a/android/telephony/mbms/StreamingServiceInfo.java
+++ b/android/telephony/mbms/StreamingServiceInfo.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,6 +42,7 @@ public final class StreamingServiceInfo extends ServiceInfo implements Parcelabl
* @hide
*/
@SystemApi
+ @TestApi
public StreamingServiceInfo(Map<Locale, String> names, String className,
List<Locale> locales, String serviceId, Date start, Date end) {
super(names, className, locales, serviceId, start, end);
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index a2381536..db177c0c 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,6 +18,7 @@ package android.telephony.mbms.vendor;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
@@ -38,6 +39,7 @@ import java.util.List;
* @hide
*/
@SystemApi
+@TestApi
public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Initialize streaming service for this app and subId, registering the listener.
diff --git a/android/text/Layout.java b/android/text/Layout.java
index ac5c2e92..4d2a9629 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -1910,7 +1910,7 @@ public abstract class Layout {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
Directions directions;
int dir;
if (mt.mEasy) {
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index ffc44a72..3d9fba71 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -39,7 +39,6 @@ class MeasuredText {
private int mPos;
private TextPaint mWorkPaint;
- private StaticLayout.Builder mBuilder;
private MeasuredText() {
mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@ class MeasuredText {
void finish() {
mText = null;
- mBuilder = null;
if (mLen > 1000) {
mWidths = null;
mChars = null;
@@ -93,9 +91,7 @@ class MeasuredText {
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
- void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
- StaticLayout.Builder builder) {
- mBuilder = builder;
+ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
mText = text;
mTextStart = start;
@@ -159,12 +155,12 @@ class MeasuredText {
/**
* Apply the style.
*
- * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
- * text width.
- * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
- * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+ * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+ * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+ * code by calling StaticLayout.addstyleRun() and returns 0.
*/
- float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+ long nativeStaticLayoutPtr) {
if (fm != null) {
paint.getFontMetricsInt(fm);
}
@@ -174,10 +170,10 @@ class MeasuredText {
if (mEasy) {
final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
} else {
- mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
return 0.0f; // Builder.addStyleRun doesn't return the width.
}
}
@@ -187,12 +183,12 @@ class MeasuredText {
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
final boolean isRtl = (level & 0x1) != 0;
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
totalAdvance +=
paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
} else {
// Builder.addStyleRun doesn't return the width.
- mBuilder.addStyleRun(paint, q, i, isRtl);
+ StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
}
if (i == e) {
break;
@@ -201,11 +197,15 @@ class MeasuredText {
level = mLevels[i];
}
}
- return totalAdvance; // If mBuilder is null, the result is zero.
+ return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero.
+ }
+
+ float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, len, fm, 0 /* native ptr */);
}
float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
- Paint.FontMetricsInt fm) {
+ Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
TextPaint workPaint = mWorkPaint;
workPaint.set(paint);
@@ -224,18 +224,18 @@ class MeasuredText {
float wid;
if (replacement == null) {
- wid = addStyleRun(workPaint, len, fm);
+ wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
} else {
// Use original text. Shouldn't matter.
wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
mTextStart + mPos + len, fm);
- if (mBuilder == null) {
+ if (nativeStaticLayoutPtr == 0) {
float[] w = mWidths;
w[mPos] = wid;
for (int i = mPos + 1, e = mPos + len; i < e; i++)
w[i] = 0;
} else {
- mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
+ StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
}
mPos += len;
}
@@ -253,6 +253,11 @@ class MeasuredText {
return wid;
}
+ float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+ Paint.FontMetricsInt fm) {
+ return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+ }
+
int breakText(int limit, boolean forwards, float width) {
float[] w = mWidths;
if (forwards) {
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index 5c60188d..c0fc44fd 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -32,6 +32,9 @@ import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
import java.util.Arrays;
/**
@@ -57,9 +60,7 @@ public class StaticLayout extends Layout {
* default values.
*/
public final static class Builder {
- private Builder() {
- mNativePtr = nNewBuilder();
- }
+ private Builder() {}
/**
* Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@ public class StaticLayout extends Layout {
b.mRightIndents = null;
b.mLeftPaddings = null;
b.mRightPaddings = null;
- nFinishBuilder(b.mNativePtr);
sPool.release(b);
}
// release any expensive state
/* package */ void finish() {
- nFinishBuilder(mNativePtr);
mText = null;
mPaint = null;
mLeftIndents = null;
@@ -405,32 +404,6 @@ public class StaticLayout extends Layout {
}
/**
- * Measurement and break iteration is done in native code. The protocol for using
- * the native code is as follows.
- *
- * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
- * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
- * future).
- *
- * Then, for each run within the paragraph:
- * - one of the following, depending on the type of run:
- * + addStyleRun (a text run, to be measured in native code)
- * + addReplacementRun (a replacement run, width is given)
- *
- * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
- *
- * After all paragraphs, call finish() to release expensive buffers.
- */
-
- /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
- }
-
- /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
- nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
- }
-
- /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@ public class StaticLayout extends Layout {
return result;
}
- @Override
- protected void finalize() throws Throwable {
- try {
- nFreeBuilder(mNativePtr);
- } finally {
- super.finalize();
- }
- }
-
- /* package */ long mNativePtr;
-
private CharSequence mText;
private int mStart;
private int mEnd;
@@ -694,270 +656,294 @@ public class StaticLayout extends Layout {
indents = null;
}
- int paraEnd;
- for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
- if (paraEnd < 0)
- paraEnd = bufEnd;
- else
- paraEnd++;
-
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
-
- LineHeightSpan[] chooseHt = null;
-
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
-
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
- }
+ final long nativePtr = nInit(
+ b.mBreakStrategy, b.mHyphenationFrequency,
+ // TODO: Support more justification mode, e.g. letter spacing, stretching.
+ b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+ indents, mLeftPaddings, mRightPaddings);
+
+ try {
+ int paraEnd;
+ for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+ if (paraEnd < 0) {
+ paraEnd = bufEnd;
+ } else {
+ paraEnd++;
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
-
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
- } else {
- if (chooseHtv == null ||
- chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
+
+ LineHeightSpan[] chooseHt = null;
+
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
+
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
}
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ }
- chooseHtv[i] = getLineTop(getLineForOffset(o));
- } else {
- // starts in this paragraph
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
+
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
- chooseHtv[i] = v;
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
+
+ chooseHtv[i] = v;
+ }
}
}
}
- }
- measured.setPara(source, paraStart, paraEnd, textDir, b);
- char[] chs = measured.mChars;
- float[] widths = measured.mWidths;
- byte[] chdirs = measured.mLevels;
- int dir = measured.mDir;
- boolean easy = measured.mEasy;
-
- // tab stop locations
- int[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- int[] stops = new int[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = spans[i].getTabStop();
+ measured.setPara(source, paraStart, paraEnd, textDir);
+ char[] chs = measured.mChars;
+ float[] widths = measured.mWidths;
+ byte[] chdirs = measured.mLevels;
+ int dir = measured.mDir;
+ boolean easy = measured.mEasy;
+
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
}
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
- }
- }
-
- nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
- firstWidth, firstWidthLineCount, restWidth,
- variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
- // TODO: Support more justification mode, e.g. letter spacing, stretching.
- b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
- // TODO: indents and paddings don't need to get passed to native code for every
- // paragraph. Pass them to native code just once.
- indents, mLeftPaddings, mRightPaddings, mLineCount);
-
- // measurement has to be done before performing line breaking
- // but we don't want to recompute fontmetrics or span ranges the
- // second time, so we cache those and then use those stored values
- int fmCacheCount = 0;
- int spanEndCacheCount = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- if (fmCacheCount * 4 >= fmCache.length) {
- int[] grow = new int[fmCacheCount * 4 * 2];
- System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
- fmCache = grow;
- }
-
- if (spanEndCacheCount >= spanEndCache.length) {
- int[] grow = new int[spanEndCacheCount * 2];
- System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
- spanEndCache = grow;
- }
-
- if (spanned == null) {
- spanEnd = paraEnd;
- int spanLen = spanEnd - spanStart;
- measured.addStyleRun(paint, spanLen, fm);
- } else {
- spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
- MetricAffectingSpan.class);
- int spanLen = spanEnd - spanStart;
- MetricAffectingSpan[] spans =
- spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
- measured.addStyleRun(paint, spans, spanLen, fm);
}
- // the order of storage here (top, bottom, ascent, descent) has to match the code below
- // where these values are retrieved
- fmCache[fmCacheCount * 4 + 0] = fm.top;
- fmCache[fmCacheCount * 4 + 1] = fm.bottom;
- fmCache[fmCacheCount * 4 + 2] = fm.ascent;
- fmCache[fmCacheCount * 4 + 3] = fm.descent;
- fmCacheCount++;
+ // measurement has to be done before performing line breaking
+ // but we don't want to recompute fontmetrics or span ranges the
+ // second time, so we cache those and then use those stored values
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
+ }
- spanEndCache[spanEndCacheCount] = spanEnd;
- spanEndCacheCount++;
- }
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
- int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
- lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
- lineBreaks.breaks.length, widths);
-
- final int[] breaks = lineBreaks.breaks;
- final float[] lineWidths = lineBreaks.widths;
- final float[] ascents = lineBreaks.ascents;
- final float[] descents = lineBreaks.descents;
- final int[] flags = lineBreaks.flags;
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width and flag.
- float width = 0;
- int flag = 0; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
+ if (spanned == null) {
+ spanEnd = paraEnd;
+ int spanLen = spanEnd - spanStart;
+ measured.addStyleRun(paint, spanLen, fm, nativePtr);
} else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += widths[j];
- }
+ spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+ MetricAffectingSpan.class);
+ int spanLen = spanEnd - spanStart;
+ MetricAffectingSpan[] spans =
+ spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, spanned,
+ MetricAffectingSpan.class);
+ measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
}
- flag |= flags[i] & TAB_MASK;
- }
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- flags[remainingLineCount - 1] = flag;
- breakCount = remainingLineCount;
- }
+ // the order of storage here (top, bottom, ascent, descent) has to match the
+ // code below where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
- // here is the offset of the starting character of the line we are currently measuring
- int here = paraStart;
-
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
}
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
+ int breakCount = nComputeLineBreaks(
+ nativePtr,
+
+ // Inputs
+ chs,
+ paraEnd - paraStart,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ TAB_INCREMENT,
+ mLineCount,
+
+ // Outputs
+ lineBreaks,
+ lineBreaks.breaks.length,
+ lineBreaks.breaks,
+ lineBreaks.widths,
+ lineBreaks.ascents,
+ lineBreaks.descents,
+ lineBreaks.flags,
+ widths);
+
+ final int[] breaks = lineBreaks.breaks;
+ final float[] lineWidths = lineBreaks.widths;
+ final float[] ascents = lineBreaks.ascents;
+ final float[] descents = lineBreaks.descents;
+ final int[] flags = lineBreaks.flags;
+
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width and flag.
+ float width = 0;
+ int flag = 0; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
+ } else {
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += widths[j];
+ }
+ }
+ flag |= flags[i] & TAB_MASK;
+ }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ flags[remainingLineCount - 1] = flag;
+
+ breakCount = remainingLineCount;
}
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
-
- final int ascent = fallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = fallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
- needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
- addLastLineSpacing, chs, widths, paraStart, ellipsize,
- ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
+
+ int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
fmTop = fm.top;
- fmBottom = fm.bottom;
+ }
+ if (fm.ascent < fmAscent) {
fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
fmDescent = fm.descent;
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
}
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
+ }
+
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
+ }
+
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
+
+ boolean moreChars = (endPos < bufEnd);
+
+ final int ascent = fallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = fallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+ ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+ moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = fm.top;
+ fmBottom = fm.bottom;
+ fmAscent = fm.ascent;
+ fmDescent = fm.descent;
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
- here = endPos;
- breakIndex++;
+ here = endPos;
+ breakIndex++;
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
+ }
}
}
- }
- if (paraEnd == bufEnd)
- break;
- }
+ if (paraEnd == bufEnd) {
+ break;
+ }
+ }
- if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
- mLineCount < mMaximumVisibleLineCount) {
- measured.setPara(source, bufEnd, bufEnd, textDir, b);
-
- paint.getFontMetricsInt(fm);
-
- v = out(source,
- bufEnd, bufEnd, fm.ascent, fm.descent,
- fm.top, fm.bottom,
- v,
- spacingmult, spacingadd, null,
- null, fm, 0,
- needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
- includepad, trackpad, addLastLineSpacing, null,
- null, bufStart, ellipsize,
- ellipsizedWidth, 0, paint, false);
+ if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+ && mLineCount < mMaximumVisibleLineCount) {
+ measured.setPara(source, bufEnd, bufEnd, textDir);
+
+ paint.getFontMetricsInt(fm);
+
+ v = out(source,
+ bufEnd, bufEnd, fm.ascent, fm.descent,
+ fm.top, fm.bottom,
+ v,
+ spacingmult, spacingadd, null,
+ null, fm, 0,
+ needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+ includepad, trackpad, addLastLineSpacing, null,
+ null, bufStart, ellipsize,
+ ellipsizedWidth, 0, paint, false);
+ }
+ } finally {
+ nFinish(nativePtr);
}
}
@@ -1487,26 +1473,51 @@ public class StaticLayout extends Layout {
mMaxLineHeight : super.getHeight();
}
- private static native long nNewBuilder();
- private static native void nFreeBuilder(long nativePtr);
- private static native void nFinishBuilder(long nativePtr);
-
- // Set up paragraph text and settings; done as one big method to minimize jni crossings
- private static native void nSetupParagraph(
- /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
- @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
- @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
- int defaultTabStop, @BreakStrategy int breakStrategy,
- @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
- @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
- @IntRange(from = 0) int indentsOffset);
-
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ /**
+ * Measurement and break iteration is done in native code. The protocol for using
+ * the native code is as follows.
+ *
+ * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+ * following:
+ *
+ * - Call one of the following methods for each run within the paragraph depending on the type
+ * of run:
+ * + addStyleRun (a text run, to be measured in native code)
+ * + addReplacementRun (a replacement run, width is given)
+ *
+ * - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+ *
+ * After all paragraphs, call finish() to release expensive buffers.
+ */
+
+ /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+ boolean isRtl) {
+ nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+ }
+
+ /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+ float width) {
+ nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+ }
+
+ @FastNative
+ private static native long nInit(
+ @BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents,
+ @Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings);
+
+ @CriticalNative
+ private static native void nFinish(long nativePtr);
+
+ @CriticalNative
private static native void nAddStyleRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
- // TODO: Make this method CriticalNative once native code defers doing layouts.
+ @CriticalNative
private static native void nAddReplacementRun(
/* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@ public class StaticLayout extends Layout {
// arrays do not have to be resized
// The individual character widths will be returned in charWidths. The length of charWidths must
// be at least the length of the text.
- private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength,
- float[] charWidths);
+ private static native int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ @NonNull char[] text,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ @IntRange(from = 0) int indentsOffset,
+
+ // Outputs
+ @NonNull LineBreaks recycle,
+ @IntRange(from = 0) int recycleLength,
+ @NonNull int[] recycleBreaks,
+ @NonNull float[] recycleWidths,
+ @NonNull float[] recycleAscents,
+ @NonNull float[] recycleDescents,
+ @NonNull int[] recycleFlags,
+ @NonNull float[] charWidths);
private int mLineCount;
private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index def3c91c..ef7525ec 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -4,12 +4,14 @@ import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.BidiRenderer;
import android.graphics.Paint;
import android.graphics.Paint_Delegate;
import android.graphics.RectF;
import android.icu.text.BreakIterator;
-import android.icu.util.ULocale;
+import android.text.Layout.BreakStrategy;
+import android.text.Layout.HyphenationFrequency;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
@@ -38,35 +40,21 @@ public class StaticLayout_Delegate {
new DelegateManager<Builder>(Builder.class);
@LayoutlibDelegate
- /*package*/ static long nNewBuilder() {
- return sBuilderManager.addNewDelegate(new Builder());
+ /*package*/ static long nInit(
+ @BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents,
+ @Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings) {
+ Builder builder = new Builder();
+ builder.mBreakStrategy = breakStrategy;
+ return sBuilderManager.addNewDelegate(builder);
}
@LayoutlibDelegate
- /*package*/ static void nFreeBuilder(long nativeBuilder) {
- sBuilderManager.removeJavaReferenceFor(nativeBuilder);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nFinishBuilder(long nativeBuilder) {
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
- float firstWidth, int firstWidthLineCount, float restWidth,
- int[] variableTabStops, int defaultTabStop, int breakStrategy,
- int hyphenationFrequency, boolean isJustified, int[] indents, int[] leftPaddings,
- int[] rightPaddings, int intentsOffset) {
- // TODO: implement justified alignment
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return;
- }
-
- builder.mText = text;
- builder.mWidths = new float[length];
- builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
- builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
+ /*package*/ static void nFinish(long nativePtr) {
+ sBuilderManager.removeJavaReferenceFor(nativePtr);
}
@LayoutlibDelegate
@@ -76,10 +64,7 @@ public class StaticLayout_Delegate {
if (builder == null) {
return;
}
-
- int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
- bidiFlags);
+ builder.mRuns.add(new StyleRun(nativePaint, start, end, isRtl));
}
@LayoutlibDelegate
@@ -89,22 +74,47 @@ public class StaticLayout_Delegate {
if (builder == null) {
return;
}
- builder.mWidths[start] = width;
- Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
+ builder.mRuns.add(new ReplacementRun(start, end, width));
}
@LayoutlibDelegate
- /*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
- float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) {
+ /*package*/ static int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ // Inputs
+ @NonNull char[] text,
+ int length,
+ float firstWidth,
+ int firstWidthLineCount,
+ float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ int indentsOffset,
+
+ // Outputs
+ @NonNull LineBreaks recycle,
+ int recycleLength,
+ @NonNull int[] recycleBreaks,
+ @NonNull float[] recycleWidths,
+ @NonNull float[] recycleAscents,
+ @NonNull float[] recycleDescents,
+ @NonNull int[] recycleFlags,
+ @NonNull float[] charWidths) {
+ Builder builder = sBuilderManager.getDelegate(nativePtr);
if (builder == null) {
return 0;
}
+ builder.mText = text;
+ builder.mWidths = new float[length];
+ builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
+ builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
+
+ for (Run run: builder.mRuns) {
+ run.addTo(builder);
+ }
+
// compute all possible breakpoints.
- int length = builder.mWidths.length;
BreakIterator it = BreakIterator.getLineInstance();
it.setText(new Segment(builder.mText, 0, length));
@@ -196,9 +206,55 @@ public class StaticLayout_Delegate {
private static class Builder {
char[] mText;
float[] mWidths;
- LineBreaker mLineBreaker;
- int mBreakStrategy;
- LineWidth mLineWidth;
- TabStops mTabStopCalculator;
+ private LineBreaker mLineBreaker;
+ private int mBreakStrategy;
+ private LineWidth mLineWidth;
+ private TabStops mTabStopCalculator;
+ private ArrayList<Run> mRuns = new ArrayList<>();
+ }
+
+ private abstract static class Run {
+ int mStart;
+ int mEnd;
+
+ Run(int start, int end) {
+ mStart = start;
+ mEnd = end;
+ }
+
+ abstract void addTo(Builder builder);
+ }
+
+ private static class StyleRun extends Run {
+ private long mNativePaint;
+ private boolean mIsRtl;
+
+ private StyleRun(long nativePaint, int start, int end, boolean isRtl) {
+ super(start, end);
+ mNativePaint = nativePaint;
+ mIsRtl = isRtl;
+ }
+
+ @Override
+ void addTo(Builder builder) {
+ int bidiFlags = mIsRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ measureText(mNativePaint, builder.mText, mStart, mEnd - mStart, builder.mWidths,
+ bidiFlags);
+ }
+ }
+
+ private static class ReplacementRun extends Run {
+ private final float mWidth;
+
+ private ReplacementRun(int start, int end, float width) {
+ super(start, end);
+ mWidth = width;
+ }
+
+ @Override
+ void addTo(Builder builder) {
+ builder.mWidths[mStart] = mWidth;
+ Arrays.fill(builder.mWidths, mStart + 1, mEnd, 0.0f);
+ }
}
}
diff --git a/android/text/TextLine.java b/android/text/TextLine.java
index 20c0ed87..86cc0141 100644
--- a/android/text/TextLine.java
+++ b/android/text/TextLine.java
@@ -28,6 +28,7 @@ import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -44,7 +45,8 @@ import java.util.ArrayList;
*
* @hide
*/
-class TextLine {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class TextLine {
private static final boolean DEBUG = false;
private TextPaint mPaint;
@@ -82,7 +84,8 @@ class TextLine {
*
* @return an uninitialized TextLine
*/
- static TextLine obtain() {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static TextLine obtain() {
TextLine tl;
synchronized (sCached) {
for (int i = sCached.length; --i >= 0;) {
@@ -107,7 +110,8 @@ class TextLine {
* @return null, as a convenience from clearing references to the provided
* TextLine
*/
- static TextLine recycle(TextLine tl) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static TextLine recycle(TextLine tl) {
tl.mText = null;
tl.mPaint = null;
tl.mDirections = null;
@@ -142,7 +146,8 @@ class TextLine {
* @param hasTabs true if the line might contain tabs
* @param tabStops the tabStops. Can be null.
*/
- void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
Directions directions, boolean hasTabs, TabStops tabStops) {
mPaint = paint;
mText = text;
@@ -196,7 +201,8 @@ class TextLine {
/**
* Justify the line to the given width.
*/
- void justify(float justifyWidth) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void justify(float justifyWidth) {
int end = mLen;
while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
end--;
@@ -277,7 +283,8 @@ class TextLine {
* @param fmi receives font metrics information, can be null
* @return the signed width of the line
*/
- float metrics(FontMetricsInt fmi) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public float metrics(FontMetricsInt fmi) {
return measure(mLen, false, fmi);
}
@@ -1165,23 +1172,18 @@ class TextLine {
}
private boolean isStretchableWhitespace(int ch) {
- // TODO: Support other stretchable whitespace. (Bug: 34013491)
- return ch == 0x0020 || ch == 0x00A0;
- }
-
- private int nextStretchableSpace(int start, int end) {
- for (int i = start; i < end; i++) {
- final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
- if (isStretchableWhitespace(c)) return i;
- }
- return end;
+ // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
+ return ch == 0x0020;
}
/* Return the number of spaces in the text line, for the purpose of justification */
private int countStretchableSpaces(int start, int end) {
int count = 0;
- for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
- count++;
+ for (int i = start; i < end; i++) {
+ final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+ if (isStretchableWhitespace(c)) {
+ count++;
+ }
}
return count;
}
diff --git a/android/text/TextUtils.java b/android/text/TextUtils.java
index 68afeecf..cbdaa69b 100644
--- a/android/text/TextUtils.java
+++ b/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@ public class TextUtils {
}
// XXX this is probably ok, but need to look at it more
- tempMt.setPara(format, 0, format.length(), textDir, null);
+ tempMt.setPara(format, 0, format.length(), textDir);
float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@ public class TextUtils {
private static float setPara(MeasuredText mt, TextPaint paint,
CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
- mt.setPara(text, start, end, textDir, null);
+ mt.setPara(text, start, end, textDir);
float width;
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/android/util/Log.java b/android/util/Log.java
index b94e48b3..02998653 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,12 +16,45 @@
package android.util;
+import android.os.DeadSystemException;
+
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.LineBreakBufferedWriter;
+
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.io.Writer;
import java.net.UnknownHostException;
/**
- * Mock Log implementation for testing on non android host.
+ * API for sending log output.
+ *
+ * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
+ * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
+ * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
+ *
+ * <p>The order in terms of verbosity, from least to most is
+ * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
+ * into an application except during development. Debug logs are compiled
+ * in but stripped at runtime. Error, warning and info logs are always kept.
+ *
+ * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
+ * in your class:
+ *
+ * <pre>private static final String TAG = "MyActivity";</pre>
+ *
+ * and use that in subsequent calls to the log methods.
+ * </p>
+ *
+ * <p><b>Tip:</b> Don't forget that when you make a call like
+ * <pre>Log.v(TAG, "index=" + i);</pre>
+ * that when you're building the string to pass into Log.d, the compiler uses a
+ * StringBuilder and at least three allocations occur: the StringBuilder
+ * itself, the buffer, and the String object. Realistically, there is also
+ * another buffer allocation and copy, and even more pressure on the gc.
+ * That means that if your log message is filtered out, you might be doing
+ * significant work and incurring significant overhead.
*/
public final class Log {
@@ -55,6 +88,29 @@ public final class Log {
*/
public static final int ASSERT = 7;
+ /**
+ * Exception class used to capture a stack trace in {@link #wtf}.
+ * @hide
+ */
+ public static class TerribleFailure extends Exception {
+ TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
+ }
+
+ /**
+ * Interface to handle terrible failures from {@link #wtf}.
+ *
+ * @hide
+ */
+ public interface TerribleFailureHandler {
+ void onTerribleFailure(String tag, TerribleFailure what, boolean system);
+ }
+
+ private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
+ public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
+ RuntimeInit.wtf(tag, what, system);
+ }
+ };
+
private Log() {
}
@@ -65,7 +121,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -76,7 +132,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
/**
@@ -86,7 +142,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -97,7 +153,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
}
/**
@@ -107,7 +163,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println(LOG_ID_MAIN, INFO, tag, msg);
+ return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -118,7 +174,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
}
/**
@@ -128,7 +184,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println(LOG_ID_MAIN, WARN, tag, msg);
+ return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -139,9 +195,31 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
}
+ /**
+ * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+ *
+ * The default level of any tag is set to INFO. This means that any level above and including
+ * INFO will be logged. Before you make any calls to a logging method you should check to see
+ * if your tag should be logged. You can change the default level by setting a system property:
+ * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
+ * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
+ * turn off all logging for your tag. You can also create a local.prop file that with the
+ * following in it:
+ * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
+ * and place that in /data/local.prop.
+ *
+ * @param tag The tag to check.
+ * @param level The level to check.
+ * @return Whether or not that this is allowed to be logged.
+ * @throws IllegalArgumentException is thrown if the tag.length() > 23
+ * for Nougat (7.0) releases (API <= 23) and prior, there is no
+ * tag limit of concern after this API level.
+ */
+ public static native boolean isLoggable(String tag, int level);
+
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -149,7 +227,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
}
/**
@@ -159,7 +237,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println(LOG_ID_MAIN, ERROR, tag, msg);
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -170,7 +248,82 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
+ return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
+ }
+
+ /**
+ * Like {@link #wtf(String, String)}, but also writes to the log the full
+ * call stack.
+ * @hide
+ */
+ public static int wtfStack(String tag, String msg) {
+ return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
+ }
+
+ static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
+ boolean system) {
+ TerribleFailure what = new TerribleFailure(msg, tr);
+ // Only mark this as ERROR, do not use ASSERT since that should be
+ // reserved for cases where the system is guaranteed to abort.
+ // The onTerribleFailure call does not always cause a crash.
+ int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
+ sWtfHandler.onTerribleFailure(tag, what, system);
+ return bytes;
+ }
+
+ static void wtfQuiet(int logId, String tag, String msg, boolean system) {
+ TerribleFailure what = new TerribleFailure(msg, null);
+ sWtfHandler.onTerribleFailure(tag, what, system);
+ }
+
+ /**
+ * Sets the terrible failure handler, for testing.
+ *
+ * @return the old handler
+ *
+ * @hide
+ */
+ public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler == null");
+ }
+ TerribleFailureHandler oldHandler = sWtfHandler;
+ sWtfHandler = handler;
+ return oldHandler;
}
/**
@@ -193,7 +346,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
+ PrintWriter pw = new FastPrintWriter(sw, false, 256);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -208,7 +361,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println(LOG_ID_MAIN, priority, tag, msg);
+ return println_native(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -217,9 +370,115 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ @SuppressWarnings("unused")
- public static int println(int bufID,
- int priority, String tag, String msg) {
- return 0;
+ /** @hide */ public static native int println_native(int bufID,
+ int priority, String tag, String msg);
+
+ /**
+ * Return the maximum payload the log daemon accepts without truncation.
+ * @return LOGGER_ENTRY_MAX_PAYLOAD.
+ */
+ private static native int logger_entry_max_payload_native();
+
+ /**
+ * Helper function for long messages. Uses the LineBreakBufferedWriter to break
+ * up long messages and stacktraces along newlines, but tries to write in large
+ * chunks. This is to avoid truncation.
+ * @hide
+ */
+ public static int printlns(int bufID, int priority, String tag, String msg,
+ Throwable tr) {
+ ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
+ // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
+ // and the length of the tag.
+ // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
+ // is too expensive to compute that ahead of time.
+ int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
+ - 2 // Two terminators.
+ - (tag != null ? tag.length() : 0) // Tag length.
+ - 32; // Some slack.
+ // At least assume you can print *some* characters (tag is not too large).
+ bufferSize = Math.max(bufferSize, 100);
+
+ LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
+
+ lbbw.println(msg);
+
+ if (tr != null) {
+ // This is to reduce the amount of log spew that apps do in the non-error
+ // condition of the network being unavailable.
+ Throwable t = tr;
+ while (t != null) {
+ if (t instanceof UnknownHostException) {
+ break;
+ }
+ if (t instanceof DeadSystemException) {
+ lbbw.println("DeadSystemException: The system died; "
+ + "earlier logs will point to the root cause");
+ break;
+ }
+ t = t.getCause();
+ }
+ if (t == null) {
+ tr.printStackTrace(lbbw);
+ }
+ }
+
+ lbbw.flush();
+
+ return logWriter.getWritten();
+ }
+
+ /**
+ * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+ * a JNI call during logging.
+ */
+ static class PreloadHolder {
+ public final static int LOGGER_ENTRY_MAX_PAYLOAD =
+ logger_entry_max_payload_native();
+ }
+
+ /**
+ * Helper class to write to the logcat. Different from LogWriter, this writes
+ * the whole given buffer and does not break along newlines.
+ */
+ private static class ImmediateLogWriter extends Writer {
+
+ private int bufID;
+ private int priority;
+ private String tag;
+
+ private int written = 0;
+
+ /**
+ * Create a writer that immediately writes to the log, using the given
+ * parameters.
+ */
+ public ImmediateLogWriter(int bufID, int priority, String tag) {
+ this.bufID = bufID;
+ this.priority = priority;
+ this.tag = tag;
+ }
+
+ public int getWritten() {
+ return written;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ // Note: using String here has a bit of overhead as a Java object is created,
+ // but using the char[] directly is not easier, as it needs to be translated
+ // to a C char[] for logging.
+ written += println_native(bufID, priority, tag, new String(cbuf, off, len));
+ }
+
+ @Override
+ public void flush() {
+ // Ignored.
+ }
+
+ @Override
+ public void close() {
+ // Ignored.
+ }
}
}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 52086065..40154880 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,10 +20,6 @@ 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
@@ -91,9 +87,8 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- * @param maxSize The new maximum size.
*
- * @hide
+ * @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -190,10 +185,13 @@ 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.
*/
- private void trimToSize(int maxSize) {
+ public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -207,16 +205,7 @@ public class LruCache<K, V> {
break;
}
- // 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
-
+ Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
diff --git a/android/view/RectShadowPainter.java b/android/view/RectShadowPainter.java
index 5665d4f3..88771a76 100644
--- a/android/view/RectShadowPainter.java
+++ b/android/view/RectShadowPainter.java
@@ -19,8 +19,10 @@ package android.view;
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import android.graphics.BaseCanvas_Delegate;
import android.graphics.Canvas;
import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -46,7 +48,8 @@ public class RectShadowPainter {
private static final int END_COLOR = ResourceHelper.getColor("#03000000");
private static final float PERPENDICULAR_ANGLE = 90f;
- public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas,
+ float alpha) {
Rect outline = new Rect();
if (!viewOutline.getRect(outline)) {
assert false : "Outline is not a rect shadow";
@@ -74,9 +77,16 @@ public class RectShadowPainter {
edgePaint.setAntiAlias(false);
float outerArcRadius = radius + shadowSize;
int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+ if (alpha != 1f) {
+ // Correct colors using the given component alpha
+ for (int i = 0; i < colors.length; i++) {
+ colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]),
+ Color.green(colors[i]), Color.blue(colors[i]));
+ }
+ }
cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
- edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+ edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2],
TileMode.CLAMP));
Path path = new Path();
path.setFillType(FillType.EVEN_ODD);
@@ -184,7 +194,8 @@ public class RectShadowPainter {
/**
* Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
* <p/>
- * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
+ * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float,
+ * float,
* float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
* drawing empty rectangles, which results in IllegalArgumentException.
*/
diff --git a/android/view/ShadowPainter.java b/android/view/ShadowPainter.java
index f09fffd1..788c6c35 100644
--- a/android/view/ShadowPainter.java
+++ b/android/view/ShadowPainter.java
@@ -41,14 +41,16 @@ public class ShadowPainter {
* @param source the source image
* @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
* #SMALL_SHADOW_SIZE}}
+ * @param alpha alpha value to apply to the shadow
*
* @return an image with the shadow painted in or the source image if shadowSize <= 1
*/
@NonNull
- public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float
+ alpha) {
shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
- return createDropShadow(source, shadowSize, 0.7f, 0);
+ return createDropShadow(source, shadowSize, 0.7f * alpha, 0);
}
/**
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index ff027a94..6f8315ae 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -16,27 +16,22 @@
package android.view;
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-
import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.PointF;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.Binder;
-import android.os.Debug;
import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
-
import dalvik.system.CloseGuard;
+import libcore.util.NativeAllocationRegistry;
import java.io.Closeable;
-import libcore.util.NativeAllocationRegistry;
-
/**
* SurfaceControl
* @hide
@@ -60,6 +55,8 @@ public class SurfaceControl {
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
+ private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
+ int rotation);
private static native long nativeCreateTransaction();
private static native long nativeGetNativeTransactionFinalizer();
@@ -186,7 +183,7 @@ public class SurfaceControl {
/**
* Surface creation flag: Indicates that the surface must be considered opaque,
- * even if its pixel format is set to translucent. This can be useful if an
+ * even if its pixel format contains an alpha channel. This can be useful if an
* application needs full RGBA 8888 support for instance but will
* still draw every pixel opaque.
* <p>
@@ -307,6 +304,203 @@ public class SurfaceControl {
public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
/**
+ * Builder class for {@link SurfaceControl} objects.
+ */
+ public static class Builder {
+ private SurfaceSession mSession;
+ private int mFlags = HIDDEN;
+ private int mWidth;
+ private int mHeight;
+ private int mFormat = PixelFormat.OPAQUE;
+ private String mName;
+ private SurfaceControl mParent;
+ private int mWindowType;
+ private int mOwnerUid;
+
+ /**
+ * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+ *
+ * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+ */
+ public Builder(SurfaceSession session) {
+ mSession = session;
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters.
+ */
+ public SurfaceControl build() {
+ if (mWidth <= 0 || mHeight <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be set");
+ }
+ return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
+ mFlags, mParent, mWindowType, mOwnerUid);
+ }
+
+ /**
+ * Set a debugging-name for the SurfaceControl.
+ *
+ * @param name A name to identify the Surface in debugging.
+ */
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Set the initial size of the controlled surface's buffers in pixels.
+ *
+ * @param width The buffer width in pixels.
+ * @param height The buffer height in pixels.
+ */
+ public Builder setSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be positive");
+ }
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ /**
+ * Set the pixel format of the controlled surface's buffers, using constants from
+ * {@link android.graphics.PixelFormat}.
+ */
+ public Builder setFormat(@PixelFormat.Format int format) {
+ mFormat = format;
+ return this;
+ }
+
+ /**
+ * Specify if the app requires a hardware-protected path to
+ * an external display sync. If protected content is enabled, but
+ * such a path is not available, then the controlled Surface will
+ * not be displayed.
+ *
+ * @param protectedContent Whether to require a protected sink.
+ */
+ public Builder setProtected(boolean protectedContent) {
+ if (protectedContent) {
+ mFlags |= PROTECTED_APP;
+ } else {
+ mFlags &= ~PROTECTED_APP;
+ }
+ return this;
+ }
+
+ /**
+ * Specify whether the Surface contains secure content. If true, the system
+ * will prevent the surfaces content from being copied by another process. In
+ * particular screenshots and VNC servers will be disabled. This is however
+ * not a complete prevention of readback as {@link #setProtected}.
+ */
+ public Builder setSecure(boolean secure) {
+ if (secure) {
+ mFlags |= SECURE;
+ } else {
+ mFlags &= ~SECURE;
+ }
+ return this;
+ }
+
+ /**
+ * Indicates whether the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
+ * application needs full RGBA 8888 support for instance but will
+ * still draw every pixel opaque.
+ * <p>
+ * This flag only determines whether opacity will be sampled from the alpha channel.
+ * Plane-alpha from calls to setAlpha() can still result in blended composition
+ * regardless of the opaque setting.
+ *
+ * Combined effects are (assuming a buffer format with an alpha channel):
+ * <ul>
+ * <li>OPAQUE + alpha(1.0) == opaque composition
+ * <li>OPAQUE + alpha(0.x) == blended composition
+ * <li>OPAQUE + alpha(0.0) == no composition
+ * <li>!OPAQUE + alpha(1.0) == blended composition
+ * <li>!OPAQUE + alpha(0.x) == blended composition
+ * <li>!OPAQUE + alpha(0.0) == no composition
+ * </ul>
+ * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+ * were set automatically.
+ * @param opaque Whether the Surface is OPAQUE.
+ */
+ public Builder setOpaque(boolean opaque) {
+ if (opaque) {
+ mFlags |= OPAQUE;
+ } else {
+ mFlags &= ~OPAQUE;
+ }
+ return this;
+ }
+
+ /**
+ * Set a parent surface for our new SurfaceControl.
+ *
+ * Child surfaces are constrained to the onscreen region of their parent.
+ * Furthermore they stack relatively in Z order, and inherit the transformation
+ * of the parent.
+ *
+ * @param parent The parent control.
+ */
+ public Builder setParent(SurfaceControl parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Set surface metadata.
+ *
+ * Currently these are window-types as per {@link WindowManager.LayoutParams} and
+ * owner UIDs. Child surfaces inherit their parents
+ * metadata so only the WindowManager needs to set this on root Surfaces.
+ *
+ * @param windowType A window-type
+ * @param ownerUid UID of the window owner.
+ */
+ public Builder setMetadata(int windowType, int ownerUid) {
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ throw new UnsupportedOperationException(
+ "It only makes sense to set Surface metadata from the WindowManager");
+ }
+ mWindowType = windowType;
+ mOwnerUid = ownerUid;
+ return this;
+ }
+
+ /**
+ * Indicate whether a 'ColorLayer' is to be constructed.
+ *
+ * Color layers will not have an associated BufferQueue and will instead always render a
+ * solid color (that is, solid before plane alpha). Currently that color is black.
+ *
+ * @param isColorLayer Whether to create a color layer.
+ */
+ public Builder setColorLayer(boolean isColorLayer) {
+ if (isColorLayer) {
+ mFlags |= FX_SURFACE_DIM;
+ } else {
+ mFlags &= ~FX_SURFACE_DIM;
+ }
+ return this;
+ }
+
+ /**
+ * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}.
+ *
+ * TODO: Finish conversion to individual builder methods?
+ * @param flags The combined flags
+ */
+ public Builder setFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+ }
+
+ /**
* Create a surface with a name.
* <p>
* The surface creation flags specify what kind of surface to create and
@@ -331,19 +525,7 @@ public class SurfaceControl {
*
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags, int windowType, int ownerUid)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, windowType, ownerUid);
- }
-
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
- }
-
- public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException {
if (session == null) {
@@ -533,7 +715,7 @@ public class SurfaceControl {
}
}
- public void setRelativeLayer(IBinder relativeTo, int zorder) {
+ public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
checkNotReleased();
synchronized(SurfaceControl.class) {
sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
@@ -970,6 +1152,20 @@ public class SurfaceControl {
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+ /**
+ * Captures a layer and its children into the provided {@link Surface}.
+ *
+ * @param layerHandleToken The root layer to capture.
+ * @param consumer The {@link Surface} to capture the layer into.
+ * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+ * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
+ * native portrait orientation by default, so this is useful for returning
+ * captures that are independent of device orientation.
+ */
+ public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
+ nativeCaptureLayers(layerHandleToken, consumer, rotation);
+ }
+
public static class Transaction implements Closeable {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
@@ -1035,9 +1231,9 @@ public class SurfaceControl {
return this;
}
- public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
- relativeTo, z);
+ relativeTo.getHandle(), z);
return this;
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af45..4eab496e 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1215 @@
package android.view;
-import com.android.layoutlib.bridge.MockView;
+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 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;
/**
- * 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.
+ * 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.
*
- * TODO: generate automatically.
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
*
+ * <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 MockView {
+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 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 defStyle) {
- super(context, attrs, defStyle);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
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;
+ mParent.requestTransparentRegion(SurfaceView.this);
+ 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) {
+ 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) {
- return false;
+ 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;
}
+ @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;
+ }
}
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
+ private void updateOpaqueFlag() {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ mSurfaceFlags |= SurfaceControl.OPAQUE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+ }
}
- private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ 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();
+ final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+ mSurfaceControl = new SurfaceControlWithBackground(
+ name,
+ (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+ new SurfaceControl.Builder(mSurfaceSession)
+ .setSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setFlags(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;
+ }
+
+ private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
- return false;
+ return mIsCreating;
}
@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
- public void setType(int type) {
- }
+ @Deprecated
+ 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 null;
+ 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);
}
@Override
- public Canvas lockCanvas(Rect dirty) {
+ 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();
+
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 null;
+ return mSurface;
}
@Override
public Rect getSurfaceFrame() {
- return null;
+ return mSurfaceFrame;
}
};
-}
+ class SurfaceControlWithBackground extends SurfaceControl {
+ private SurfaceControl mBackgroundControl;
+ private boolean mOpaque = true;
+ public boolean mVisible = false;
+
+ public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
+ throws Exception {
+ super(b.setName(name).build());
+
+ mBackgroundControl = b.setName("Background for -" + name)
+ .setFormat(OPAQUE)
+ .setColorLayer(true)
+ .build();
+ mOpaque = opaque;
+ }
+
+ @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/TouchDelegate.java b/android/view/TouchDelegate.java
index cf36f436..dc50fa1d 100644
--- a/android/view/TouchDelegate.java
+++ b/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@ public class TouchDelegate {
/**
* mBounds inflated to include some slop. This rect is to track whether the motion events
- * should be considered to be be within the delegate view.
+ * should be considered to be within the delegate view.
*/
private Rect mSlopBounds;
@@ -64,14 +64,12 @@ public class TouchDelegate {
public static final int BELOW = 2;
/**
- * The touchable region of the View extends to the left of its
- * actual extent.
+ * The touchable region of the View extends to the left of its actual extent.
*/
public static final int TO_LEFT = 4;
/**
- * The touchable region of the View extends to the right of its
- * actual extent.
+ * The touchable region of the View extends to the right of its actual extent.
*/
public static final int TO_RIGHT = 8;
@@ -108,28 +106,24 @@ public class TouchDelegate {
boolean handled = false;
switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Rect bounds = mBounds;
-
- if (bounds.contains(x, y)) {
- mDelegateTargeted = true;
- sendToDelegate = true;
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_MOVE:
- sendToDelegate = mDelegateTargeted;
- if (sendToDelegate) {
- Rect slopBounds = mSlopBounds;
- if (!slopBounds.contains(x, y)) {
- hit = false;
+ case MotionEvent.ACTION_DOWN:
+ mDelegateTargeted = mBounds.contains(x, y);
+ sendToDelegate = mDelegateTargeted;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ sendToDelegate = mDelegateTargeted;
+ if (sendToDelegate) {
+ Rect slopBounds = mSlopBounds;
+ if (!slopBounds.contains(x, y)) {
+ hit = false;
+ }
}
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- sendToDelegate = mDelegateTargeted;
- mDelegateTargeted = false;
- break;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ sendToDelegate = mDelegateTargeted;
+ mDelegateTargeted = false;
+ break;
}
if (sendToDelegate) {
final View delegateView = mDelegateView;
diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java
index 4b760a7d..6daae200 100644
--- a/android/view/ViewGroup_Delegate.java
+++ b/android/view/ViewGroup_Delegate.java
@@ -67,12 +67,12 @@ public class ViewGroup_Delegate {
Outline outline) {
float elevation = getElevation(child, parent);
if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
- RectShadowPainter.paintShadow(outline, elevation, canvas);
+ RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
return;
}
BufferedImage shadow = null;
if (outline.mPath != null) {
- shadow = getPathShadow(outline, canvas, elevation);
+ shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
}
if (shadow == null) {
return;
@@ -91,7 +91,8 @@ public class ViewGroup_Delegate {
return child.getZ() - parent.getZ();
}
- private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+ private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+ float alpha) {
Rect clipBounds = canvas.getClipBounds();
if (clipBounds.isEmpty()) {
return null;
@@ -101,7 +102,7 @@ public class ViewGroup_Delegate {
Graphics2D graphics = image.createGraphics();
graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
graphics.dispose();
- return ShadowPainter.createDropShadow(image, (int) elevation);
+ return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
}
// Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 99438d87..37829f0b 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -2284,18 +2284,36 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (mFirst && sAlwaysAssignFocus) {
- // handle first focus request
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
- + mView.hasFocus());
- if (mView != null) {
- if (!mView.hasFocus()) {
- mView.restoreDefaultFocus();
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
- + mView.findFocus());
- } else {
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
- + mView.findFocus());
+ if (mFirst) {
+ if (sAlwaysAssignFocus) {
+ // handle first focus request
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+ }
+ if (mView != null) {
+ if (!mView.hasFocus()) {
+ mView.restoreDefaultFocus();
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+ }
+ } else {
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+ }
+ }
+ }
+ } else {
+ // Some views (like ScrollView) won't hand focus to descendants that aren't within
+ // their viewport. Before layout, there's a good change these views are size 0
+ // which means no children can get focus. After layout, this view now has size, but
+ // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+ // case where the child has a size prior to layout and thus won't trigger
+ // focusableViewAvailable).
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup
+ && ((ViewGroup) focused).getDescendantFocusability()
+ == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+ focused.restoreDefaultFocus();
}
}
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046a..0b9bc576 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
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.
- * Such events are generated when something notable happens in the user interface,
+ * 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,
* 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
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
*/
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
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;
- private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+ int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ 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 accessibility state.
+ * 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}.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called back on change in the accessibility state.
+ * Called when the accessibility enabled state changes.
*
* @param enabled Whether accessibility is enabled.
*/
- public void onAccessibilityStateChanged(boolean enabled);
+ void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -71,7 +177,24 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- public void onTouchExplorationStateChanged(boolean 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);
}
/**
@@ -79,6 +202,8 @@ 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 {
@@ -87,26 +212,72 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- public void onHighTextContrastStateChanged(boolean enabled);
+ void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- public void setState(int state) {
- }
+ @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();
+ }
- public void notifyServicesStateChanged() {
+ @Override
+ public void notifyServicesStateChanged() {
+ final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mServicesStateChangeListeners.isEmpty()) {
+ return;
}
+ listeners = new ArrayMap<>(mServicesStateChangeListeners);
+ }
- public void setRelevantEventTypes(int eventTypes) {
- }
- };
+ 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));
+ }
+ }
+
+ @Override
+ public void setRelevantEventTypes(int eventTypes) {
+ mRelevantEventTypes = 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;
}
@@ -114,21 +285,68 @@ 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;
}
/**
- * Returns if the {@link AccessibilityManager} is enabled.
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns if the accessibility in the system is enabled.
*
- * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ * @return True if accessibility is enabled, false otherwise.
*/
public boolean isEnabled() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsEnabled;
+ }
}
/**
@@ -137,7 +355,13 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- return true;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsTouchExplorationEnabled;
+ }
}
/**
@@ -147,35 +371,169 @@ 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() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsHighTextContrastEnabled;
+ }
}
/**
* 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 interruption of the accessibility feedback from all accessibility services.
+ * Requests feedback interruption 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() {
- return Collections.emptyList();
+ 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);
}
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- return Collections.emptyList();
+ 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();
+ }
}
/**
@@ -190,21 +548,48 @@ 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) {
- return Collections.emptyList();
+ 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();
+ }
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system.
+ * the global accessibility state of the system. Equivalent to calling
+ * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
+ @NonNull AccessibilityStateChangeListener listener) {
+ addAccessibilityStateChangeListener(listener, null);
return true;
}
@@ -218,22 +603,40 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mAccessibilityStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if the listener was previously registered.
+ */
public boolean removeAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
- return true;
+ @NonNull AccessibilityStateChangeListener listener) {
+ synchronized (mLock) {
+ int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+ mAccessibilityStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system.
+ * the global touch exploration state of the system. Equivalent to calling
+ * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
+ addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -247,17 +650,103 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mTouchExplorationStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if successfully unregistered.
+ * @return True if listener was previously registered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- return true;
+ 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);
}
/**
@@ -269,7 +758,12 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -279,7 +773,51 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {}
+ @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;
+ }
+ }
/**
* Sets the current state and notifies listeners, if necessary.
@@ -287,14 +825,314 @@ 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/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e564fa34..e79d201b 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -224,7 +225,7 @@ public final class AutofillManager {
/**
* State where the autofill context was finished by the server because the autofill
- * service could not autofill the page.
+ * service could not autofill the activity.
*
* <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
* exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
@@ -242,6 +243,16 @@ public final class AutofillManager {
public static final int STATE_SHOWING_SAVE_UI = 3;
/**
+ * State where the autofill is disabled because the service cannot autofill the activity at all.
+ *
+ * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
+ * (and {@link #requestAutofill(View, int, Rect)}).
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED_BY_SERVICE = 4;
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
@@ -398,6 +409,11 @@ public final class AutofillManager {
* Runs the specified action on the UI thread.
*/
void runOnUiThread(Runnable action);
+
+ /**
+ * Gets the complete component name of this client.
+ */
+ ComponentName getComponentName();
}
/**
@@ -506,7 +522,7 @@ public final class AutofillManager {
* @return whether autofill is enabled for the current user.
*/
public boolean isEnabled() {
- if (!hasAutofillFeature()) {
+ if (!hasAutofillFeature() || isDisabledByService()) {
return false;
}
synchronized (mLock) {
@@ -580,19 +596,31 @@ public final class AutofillManager {
notifyViewEntered(view, 0);
}
+ private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
+ if (isDisabledByService()) {
+ if (sVerbose) {
+ Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
+ }
+ return true;
+ }
+ if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+ + ") on state " + getStateAsStringLocked());
+ }
+ return true;
+ }
+ return false;
+ }
+
private void notifyViewEntered(@NonNull View view, int flags) {
if (!hasAutofillFeature()) {
return;
}
AutofillCallback callback = null;
synchronized (mLock) {
- if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
- + "): ignored on state " + getStateAsStringLocked());
- }
- return;
- }
+ if (shouldIgnoreViewEnteredLocked(view, flags)) return;
ensureServiceClientAddedIfNeededLocked();
@@ -717,14 +745,8 @@ public final class AutofillManager {
}
AutofillCallback callback = null;
synchronized (mLock) {
- if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
- if (sVerbose) {
- Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
- + ", virtualId=" + virtualId
- + "): ignored on state " + getStateAsStringLocked());
- }
- return;
- }
+ if (shouldIgnoreViewEnteredLocked(view, flags)) return;
+
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -1059,13 +1081,13 @@ public final class AutofillManager {
return;
}
try {
+ final AutofillClient client = getClientLocked();
mSessionId = mService.startSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
- mCallback != null, flags, mContext.getOpPackageName());
+ mCallback != null, flags, client.getComponentName());
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
- final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
@@ -1120,14 +1142,14 @@ public final class AutofillManager {
try {
if (restartIfNecessary) {
+ final AutofillClient client = getClientLocked();
final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
- mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
+ mCallback != null, flags, client.getComponentName(), mSessionId, action);
if (newId != mSessionId) {
if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
mSessionId = newId;
mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
- final AutofillClient client = getClientLocked();
if (client != null) {
client.autofillCallbackResetableStateAvailable();
}
@@ -1437,7 +1459,9 @@ public final class AutofillManager {
* Marks the state of the session as finished.
*
* @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
- * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+ * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or
+ * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill
+ * requests for the activity).
*/
private void setSessionFinished(int newState) {
synchronized (mLock) {
@@ -1482,10 +1506,10 @@ public final class AutofillManager {
}
}
- private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
if (sVerbose) {
Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
- + ", finished=" + sessionFinished);
+ + ", sessionFinishedState=" + sessionFinishedState);
}
final View anchor = findView(id);
if (anchor == null) {
@@ -1508,9 +1532,9 @@ public final class AutofillManager {
}
}
- if (sessionFinished) {
+ if (sessionFinishedState != 0) {
// Callback call was "hijacked" to also update the session state.
- setSessionFinished(STATE_FINISHED);
+ setSessionFinished(sessionFinishedState);
}
}
@@ -1613,6 +1637,8 @@ public final class AutofillManager {
return "STATE_FINISHED";
case STATE_SHOWING_SAVE_UI:
return "STATE_SHOWING_SAVE_UI";
+ case STATE_DISABLED_BY_SERVICE:
+ return "STATE_DISABLED_BY_SERVICE";
default:
return "INVALID:" + mState;
}
@@ -1622,8 +1648,8 @@ public final class AutofillManager {
return mState == STATE_ACTIVE;
}
- private boolean isFinishedLocked() {
- return mState == STATE_FINISHED;
+ private boolean isDisabledByService() {
+ return mState == STATE_DISABLED_BY_SERVICE;
}
private void post(Runnable runnable) {
@@ -1957,10 +1983,10 @@ public final class AutofillManager {
}
@Override
- public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
+ afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
}
}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 8c3b8a2e..26d2141e 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,52 @@ import java.util.List;
/**
* Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * Button button = new Button(context);
+ * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ * button.setText(classification.getLabel());
+ * button.setOnClickListener(classification.getOnClickListener());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ * // Called preferably outside the UiThread.
+ * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ * // Called on the UiThread.
+ * view.startActionMode(new ActionMode.Callback() {
+ *
+ * public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ * for (int i = 0; i < classification.getActionCount(); i++) {
+ * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
+ * menu.add(Menu.NONE, i, 20, classification.getLabel(i))
+ * .setIcon(classification.getIcon(i))
+ * .setIntent(classification.getIntent(i));
+ * }
+ * }
+ * return true;
+ * }
+ *
+ * public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ * context.startActivity(item.getIntent());
+ * return true;
+ * }
+ *
+ * ...
+ * });
+ * }</pre>
+ *
*/
public final class TextClassification {
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index c3601d9d..46dbd0e3 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -29,8 +29,8 @@ import java.lang.annotation.RetentionPolicy;
/**
* Interface for providing text classification related features.
*
- * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
- * avoid calling them on the UI thread.
+ * <p>Unless otherwise stated, methods of this interface are blocking operations.
+ * Avoid calling them on the UI thread.
*/
public interface TextClassifier {
@@ -75,8 +75,8 @@ public interface TextClassifier {
};
/**
- * Returns suggested text selection indices, recognized types and their associated confidence
- * scores. The selections are ordered from highest to lowest scoring.
+ * Returns suggested text selection start and end indices, recognized entity types, and their
+ * associated confidence scores. The entity types are ordered from highest to lowest scoring.
*
* @param text text providing context for the selected text (which is specified
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index 8e1f2183..f368c74a 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,213 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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;
/**
- * A stub class of TextServicesManager for Layout-Lib.
+ * 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>
+ *
*/
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- private static final TextServicesManager sInstance = new TextServicesManager();
- private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+ 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));
+ }
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- return sInstance;
+ 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);
+ }
}
+ /**
+ * 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) {
- return null;
+ 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;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- return EMPTY_SPELL_CHECKER_INFO;
+ 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();
+ }
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- return null;
+ try {
+ // Passing null as a locale for ICS
+ return mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -60,13 +215,22 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- return null;
+ 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();
+ }
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- return false;
+ try {
+ return mService.isSpellCheckerEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/android/webkit/MimeTypeMap.java b/android/webkit/MimeTypeMap.java
index 39874e82..38616952 100644
--- a/android/webkit/MimeTypeMap.java
+++ b/android/webkit/MimeTypeMap.java
@@ -37,7 +37,7 @@ public class MimeTypeMap {
}
/**
- * Returns the file extension or an empty string iff there is no
+ * Returns the file extension or an empty string if there is no
* extension. This method is a convenience method for obtaining the
* extension of a url and has undefined results for other Strings.
* @param url
@@ -76,7 +76,7 @@ public class MimeTypeMap {
/**
* Return {@code true} if the given MIME type has an entry in the map.
* @param mimeType A MIME type (i.e. text/plain)
- * @return {@code true} iff there is a mimeType entry in the map.
+ * @return {@code true} if there is a mimeType entry in the map.
*/
public boolean hasMimeType(String mimeType) {
return MimeUtils.hasMimeType(mimeType);
@@ -85,7 +85,7 @@ public class MimeTypeMap {
/**
* Return the MIME type for the given extension.
* @param extension A file extension without the leading '.'
- * @return The MIME type for the given extension or {@code null} iff there is none.
+ * @return The MIME type for the given extension or {@code null} if there is none.
*/
@Nullable
public String getMimeTypeFromExtension(String extension) {
@@ -100,7 +100,7 @@ public class MimeTypeMap {
/**
* Return {@code true} if the given extension has a registered MIME type.
* @param extension A file extension without the leading '.'
- * @return {@code true} iff there is an extension entry in the map.
+ * @return {@code true} if there is an extension entry in the map.
*/
public boolean hasExtension(String extension) {
return MimeUtils.hasExtension(extension);
@@ -111,7 +111,7 @@ public class MimeTypeMap {
* MIME types map to multiple extensions. This call will return the most
* common extension for the given MIME type.
* @param mimeType A MIME type (i.e. text/plain)
- * @return The extension for the given MIME type or {@code null} iff there is none.
+ * @return The extension for the given MIME type or {@code null} if there is none.
*/
@Nullable
public String getExtensionFromMimeType(String mimeType) {
diff --git a/android/webkit/URLUtil.java b/android/webkit/URLUtil.java
index c2f121a1..84c000a3 100644
--- a/android/webkit/URLUtil.java
+++ b/android/webkit/URLUtil.java
@@ -137,7 +137,7 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is correctly URL encoded
+ * @return {@code true} if the url is correctly URL encoded
*/
static boolean verifyURLEncoding(String url) {
int count = url.length();
@@ -171,14 +171,14 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is an asset file.
+ * @return {@code true} if the url is an asset file.
*/
public static boolean isAssetUrl(String url) {
return (null != url) && url.startsWith(ASSET_BASE);
}
/**
- * @return {@code true} iff the url is a resource file.
+ * @return {@code true} if the url is a resource file.
* @hide
*/
public static boolean isResourceUrl(String url) {
@@ -186,7 +186,7 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is a proxy url to allow cookieless network
+ * @return {@code true} if the url is a proxy url to allow cookieless network
* requests from a file url.
* @deprecated Cookieless proxy is no longer supported.
*/
@@ -196,7 +196,7 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is a local file.
+ * @return {@code true} if the url is a local file.
*/
public static boolean isFileUrl(String url) {
return (null != url) && (url.startsWith(FILE_BASE) &&
@@ -205,28 +205,28 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is an about: url.
+ * @return {@code true} if the url is an about: url.
*/
public static boolean isAboutUrl(String url) {
return (null != url) && url.startsWith("about:");
}
/**
- * @return {@code true} iff the url is a data: url.
+ * @return {@code true} if the url is a data: url.
*/
public static boolean isDataUrl(String url) {
return (null != url) && url.startsWith("data:");
}
/**
- * @return {@code true} iff the url is a javascript: url.
+ * @return {@code true} if the url is a javascript: url.
*/
public static boolean isJavaScriptUrl(String url) {
return (null != url) && url.startsWith("javascript:");
}
/**
- * @return {@code true} iff the url is an http: url.
+ * @return {@code true} if the url is an http: url.
*/
public static boolean isHttpUrl(String url) {
return (null != url) &&
@@ -235,7 +235,7 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is an https: url.
+ * @return {@code true} if the url is an https: url.
*/
public static boolean isHttpsUrl(String url) {
return (null != url) &&
@@ -244,7 +244,7 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is a network url.
+ * @return {@code true} if the url is a network url.
*/
public static boolean isNetworkUrl(String url) {
if (url == null || url.length() == 0) {
@@ -254,14 +254,14 @@ public final class URLUtil {
}
/**
- * @return {@code true} iff the url is a content: url.
+ * @return {@code true} if the url is a content: url.
*/
public static boolean isContentUrl(String url) {
return (null != url) && url.startsWith(CONTENT_BASE);
}
/**
- * @return {@code true} iff the url is valid.
+ * @return {@code true} if the url is valid.
*/
public static boolean isValidUrl(String url) {
if (url == null || url.length() == 0) {
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 202f2046..259bf60a 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2006 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,223 +16,3001 @@
package android.webkit;
-import com.android.layoutlib.bridge.MockView;
-
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.Widget;
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;
/**
- * 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.
+ * <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>
*
*/
-public class WebView extends MockView {
+// 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;
+ /**
+ * 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:";
/**
- * Construct a new WebView with a Context object.
- * @param context A Context object used to access application assets.
+ * 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
+ void onNewPicture(WebView view, @Nullable 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
+ */
+ @Nullable
+ public String getExtra() {
+ return mExtra;
+ }
+ }
+
+ /**
+ * Constructs a new WebView with a Context object.
+ *
+ * @param context a Context object used to access application assets
*/
public WebView(Context context) {
this(context, null);
}
/**
- * 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.
+ * 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
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * 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.
+ * 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.
*/
- public WebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
-
- // START FAKE PUBLIC METHODS
-
+
+ /**
+ * 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
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() {
- return false;
+ // The old implementation defaulted to true, so return true for consistency
+ return true;
}
+ /**
+ * 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
+ */
+ @Nullable
+ 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
+ @Nullable
public String[] getHttpAuthUsernamePassword(String host, String realm) {
- return null;
+ checkThread();
+ return mProvider.getHttpAuthUsernamePassword(host, realm);
}
+ /**
+ * 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, {@code null} if the
+ * method fails.
+ */
+ @Nullable
+ 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
+ */
+ @Nullable
+ 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);
+ }
}
- public void loadData(String data, String mimeType, String encoding) {
+ /**
+ * 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 MIMEType of the data, e.g. 'text/html'. If {@code null},
+ * defaults to 'text/html'.
+ * @param encoding the encoding of the data
+ */
+ public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
+ checkThread();
+ mProvider.loadData(data, mimeType, encoding);
}
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String failUrl) {
+ /**
+ * 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(@Nullable String baseUrl, String data,
+ @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
+ checkThread();
+ mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
+ /**
+ * 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, @Nullable 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, @Nullable 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} if this WebView has a back history item
+ */
public boolean canGoBack() {
- return false;
+ checkThread();
+ return mProvider.canGoBack();
}
+ /**
+ * 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} if this WebView has a forward history item
+ */
public boolean canGoForward() {
- return false;
+ checkThread();
+ return mProvider.canGoForward();
}
+ /**
+ * 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) {
- return false;
+ checkThread();
+ return mProvider.canGoBackOrForward(steps);
}
+ /**
+ * 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) {
- return false;
+ checkThread();
+ return mProvider.pageUp(top);
}
-
+
+ /**
+ * 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) {
- return false;
+ 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);
+ }
+
+ /**
+ * 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() {
- return null;
+ 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");
}
+ /**
+ * 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() {
- return 0;
+ checkThread();
+ return mProvider.getScale();
}
+ /**
+ * 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();
}
- public void requestFocusNodeHref(Message hrefMsg) {
+ /**
+ * 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(@Nullable 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() {
- return null;
+ checkThread();
+ return mProvider.getUrl();
}
+ /**
+ * 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() {
- return null;
+ checkThread();
+ return mProvider.getTitle();
}
+ /**
+ * 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() {
- return null;
+ checkThread();
+ return mProvider.getFavicon();
}
+ /**
+ * 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() {
- return 0;
+ checkThread();
+ return mProvider.getProgress();
}
-
+
+ /**
+ * Gets the height of the HTML content.
+ *
+ * @return the height of the HTML content
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public int getContentHeight() {
- return 0;
+ 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();
}
+ /**
+ * 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();
}
- public void clearCache() {
+ /**
+ * 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);
}
+ /**
+ * 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 runnable will be called in UI thread.
+ */
+ public static void clearClientCertPreferences(@Nullable 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,
+ @Nullable 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(@Nullable 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}
+ */
+ @Nullable
public static String findAddress(String addr) {
- return null;
+ // 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();
}
+ /**
+ * 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);
}
- public void addJavascriptInterface(Object obj, String interfaceName) {
+ /**
+ * Gets the chrome handler.
+ *
+ * @return the WebChromeClient, or {@code null} if not yet set
+ * @see #setWebChromeClient
+ */
+ @Nullable
+ 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(@NonNull 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 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() {
- return null;
+ checkThread();
+ return mProvider.getZoomControls();
}
+ /**
+ * 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() {
- return false;
+ checkThread();
+ return mProvider.zoomIn();
}
+ /**
+ * Performs zoom out in this WebView.
+ *
+ * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
+ */
public boolean zoomOut() {
- return false;
+ 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>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+ * {@link ViewStructure#setAutofillHints(String[])}.
+ * <li>If the view is editable, 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>If the WebView implementation can determine that the value of a field was set statically
+ * (for example, not through Javascript), it should also call
+ * {@code structure.setDataIsSensitive(false)}.
+ *
+ * <p>For example, 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.setAutofillHints("username");
+ * username.setHtmlInfo(username.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "text")
+ * .addAttribute("name", "username")
+ * .build());
+ * username.setHint("Email or username");
+ * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * username.setAutofillValue(AutofillValue.forText("Type your username"));
+ * // Value of the field is not sensitive because it was created statically and not changed.
+ * username.setDataIsSensitive(false);
+ *
+ * ViewStructure password = structure.newChild(index + 1);
+ * username.setAutofillId(structure, 2); // id 2 - second child
+ * password.setAutofillHints("current-password");
+ * password.setHtmlInfo(password.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "password")
+ * .addAttribute("name", "password")
+ * .build());
+ * password.setHint("Password");
+ * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * </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.
+ */
+ @Nullable
+ public static PackageInfo getCurrentWebViewPackage() {
+ PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
+ if (webviewPackage != null) {
+ return webviewPackage;
+ }
+
+ IWebViewUpdateService service = WebViewFactory.getUpdateService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.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());
}
}
diff --git a/android/webkit/WebViewFactory.java b/android/webkit/WebViewFactory.java
index 95cb4549..797bdfb7 100644
--- a/android/webkit/WebViewFactory.java
+++ b/android/webkit/WebViewFactory.java
@@ -25,7 +25,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -138,7 +137,7 @@ public final class WebViewFactory {
}
/**
- * Load the native library for the given package name iff that package
+ * Load the native library for the given package name if that package
* name is the same as the one providing the webview.
*/
public static int loadWebViewNativeLibraryFromPackage(String packageName,
@@ -445,38 +444,17 @@ public final class WebViewFactory {
}
}
- private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
- if (DEBUG) Log.v(LOGTAG, "creating relro files");
- int numRelros = 0;
-
- // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
- // unexpected values will be handled there to ensure that we trigger notifying any process
- // waiting on relro creation.
- if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
- WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
- numRelros++;
- }
-
- if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
- if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
- WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
- numRelros++;
- }
- return numRelros;
- }
-
/**
* @hide
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
- String[] nativeLibs = null;
+ int startedRelroProcesses = 0;
ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
fixupStubApplicationInfo(packageInfo.applicationInfo,
AppGlobals.getInitialApplication().getPackageManager());
- nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo);
+ startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
@@ -484,7 +462,7 @@ public final class WebViewFactory {
WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
- return prepareWebViewInSystemServer(nativeLibs);
+ return startedRelroProcesses;
}
private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index fa1a3907..341c69fd 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -26,6 +28,7 @@ import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import dalvik.system.VMRuntime;
@@ -36,7 +39,11 @@ import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-class WebViewLibraryLoader {
+/**
+ * @hide
+ */
+@VisibleForTesting
+public class WebViewLibraryLoader {
private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
@@ -94,7 +101,7 @@ class WebViewLibraryLoader {
/**
* Create a single relro file by invoking an isolated process that to do the actual work.
*/
- static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) {
+ static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
final String abi =
is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
@@ -112,12 +119,12 @@ class WebViewLibraryLoader {
};
try {
- if (nativeLibraryPath == null) {
+ if (nativeLib == null || nativeLib.path == null) {
throw new IllegalArgumentException(
"Native library paths to the WebView RelRo process must not be null!");
}
int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
- RelroFileCreator.class.getName(), new String[] { nativeLibraryPath },
+ RelroFileCreator.class.getName(), new String[] { nativeLib.path },
"WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
@@ -128,56 +135,77 @@ class WebViewLibraryLoader {
}
/**
+ * Perform preparations needed to allow loading WebView from an application. This method should
+ * be called whenever we change WebView provider.
+ * @return the number of relro processes started.
+ */
+ static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
+ throws WebViewFactory.MissingWebViewPackageException {
+ WebViewNativeLibrary nativeLib32bit =
+ getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
+ WebViewNativeLibrary nativeLib64bit =
+ getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
+ updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
+
+ return createRelros(nativeLib32bit, nativeLib64bit);
+ }
+
+ /**
+ * @return the number of relro processes started.
+ */
+ private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
+ @Nullable WebViewNativeLibrary nativeLib64bit) {
+ if (DEBUG) Log.v(LOGTAG, "creating relro files");
+ int numRelros = 0;
+
+ if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+ if (nativeLib32bit == null) {
+ Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
+ } else {
+ if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
+ createRelroFile(false /* is64Bit */, nativeLib32bit);
+ numRelros++;
+ }
+ }
+
+ if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+ if (nativeLib64bit == null) {
+ Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
+ } else {
+ if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
+ createRelroFile(true /* is64Bit */, nativeLib64bit);
+ numRelros++;
+ }
+ }
+ return numRelros;
+ }
+
+ /**
*
* @return the native WebView libraries in the new WebView APK.
*/
- static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
+ private static void updateWebViewZygoteVmSize(
+ @Nullable WebViewNativeLibrary nativeLib32bit,
+ @Nullable WebViewNativeLibrary nativeLib64bit)
throws WebViewFactory.MissingWebViewPackageException {
// Find the native libraries of the new WebView package, to change the size of the
// memory region in the Zygote reserved for the library.
- String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo);
- if (nativeLibs != null) {
- long newVmSize = 0L;
-
- for (String path : nativeLibs) {
- if (path == null || TextUtils.isEmpty(path)) continue;
- if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
- File f = new File(path);
- if (f.exists()) {
- newVmSize = Math.max(newVmSize, f.length());
- continue;
- }
- if (path.contains("!/")) {
- String[] split = TextUtils.split(path, "!/");
- if (split.length == 2) {
- try (ZipFile z = new ZipFile(split[0])) {
- ZipEntry e = z.getEntry(split[1]);
- if (e != null && e.getMethod() == ZipEntry.STORED) {
- newVmSize = Math.max(newVmSize, e.getSize());
- continue;
- }
- }
- catch (IOException e) {
- Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
- }
- }
- }
- Log.e(LOGTAG, "error sizing load for " + path);
- }
+ long newVmSize = 0L;
- if (DEBUG) {
- Log.v(LOGTAG, "Based on library size, need " + newVmSize
- + " bytes of address space.");
- }
- // The required memory can be larger than the file on disk (due to .bss), and an
- // upgraded version of the library will likely be larger, so always attempt to
- // reserve twice as much as we think to allow for the library to grow during this
- // boot cycle.
- newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
- Log.d(LOGTAG, "Setting new address space to " + newVmSize);
- setWebViewZygoteVmSize(newVmSize);
+ if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
+ if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
+
+ if (DEBUG) {
+ Log.v(LOGTAG, "Based on library size, need " + newVmSize
+ + " bytes of address space.");
}
- return nativeLibs;
+ // The required memory can be larger than the file on disk (due to .bss), and an
+ // upgraded version of the library will likely be larger, so always attempt to
+ // reserve twice as much as we think to allow for the library to grow during this
+ // boot cycle.
+ newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+ Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+ setWebViewZygoteVmSize(newVmSize);
}
/**
@@ -227,64 +255,78 @@ class WebViewLibraryLoader {
/**
* Fetch WebView's native library paths from {@param packageInfo}.
+ * @hide
*/
- static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
- throws WebViewFactory.MissingWebViewPackageException {
+ @Nullable
+ @VisibleForTesting
+ public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
+ boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
ApplicationInfo ai = packageInfo.applicationInfo;
final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
- String path32;
- String path64;
- boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
- if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
- // Multi-arch case.
- if (primaryArchIs64bit) {
- // Primary arch: 64-bit, secondary: 32-bit.
- path64 = ai.nativeLibraryDir;
- path32 = ai.secondaryNativeLibraryDir;
- } else {
- // Primary arch: 32-bit, secondary: 64-bit.
- path64 = ai.secondaryNativeLibraryDir;
- path32 = ai.nativeLibraryDir;
- }
- } else if (primaryArchIs64bit) {
- // Single-arch 64-bit.
- path64 = ai.nativeLibraryDir;
- path32 = "";
- } else {
- // Single-arch 32-bit.
- path32 = ai.nativeLibraryDir;
- path64 = "";
+ String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
+
+ WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
+ is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
+
+ if (DEBUG) {
+ Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
}
+ return lib;
+ }
- // Form the full paths to the extracted native libraries.
- // If libraries were not extracted, try load from APK paths instead.
- if (!TextUtils.isEmpty(path32)) {
- path32 += "/" + nativeLibFileName;
- File f = new File(path32);
- if (!f.exists()) {
- path32 = getLoadFromApkPath(ai.sourceDir,
- Build.SUPPORTED_32_BIT_ABIS,
- nativeLibFileName);
- }
+ /**
+ * @return the directory of the native WebView library with bitness {@param is64bit}.
+ * @hide
+ */
+ @VisibleForTesting
+ public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
+ // Primary arch has the same bitness as the library we are looking for.
+ if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
+
+ // Secondary arch has the same bitness as the library we are looking for.
+ if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
+ return ai.secondaryNativeLibraryDir;
}
- if (!TextUtils.isEmpty(path64)) {
- path64 += "/" + nativeLibFileName;
- File f = new File(path64);
- if (!f.exists()) {
- path64 = getLoadFromApkPath(ai.sourceDir,
- Build.SUPPORTED_64_BIT_ABIS,
- nativeLibFileName);
- }
+
+ return "";
+ }
+
+ /**
+ * @return an object describing a native WebView library given the directory path of that
+ * library, or null if the library couldn't be found.
+ */
+ @Nullable
+ private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
+ String nativeLibFileName, String[] abiList, String libDirectory)
+ throws WebViewFactory.MissingWebViewPackageException {
+ if (TextUtils.isEmpty(libDirectory)) return null;
+ String libPath = libDirectory + "/" + nativeLibFileName;
+ File f = new File(libPath);
+ if (f.exists()) {
+ return new WebViewNativeLibrary(libPath, f.length());
+ } else {
+ return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
}
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static class WebViewNativeLibrary {
+ public final String path;
+ public final long size;
- if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
- return new String[] { path32, path64 };
+ WebViewNativeLibrary(String path, long size) {
+ this.path = path;
+ this.size = size;
+ }
}
- private static String getLoadFromApkPath(String apkPath,
- String[] abiList,
- String nativeLibFileName)
+ private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
+ String[] abiList,
+ String nativeLibFileName)
throws WebViewFactory.MissingWebViewPackageException {
// Search the APK for a native library conforming to a listed ABI.
try (ZipFile z = new ZipFile(apkPath)) {
@@ -293,13 +335,13 @@ class WebViewLibraryLoader {
ZipEntry e = z.getEntry(entry);
if (e != null && e.getMethod() == ZipEntry.STORED) {
// Return a path formatted for dlopen() load from APK.
- return apkPath + "!/" + entry;
+ return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
}
}
} catch (IOException e) {
throw new WebViewFactory.MissingWebViewPackageException(e);
}
- return "";
+ return null;
}
/**
diff --git a/android/widget/AdapterView.java b/android/widget/AdapterView.java
index dd01251c..6c192563 100644
--- a/android/widget/AdapterView.java
+++ b/android/widget/AdapterView.java
@@ -202,7 +202,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* The last selected position we used when notifying
*/
int mOldSelectedPosition = INVALID_POSITION;
-
+
/**
* The id of the last selected position we used when notifying
*/
@@ -382,7 +382,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* position is different from the previously selected position or if
* there was no selected item.</p>
*
- * Impelmenters can call getItemAtPosition(position) if they need to access the
+ * Implementers can call getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* @param parent The AdapterView where the selection happened
@@ -778,8 +778,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
- if (mDataChanged) {
- this.onLayout(false, mLeft, mTop, mRight, mBottom);
+ if (mDataChanged) {
+ this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
@@ -1304,4 +1304,4 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
structure.setAutofillOptions(options);
}
}
-} \ No newline at end of file
+}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 384f4f83..e6da69dc 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -129,7 +129,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
-
+import java.util.Map;
/**
* Helper class used by TextView to handle editable text views.
@@ -163,9 +163,9 @@ public class Editor {
private static final int MENU_ITEM_ORDER_REPLACE = 9;
private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+ private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
- private static final float MAGNIFIER_ZOOM = 1.25f;
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -3793,6 +3793,7 @@ public class Editor {
private final RectF mSelectionBounds = new RectF();
private final boolean mHasSelection;
private final int mHandleHeight;
+ private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
public TextActionModeCallback(boolean hasSelection) {
mHasSelection = hasSelection;
@@ -3820,6 +3821,8 @@ public class Editor {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mAssistClickHandlers.clear();
+
mode.setTitle(null);
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
@@ -3903,14 +3906,14 @@ public class Editor {
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu);
+ updateAssistMenuItems(menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateSelectAllItem(menu);
updateReplaceItem(menu);
- updateAssistMenuItem(menu);
+ updateAssistMenuItems(menu);
Callback customCallback = getCustomCallback();
if (customCallback != null) {
@@ -3943,32 +3946,118 @@ public class Editor {
}
}
- private void updateAssistMenuItem(Menu menu) {
- menu.removeItem(TextView.ID_ASSIST);
+ private void updateAssistMenuItems(Menu menu) {
+ clearAssistMenuItems(menu);
+ if (!mTextView.isDeviceProvisioned()) {
+ return;
+ }
final TextClassification textClassification =
getSelectionActionModeHelper().getTextClassification();
- if (canAssist()) {
- menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
- 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()));
+ final int count = textClassification != null ? textClassification.getActionCount() : 0;
+ for (int i = 0; i < count; i++) {
+ if (!isValidAssistMenuItem(i)) {
+ continue;
+ }
+ final int groupId = TextView.ID_ASSIST;
+ final int order = (i == 0)
+ ? MENU_ITEM_ORDER_ASSIST
+ : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
+ final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE;
+ final int showAsFlag = (i == 0)
+ ? MenuItem.SHOW_AS_ACTION_ALWAYS
+ : MenuItem.SHOW_AS_ACTION_NEVER;
+ final MenuItem item = menu.add(
+ groupId, id, order, textClassification.getLabel(i))
+ .setIcon(textClassification.getIcon(i))
+ .setIntent(textClassification.getIntent(i));
+ item.setShowAsAction(showAsFlag);
+ mAssistClickHandlers.put(item, textClassification.getOnClickListener(i));
+ if (id == TextView.ID_ASSIST) {
+ mMetricsLogger.write(
+ new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+ .setType(MetricsEvent.TYPE_OPEN)
+ .setSubtype(textClassification.getLogType()));
+ }
+ }
+ }
+
+ private void clearAssistMenuItems(Menu menu) {
+ int i = 0;
+ while (i < menu.size()) {
+ final MenuItem menuItem = menu.getItem(i);
+ if (menuItem.getGroupId() == TextView.ID_ASSIST) {
+ menu.removeItem(menuItem.getItemId());
+ continue;
+ }
+ i++;
+ }
+ }
+
+ private boolean isValidAssistMenuItem(int index) {
+ final TextClassification textClassification =
+ getSelectionActionModeHelper().getTextClassification();
+ if (!mTextView.isDeviceProvisioned() || textClassification == null
+ || index < 0 || index >= textClassification.getActionCount()) {
+ return false;
+ }
+ final Drawable icon = textClassification.getIcon(index);
+ final CharSequence label = textClassification.getLabel(index);
+ final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
+ final OnClickListener onClick = textClassification.getOnClickListener(index);
+ final Intent intent = textClassification.getIntent(index);
+ final boolean hasAction = onClick != null || isSupportedIntent(intent);
+ return hasUi && hasAction;
+ }
+
+ private boolean isSupportedIntent(Intent intent) {
+ if (intent == null) {
+ return false;
+ }
+ final Context context = mTextView.getContext();
+ final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0);
+ final boolean samePackage = context.getPackageName().equals(
+ info.activityInfo.packageName);
+ if (samePackage) {
+ return true;
}
+
+ final boolean exported = info.activityInfo.exported;
+ final boolean requiresPermission = info.activityInfo.permission != null;
+ final boolean hasPermission = !requiresPermission
+ || context.checkSelfPermission(info.activityInfo.permission)
+ == PackageManager.PERMISSION_GRANTED;
+ return exported && hasPermission;
}
- private boolean canAssist() {
+ private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
+ Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST);
+
final TextClassification textClassification =
getSelectionActionModeHelper().getTextClassification();
- return mTextView.isDeviceProvisioned()
- && textClassification != null
- && (textClassification.getIcon() != null
- || !TextUtils.isEmpty(textClassification.getLabel()))
- && (textClassification.getOnClickListener() != null
- || (textClassification.getIntent() != null
- && mTextView.getContext().canStartActivityForResult()));
+ if (!mTextView.isDeviceProvisioned() || textClassification == null) {
+ // No textClassification result to handle the click. Eat the click.
+ return true;
+ }
+
+ OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem);
+ if (onClickListener == null) {
+ final Intent intent = assistMenuItem.getIntent();
+ if (intent != null) {
+ onClickListener = TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), intent);
+ }
+ }
+ if (onClickListener != null) {
+ onClickListener.onClick(mTextView);
+ stopTextActionMode();
+ if (assistMenuItem.getItemId() == TextView.ID_ASSIST) {
+ mMetricsLogger.action(
+ MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+ textClassification.getLogType());
+ }
+ }
+ // We tried our best.
+ return true;
}
@Override
@@ -3982,25 +4071,7 @@ public class Editor {
if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
return true;
}
- final TextClassification textClassification =
- getSelectionActionModeHelper().getTextClassification();
- if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) {
- final OnClickListener onClickListener =
- textClassification.getOnClickListener();
- if (onClickListener != null) {
- onClickListener.onClick(mTextView);
- } else {
- final Intent intent = textClassification.getIntent();
- if (intent != null) {
- TextClassification.createStartActivityOnClickListener(
- mTextView.getContext(), intent)
- .onClick(mTextView);
- }
- }
- mMetricsLogger.action(
- MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
- textClassification.getLogType());
- stopTextActionMode();
+ if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) {
return true;
}
return mTextView.onTextContextMenuItem(item.getItemId());
@@ -4029,6 +4100,8 @@ public class Editor {
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.hide();
}
+
+ mAssistClickHandlers.clear();
}
@Override
@@ -4547,7 +4620,7 @@ public class Editor {
+ mTextView.getTotalPaddingTop() - mTextView.getScrollY();
suspendBlink();
- mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
+ mMagnifier.show(xPosInView, yPosInView);
}
protected final void dismissMagnifier() {
@@ -6560,6 +6633,9 @@ public class Editor {
private void loadSupportedActivities() {
mSupportedActivities.clear();
+ if (!mContext.canStartActivityForResult()) {
+ return;
+ }
PackageManager packageManager = mTextView.getContext().getPackageManager();
List<ResolveInfo> unfiltered =
packageManager.queryIntentActivities(createProcessTextIntent(), 0);
diff --git a/android/widget/ListPopupWindow.java b/android/widget/ListPopupWindow.java
index 0d676153..adf366a4 100644
--- a/android/widget/ListPopupWindow.java
+++ b/android/widget/ListPopupWindow.java
@@ -657,6 +657,7 @@ public class ListPopupWindow implements ShowableListMenu {
mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
(heightSpec < 0)? -1 : heightSpec);
+ mPopup.getContentView().restoreDefaultFocus();
} else {
final int widthSpec;
if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -695,6 +696,7 @@ public class ListPopupWindow implements ShowableListMenu {
mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
+ mPopup.getContentView().restoreDefaultFocus();
if (!mModal || mDropDownList.isInTouchMode()) {
clearListSelection();
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index 23ebadb3..8dc8cab1 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -2461,14 +2461,14 @@ public class PopupWindow {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
enterTransition.addTarget(child);
- child.setVisibility(View.INVISIBLE);
+ child.setTransitionVisibility(View.INVISIBLE);
}
TransitionManager.beginDelayedTransition(this, enterTransition);
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- child.setVisibility(View.VISIBLE);
+ child.setTransitionVisibility(View.VISIBLE);
}
}
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 631f3882..e3309161 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -81,6 +81,7 @@ import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Map;
import java.util.Stack;
import java.util.concurrent.Executor;
@@ -185,6 +186,9 @@ public class RemoteViews implements Parcelable, Filter {
*/
private boolean mIsWidgetCollectionChild = false;
+ /** Class cookies of the Parcel this instance was read from. */
+ private final Map<Class, Object> mClassCookies;
+
private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>();
@@ -1505,10 +1509,10 @@ public class RemoteViews implements Parcelable, Filter {
}
ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
- int depth) {
+ int depth, Map<Class, Object> classCookies) {
viewId = parcel.readInt();
mIndex = parcel.readInt();
- mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
+ mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
}
public void writeToParcel(Parcel dest, int flags) {
@@ -2120,6 +2124,7 @@ public class RemoteViews implements Parcelable, Filter {
mApplication = application;
mLayoutId = layoutId;
mBitmapCache = new BitmapCache();
+ mClassCookies = null;
}
private boolean hasLandscapeAndPortraitLayouts() {
@@ -2149,6 +2154,9 @@ public class RemoteViews implements Parcelable, Filter {
mBitmapCache = new BitmapCache();
configureRemoteViewsAsChild(landscape);
configureRemoteViewsAsChild(portrait);
+
+ mClassCookies = (portrait.mClassCookies != null)
+ ? portrait.mClassCookies : landscape.mClassCookies;
}
/**
@@ -2161,15 +2169,16 @@ public class RemoteViews implements Parcelable, Filter {
mLayoutId = src.mLayoutId;
mIsWidgetCollectionChild = src.mIsWidgetCollectionChild;
mReapplyDisallowed = src.mReapplyDisallowed;
+ mClassCookies = src.mClassCookies;
if (src.hasLandscapeAndPortraitLayouts()) {
mLandscape = new RemoteViews(src.mLandscape);
mPortrait = new RemoteViews(src.mPortrait);
-
}
if (src.mActions != null) {
Parcel p = Parcel.obtain();
+ p.putClassCookies(mClassCookies);
src.writeActionsToParcel(p);
p.setDataPosition(0);
// Since src is already in memory, we do not care about stack overflow as it has
@@ -2189,10 +2198,11 @@ public class RemoteViews implements Parcelable, Filter {
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, null, null, 0);
+ this(parcel, null, null, 0, null);
}
- private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
+ private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
+ Map<Class, Object> classCookies) {
if (depth > MAX_NESTED_VIEWS
&& (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
throw new IllegalArgumentException("Too many nested views.");
@@ -2204,8 +2214,11 @@ public class RemoteViews implements Parcelable, Filter {
// We only store a bitmap cache in the root of the RemoteViews.
if (bitmapCache == null) {
mBitmapCache = new BitmapCache(parcel);
+ // Store the class cookies such that they are available when we clone this RemoteView.
+ mClassCookies = parcel.copyClassCookies();
} else {
setBitmapCache(bitmapCache);
+ mClassCookies = classCookies;
setNotRoot();
}
@@ -2218,8 +2231,9 @@ public class RemoteViews implements Parcelable, Filter {
readActionsFromParcel(parcel, depth);
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
- mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
- mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth);
+ mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
+ mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
+ mClassCookies);
mApplication = mPortrait.mApplication;
mLayoutId = mPortrait.getLayoutId();
}
@@ -2246,7 +2260,8 @@ public class RemoteViews implements Parcelable, Filter {
case REFLECTION_ACTION_TAG:
return new ReflectionAction(parcel);
case VIEW_GROUP_ACTION_ADD_TAG:
- return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
+ return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
+ mClassCookies);
case VIEW_GROUP_ACTION_REMOVE_TAG:
return new ViewGroupActionRemove(parcel);
case VIEW_CONTENT_NAVIGATION_TAG: