1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.job.controllers;
18
19import android.app.job.JobInfo;
20import android.content.Context;
21import android.net.ConnectivityManager;
22import android.net.ConnectivityManager.NetworkCallback;
23import android.net.INetworkPolicyListener;
24import android.net.Network;
25import android.net.NetworkCapabilities;
26import android.net.NetworkInfo;
27import android.net.NetworkPolicyManager;
28import android.os.Process;
29import android.os.UserHandle;
30import android.util.ArraySet;
31import android.util.Slog;
32
33import com.android.internal.annotations.GuardedBy;
34import com.android.server.job.JobSchedulerService;
35import com.android.server.job.StateChangedListener;
36
37import java.io.PrintWriter;
38
39/**
40 * Handles changes in connectivity.
41 * <p>
42 * Each app can have a different default networks or different connectivity
43 * status due to user-requested network policies, so we need to check
44 * constraints on a per-UID basis.
45 */
46public final class ConnectivityController extends StateController implements
47        ConnectivityManager.OnNetworkActiveListener {
48    private static final String TAG = "JobScheduler.Conn";
49    private static final boolean DEBUG = false;
50
51    private final ConnectivityManager mConnManager;
52    private final NetworkPolicyManager mNetPolicyManager;
53    private boolean mConnected;
54    private boolean mValidated;
55
56    @GuardedBy("mLock")
57    private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
58
59    /** Singleton. */
60    private static ConnectivityController mSingleton;
61    private static Object sCreationLock = new Object();
62
63    public static ConnectivityController get(JobSchedulerService jms) {
64        synchronized (sCreationLock) {
65            if (mSingleton == null) {
66                mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
67            }
68            return mSingleton;
69        }
70    }
71
72    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
73            Object lock) {
74        super(stateChangedListener, context, lock);
75
76        mConnManager = mContext.getSystemService(ConnectivityManager.class);
77        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
78
79        mConnected = mValidated = false;
80
81        mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
82        mNetPolicyManager.registerListener(mNetPolicyListener);
83    }
84
85    @Override
86    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
87        if (jobStatus.hasConnectivityConstraint()) {
88            updateConstraintsSatisfied(jobStatus, null);
89            mTrackedJobs.add(jobStatus);
90            jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
91        }
92    }
93
94    @Override
95    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
96            boolean forUpdate) {
97        if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
98            mTrackedJobs.remove(jobStatus);
99        }
100    }
101
102    private boolean updateConstraintsSatisfied(JobStatus jobStatus,
103            NetworkCapabilities capabilities) {
104        final int jobUid = jobStatus.getSourceUid();
105        final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
106        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
107        if (capabilities == null) {
108            final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
109            capabilities = mConnManager.getNetworkCapabilities(network);
110        }
111
112        final boolean validated = capabilities != null
113                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
114        final boolean connected = info != null && info.isConnected();
115        final boolean connectionUsable = connected && validated;
116        final boolean metered = connected && info.isMetered();
117        final boolean unmetered = connected && !info.isMetered();
118        final boolean notRoaming = connected && !info.isRoaming();
119
120        boolean changed = false;
121        changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
122        changed |= jobStatus.setMeteredConstraintSatisfied(metered);
123        changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
124        changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
125
126        // Track system-uid connected/validated as a general reportable proxy for the
127        // overall state of connectivity constraint satisfiability.
128        if (jobUid == Process.SYSTEM_UID) {
129            mConnected = connected;
130            mValidated = validated;
131        }
132
133        if (DEBUG) {
134            Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
135                    + " for " + jobStatus + ": usable=" + connectionUsable
136                    + " connected=" + connected
137                    + " validated=" + validated
138                    + " metered=" + metered
139                    + " unmetered=" + unmetered
140                    + " notRoaming=" + notRoaming);
141        }
142        return changed;
143    }
144
145    /**
146     * Update all jobs tracked by this controller.
147     *
148     * @param uid only update jobs belonging to this UID, or {@code -1} to
149     *            update all tracked jobs.
150     */
151    private void updateTrackedJobs(int uid, NetworkCapabilities capabilities) {
152        synchronized (mLock) {
153            boolean changed = false;
154            for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
155                final JobStatus js = mTrackedJobs.valueAt(i);
156                if (uid == -1 || uid == js.getSourceUid()) {
157                    changed |= updateConstraintsSatisfied(js, capabilities);
158                }
159            }
160            if (changed) {
161                mStateChangedListener.onControllerStateChanged();
162            }
163        }
164    }
165
166    /**
167     * We know the network has just come up. We want to run any jobs that are ready.
168     */
169    @Override
170    public void onNetworkActive() {
171        synchronized (mLock) {
172            for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
173                final JobStatus js = mTrackedJobs.valueAt(i);
174                if (js.isReady()) {
175                    if (DEBUG) {
176                        Slog.d(TAG, "Running " + js + " due to network activity.");
177                    }
178                    mStateChangedListener.onRunJobNow(js);
179                }
180            }
181        }
182    }
183
184    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
185        @Override
186        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
187            if (DEBUG) {
188                Slog.v(TAG, "onCapabilitiesChanged() : " + networkCapabilities);
189            }
190            updateTrackedJobs(-1, networkCapabilities);
191        }
192
193        @Override
194        public void onLost(Network network) {
195            if (DEBUG) {
196                Slog.v(TAG, "Network lost");
197            }
198            updateTrackedJobs(-1, null);
199        }
200    };
201
202    private final INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
203        @Override
204        public void onUidRulesChanged(int uid, int uidRules) {
205            if (DEBUG) {
206                Slog.v(TAG, "Uid rules changed for " + uid);
207            }
208            updateTrackedJobs(uid, null);
209        }
210
211        @Override
212        public void onMeteredIfacesChanged(String[] meteredIfaces) {
213            // We track this via our NetworkCallback
214        }
215
216        @Override
217        public void onRestrictBackgroundChanged(boolean restrictBackground) {
218            if (DEBUG) {
219                Slog.v(TAG, "Background restriction change to " + restrictBackground);
220            }
221            updateTrackedJobs(-1, null);
222        }
223
224        @Override
225        public void onUidPoliciesChanged(int uid, int uidPolicies) {
226            if (DEBUG) {
227                Slog.v(TAG, "Uid policy changed for " + uid);
228            }
229            updateTrackedJobs(uid, null);
230        }
231    };
232
233    @Override
234    public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
235        pw.print("Connectivity: connected=");
236        pw.print(mConnected);
237        pw.print(" validated=");
238        pw.println(mValidated);
239        pw.print("Tracking ");
240        pw.print(mTrackedJobs.size());
241        pw.println(":");
242        for (int i = 0; i < mTrackedJobs.size(); i++) {
243            final JobStatus js = mTrackedJobs.valueAt(i);
244            if (js.shouldDump(filterUid)) {
245                pw.print("  #");
246                js.printUniqueId(pw);
247                pw.print(" from ");
248                UserHandle.formatUid(pw, js.getSourceUid());
249                pw.print(": C="); pw.print(js.needsAnyConnectivity());
250                pw.print(": M="); pw.print(js.needsMeteredConnectivity());
251                pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity());
252                pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity());
253            }
254        }
255    }
256}
257