blob: 2c484365b377ae1cb921e7987d3952853a90697f [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.engine.systemchannels;
import androidx.annotation.NonNull;
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.StandardMethodCodec;
import java.util.HashMap;
import java.util.Map;
/**
* System channel to exchange restoration data between framework and engine.
*
* <p>The engine can obtain the current restoration data from the framework via this channel to
* store it on disk and - when the app is relaunched - provide the stored data back to the framework
* to recreate the original state of the app.
*
* <p>The channel can be configured to delay responding to the framework's request for restoration
* data via {@code waitForRestorationData} until the engine-side has provided the data. This is
* useful when the engine is pre-warmed at a point in the application's life cycle where the
* restoration data is not available yet. For example, if the engine is pre-warmed as part of the
* Application before an Activity is created, this flag should be set to true because Android will
* only provide the restoration data to the Activity during the onCreate callback.
*
* <p>The current restoration data provided by the framework can be read via {@code
* getRestorationData()}.
*/
public class RestorationChannel {
private static final String TAG = "RestorationChannel";
public RestorationChannel(
@NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) {
this(
new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE),
waitForRestorationData);
}
RestorationChannel(MethodChannel channel, @NonNull boolean waitForRestorationData) {
this.channel = channel;
this.waitForRestorationData = waitForRestorationData;
channel.setMethodCallHandler(handler);
}
/**
* Whether the channel delays responding to the framework's initial request for restoration data
* until {@code setRestorationData} has been called.
*
* <p>If the engine never calls {@code setRestorationData} this flag must be set to false. If set
* to true, the engine must call {@code setRestorationData} either with the actual restoration
* data as argument or null if it turns out that there is no restoration data.
*
* <p>If the response to the framework's request for restoration data is not delayed until the
* data has been set via {@code setRestorationData}, the framework may intermittently initialize
* itself to default values until the restoration data has been made available. Setting this flag
* to true avoids that extra work.
*/
public final boolean waitForRestorationData;
// Holds the the most current restoration data which may have been provided by the engine
// via "setRestorationData" or by the framework via the method channel. This is the data the
// framework should be restored to in case the app is terminated.
private byte[] restorationData;
private MethodChannel channel;
private MethodChannel.Result pendingFrameworkRestorationChannelRequest;
private boolean engineHasProvidedData = false;
private boolean frameworkHasRequestedData = false;
/** Obtain the most current restoration data that the framework has provided. */
public byte[] getRestorationData() {
return restorationData;
}
/** Set the restoration data from which the framework will restore its state. */
public void setRestorationData(byte[] data) {
engineHasProvidedData = true;
if (pendingFrameworkRestorationChannelRequest != null) {
// If their is a pending request from the framework, answer it.
pendingFrameworkRestorationChannelRequest.success(packageData(data));
pendingFrameworkRestorationChannelRequest = null;
restorationData = data;
} else if (frameworkHasRequestedData) {
// If the framework has previously received the engine's restoration data, push the new data
// directly to it. This case can happen when "waitForRestorationData" is false and the
// framework retrieved the restoration state before it was set via this method.
// Experimentally, this can also be used to restore a previously used engine to another state,
// e.g. when the engine is attached to a new activity.
channel.invokeMethod(
"push",
packageData(data),
new MethodChannel.Result() {
@Override
public void success(Object result) {
restorationData = data;
}
@Override
public void error(String errorCode, String errorMessage, Object errorDetails) {
Log.e(
TAG,
"Error "
+ errorCode
+ " while sending restoration data to framework: "
+ errorMessage);
}
@Override
public void notImplemented() {
// Nothing to do.
}
});
} else {
// Otherwise, just cache the data until the framework asks for it.
restorationData = data;
}
}
/**
* Clears the current restoration data.
*
* <p>This should be called just prior to a hot restart. Otherwise, after the hot restart the
* state prior to the hot restart will get restored.
*/
public void clearData() {
restorationData = null;
}
private final MethodChannel.MethodCallHandler handler =
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
final String method = call.method;
final Object args = call.arguments;
switch (method) {
case "put":
restorationData = (byte[]) args;
result.success(null);
break;
case "get":
frameworkHasRequestedData = true;
if (engineHasProvidedData || !waitForRestorationData) {
result.success(packageData(restorationData));
// Do not delete the restoration data on the engine side after sending it to the
// framework. We may need to hand this data back to the operating system if the
// framework never modifies the data (and thus doesn't send us any
// data back).
} else {
pendingFrameworkRestorationChannelRequest = result;
}
break;
default:
result.notImplemented();
break;
}
}
};
private Map<String, Object> packageData(byte[] data) {
final Map<String, Object> packaged = new HashMap<String, Object>();
packaged.put("enabled", true); // Android supports state restoration.
packaged.put("data", data);
return packaged;
}
}