diff options
Diffstat (limited to 'common/device/android/net/NetworkFactoryLegacyImpl.java')
-rw-r--r-- | common/device/android/net/NetworkFactoryLegacyImpl.java | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/common/device/android/net/NetworkFactoryLegacyImpl.java b/common/device/android/net/NetworkFactoryLegacyImpl.java new file mode 100644 index 00000000..6dd0adb0 --- /dev/null +++ b/common/device/android/net/NetworkFactoryLegacyImpl.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A NetworkFactory is an entity that creates NetworkAgent objects. + * The bearers register with ConnectivityService using {@link #register} and + * their factory will start receiving scored NetworkRequests. NetworkRequests + * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by + * overridden function. All of these can be dynamic - changing NetworkCapabilities + * or score forces re-evaluation of all current requests. + * + * If any requests pass the filter some overrideable functions will be called. + * If the bearer only cares about very simple start/stopNetwork callbacks, those + * functions can be overridden. If the bearer needs more interaction, it can + * override addNetworkRequest and removeNetworkRequest which will give it each + * request that passes their current filters. + * @hide + **/ +// TODO(b/187083878): factor out common code between this and NetworkFactoryImpl +class NetworkFactoryLegacyImpl extends Handler + implements NetworkFactoryShim { + private static final boolean DBG = NetworkFactory.DBG; + private static final boolean VDBG = NetworkFactory.VDBG; + + // TODO(b/187082970): Replace CMD_* with Handler.post(() -> { ... }) since all the CMDs do is to + // run the tasks asynchronously on the Handler thread. + + /** + * Pass a network request to the bearer. If the bearer believes it can + * satisfy the request it should connect to the network and create a + * NetworkAgent. Once the NetworkAgent is fully functional it will + * register itself with ConnectivityService using registerNetworkAgent. + * If the bearer cannot immediately satisfy the request (no network, + * user disabled the radio, lower-scored network) it should remember + * any NetworkRequests it may be able to satisfy in the future. It may + * disregard any that it will never be able to service, for example + * those requiring a different bearer. + * msg.obj = NetworkRequest + * msg.arg1 = score - the score of the network currently satisfying this + * request. If this bearer knows in advance it cannot + * exceed this score it should not try to connect, holding the request + * for the future. + * Note that subsequent events may give a different (lower + * or higher) score for this request, transmitted to each + * NetworkFactory through additional CMD_REQUEST_NETWORK msgs + * with the same NetworkRequest but an updated score. + * Also, network conditions may change for this bearer + * allowing for a better score in the future. + * msg.arg2 = the ID of the NetworkProvider currently responsible for the + * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none. + */ + public static final int CMD_REQUEST_NETWORK = 1; + + /** + * Cancel a network request + * msg.obj = NetworkRequest + */ + public static final int CMD_CANCEL_REQUEST = 2; + + /** + * Internally used to set our best-guess score. + * msg.arg1 = new score + */ + private static final int CMD_SET_SCORE = 3; + + /** + * Internally used to set our current filter for coarse bandwidth changes with + * technology changes. + * msg.obj = new filter + */ + private static final int CMD_SET_FILTER = 4; + + final Context mContext; + final NetworkFactory mParent; + + private final Map<NetworkRequest, NetworkRequestInfo> mNetworkRequests = + new LinkedHashMap<>(); + + private int mScore; + NetworkCapabilities mCapabilityFilter; + + NetworkProvider mProvider = null; + + NetworkFactoryLegacyImpl(NetworkFactory parent, Looper looper, Context context, + NetworkCapabilities filter) { + super(looper); + mParent = parent; + mContext = context; + mCapabilityFilter = filter; + } + + /* Registers this NetworkFactory with the system. May only be called once per factory. */ + @Override public void register(final String logTag) { + if (mProvider != null) { + throw new IllegalStateException("A NetworkFactory must only be registered once"); + } + if (DBG) mParent.log("Registering NetworkFactory"); + + mProvider = new NetworkProvider(mContext, NetworkFactoryLegacyImpl.this.getLooper(), + logTag) { + @Override + public void onNetworkRequested(@NonNull NetworkRequest request, int score, + int servingProviderId) { + handleAddRequest(request, score, servingProviderId); + } + + @Override + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + handleRemoveRequest(request); + } + }; + + ((ConnectivityManager) mContext.getSystemService( + Context.CONNECTIVITY_SERVICE)).registerNetworkProvider(mProvider); + } + + /** Unregisters this NetworkFactory. After this call, the object can no longer be used. */ + @Override public void terminate() { + if (mProvider == null) { + throw new IllegalStateException("This NetworkFactory was never registered"); + } + if (DBG) mParent.log("Unregistering NetworkFactory"); + + ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .unregisterNetworkProvider(mProvider); + + // Remove all pending messages, since this object cannot be reused. Any message currently + // being processed will continue to run. + removeCallbacksAndMessages(null); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_REQUEST_NETWORK: { + handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2); + break; + } + case CMD_CANCEL_REQUEST: { + handleRemoveRequest((NetworkRequest) msg.obj); + break; + } + case CMD_SET_SCORE: { + handleSetScore(msg.arg1); + break; + } + case CMD_SET_FILTER: { + handleSetFilter((NetworkCapabilities) msg.obj); + break; + } + } + } + + private static class NetworkRequestInfo { + public final NetworkRequest request; + public int score; + public boolean requested; // do we have a request outstanding, limited by score + public int providerId; + + NetworkRequestInfo(NetworkRequest request, int score, int providerId) { + this.request = request; + this.score = score; + this.requested = false; + this.providerId = providerId; + } + + @Override + public String toString() { + return "{" + request + ", score=" + score + ", requested=" + requested + "}"; + } + } + + /** + * Add a NetworkRequest that the bearer may want to attempt to satisfy. + * @see #CMD_REQUEST_NETWORK + * + * @param request the request to handle. + * @param score the score of the NetworkAgent currently satisfying this request. + * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent + * currently satisfying this request. + */ + @VisibleForTesting + protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n == null) { + if (DBG) { + mParent.log("got request " + request + " with score " + score + + " and providerId " + servingProviderId); + } + n = new NetworkRequestInfo(request, score, servingProviderId); + mNetworkRequests.put(n.request, n); + } else { + if (VDBG) { + mParent.log("new score " + score + " for existing request " + request + + " and providerId " + servingProviderId); + } + n.score = score; + n.providerId = servingProviderId; + } + if (VDBG) mParent.log(" my score=" + mScore + ", my filter=" + mCapabilityFilter); + + evalRequest(n); + } + + private void handleRemoveRequest(NetworkRequest request) { + NetworkRequestInfo n = mNetworkRequests.get(request); + if (n != null) { + mNetworkRequests.remove(request); + if (n.requested) mParent.releaseNetworkFor(n.request); + } + } + + private void handleSetScore(int score) { + mScore = score; + evalRequests(); + } + + private void handleSetFilter(NetworkCapabilities netCap) { + mCapabilityFilter = netCap; + evalRequests(); + } + + /** + * Overridable function to provide complex filtering. + * Called for every request every time a new NetworkRequest is seen + * and whenever the filterScore or filterNetworkCapabilities change. + * + * acceptRequest can be overridden to provide complex filter behavior + * for the incoming requests + * + * For output, this class will call {@link NetworkFactory#needNetworkFor} and + * {@link NetworkFactory#releaseNetworkFor} for every request that passes the filters. + * If you don't need to see every request, you can leave the base + * implementations of those two functions and instead override + * {@link NetworkFactory#startNetwork} and {@link NetworkFactory#stopNetwork}. + * + * If you want to see every score fluctuation on every request, set + * your score filter to a very high number and watch {@link NetworkFactory#needNetworkFor}. + * + * @return {@code true} to accept the request. + */ + public boolean acceptRequest(NetworkRequest request) { + return mParent.acceptRequest(request); + } + + private void evalRequest(NetworkRequestInfo n) { + if (VDBG) { + mParent.log("evalRequest"); + mParent.log(" n.requests = " + n.requested); + mParent.log(" n.score = " + n.score); + mParent.log(" mScore = " + mScore); + mParent.log(" request.providerId = " + n.providerId); + mParent.log(" mProvider.id = " + mProvider.getProviderId()); + } + if (shouldNeedNetworkFor(n)) { + if (VDBG) mParent.log(" needNetworkFor"); + mParent.needNetworkFor(n.request); + n.requested = true; + } else if (shouldReleaseNetworkFor(n)) { + if (VDBG) mParent.log(" releaseNetworkFor"); + mParent.releaseNetworkFor(n.request); + n.requested = false; + } else { + if (VDBG) mParent.log(" done"); + } + } + + private boolean shouldNeedNetworkFor(NetworkRequestInfo n) { + // If this request is already tracked, it doesn't qualify for need + return !n.requested + // If the score of this request is higher or equal to that of this factory and some + // other factory is responsible for it, then this factory should not track the request + // because it has no hope of satisfying it. + && (n.score < mScore || n.providerId == mProvider.getProviderId()) + // If this factory can't satisfy the capability needs of this request, then it + // should not be tracked. + && n.request.canBeSatisfiedBy(mCapabilityFilter) + // Finally if the concrete implementation of the factory rejects the request, then + // don't track it. + && acceptRequest(n.request); + } + + private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) { + // Don't release a request that's not tracked. + return n.requested + // The request should be released if it can't be satisfied by this factory. That + // means either of the following conditions are met : + // - Its score is too high to be satisfied by this factory and it's not already + // assigned to the factory + // - This factory can't satisfy the capability needs of the request + // - The concrete implementation of the factory rejects the request + && ((n.score > mScore && n.providerId != mProvider.getProviderId()) + || !n.request.canBeSatisfiedBy(mCapabilityFilter) + || !acceptRequest(n.request)); + } + + private void evalRequests() { + for (NetworkRequestInfo n : mNetworkRequests.values()) { + evalRequest(n); + } + } + + /** + * Post a command, on this NetworkFactory Handler, to re-evaluate all + * outstanding requests. Can be called from a factory implementation. + */ + @Override public void reevaluateAllRequests() { + post(this::evalRequests); + } + + /** + * Can be called by a factory to release a request as unfulfillable: the request will be + * removed, and the caller will get a + * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function + * returns. + * + * Note: this should only be called by factory which KNOWS that it is the ONLY factory which + * is able to fulfill this request! + */ + @Override public void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) { + post(() -> { + if (DBG) mParent.log("releaseRequestAsUnfulfillableByAnyFactory: " + r); + final NetworkProvider provider = mProvider; + if (provider == null) { + mParent.log("Ignoring attempt to release unregistered request as unfulfillable"); + return; + } + provider.declareNetworkRequestUnfulfillable(r); + }); + } + + @Override public void setScoreFilter(int score) { + sendMessage(obtainMessage(CMD_SET_SCORE, score, 0)); + } + + @Override public void setScoreFilter(@NonNull final NetworkScore score) { + setScoreFilter(score.getLegacyInt()); + } + + @Override public void setCapabilityFilter(NetworkCapabilities netCap) { + sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap))); + } + + @Override public int getRequestCount() { + return mNetworkRequests.size(); + } + + /* TODO: delete when all callers have migrated to NetworkProvider IDs. */ + @Override public int getSerialNumber() { + return mProvider.getProviderId(); + } + + @Override public NetworkProvider getProvider() { + return mProvider; + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println(toString()); + for (NetworkRequestInfo n : mNetworkRequests.values()) { + writer.println(" " + n); + } + } + + @Override public String toString() { + return "providerId=" + + mProvider.getProviderId() + ", ScoreFilter=" + + mScore + ", Filter=" + mCapabilityFilter + ", requests=" + + mNetworkRequests.size(); + } +} |