ConnectivityController.java revision 88c4135d88eb59320fe93801088bcd6c47e50efb
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
19
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26import android.os.ServiceManager;
27import android.os.UserHandle;
28import android.util.Slog;
29
30import com.android.server.ConnectivityService;
31import com.android.server.job.JobSchedulerService;
32import com.android.server.job.StateChangedListener;
33
34import java.io.PrintWriter;
35import java.util.LinkedList;
36import java.util.List;
37
38/**
39 * Handles changes in connectivity.
40 * We are only interested in metered vs. unmetered networks, and we're interested in them on a
41 * per-user basis.
42 */
43public class ConnectivityController extends StateController implements
44        ConnectivityManager.OnNetworkActiveListener {
45    private static final String TAG = "JobScheduler.Conn";
46
47    private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
48    private final BroadcastReceiver mConnectivityChangedReceiver =
49            new ConnectivityChangedReceiver();
50    /** Singleton. */
51    private static ConnectivityController mSingleton;
52    private static Object sCreationLock = new Object();
53    /** Track whether the latest active network is metered. */
54    private boolean mNetworkUnmetered;
55    /** Track whether the latest active network is connected. */
56    private boolean mNetworkConnected;
57
58    public static ConnectivityController get(JobSchedulerService jms) {
59        synchronized (sCreationLock) {
60            if (mSingleton == null) {
61                mSingleton = new ConnectivityController(jms, jms.getContext(), jms.getLock());
62            }
63            return mSingleton;
64        }
65    }
66
67    private ConnectivityController(StateChangedListener stateChangedListener, Context context,
68            Object lock) {
69        super(stateChangedListener, context, lock);
70        // Register connectivity changed BR.
71        IntentFilter intentFilter = new IntentFilter();
72        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
73        mContext.registerReceiverAsUser(
74                mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
75        ConnectivityService cs =
76                (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
77        if (cs != null) {
78            if (cs.getActiveNetworkInfo() != null) {
79                mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
80                mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
81            } else {
82                mNetworkConnected = mNetworkUnmetered = false;
83            }
84        }
85    }
86
87    @Override
88    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
89        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
90            jobStatus.setConnectivityConstraintSatisfied(mNetworkConnected);
91            jobStatus.setUnmeteredConstraintSatisfied(mNetworkUnmetered);
92            mTrackedJobs.add(jobStatus);
93        }
94    }
95
96    @Override
97    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
98        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
99            mTrackedJobs.remove(jobStatus);
100        }
101    }
102
103    /**
104     * @param userId Id of the user for whom we are updating the connectivity state.
105     */
106    private void updateTrackedJobs(int userId) {
107        synchronized (mLock) {
108            boolean changed = false;
109            for (JobStatus js : mTrackedJobs) {
110                if (js.getUserId() != userId) {
111                    continue;
112                }
113                changed |= js.setConnectivityConstraintSatisfied(mNetworkConnected);
114                changed |= js.setUnmeteredConstraintSatisfied(mNetworkUnmetered);
115            }
116            if (changed) {
117                mStateChangedListener.onControllerStateChanged();
118            }
119        }
120    }
121
122    /**
123     * We know the network has just come up. We want to run any jobs that are ready.
124     */
125    public synchronized void onNetworkActive() {
126        synchronized (mLock) {
127            for (JobStatus js : mTrackedJobs) {
128                if (js.isReady()) {
129                    if (DEBUG) {
130                        Slog.d(TAG, "Running " + js + " due to network activity.");
131                    }
132                    mStateChangedListener.onRunJobNow(js);
133                }
134            }
135        }
136    }
137
138    class ConnectivityChangedReceiver extends BroadcastReceiver {
139        /**
140         * We'll receive connectivity changes for each user here, which we process independently.
141         * We are only interested in the active network here. We're only interested in the active
142         * network, b/c the end result of this will be for apps to try to hit the network.
143         * @param context The Context in which the receiver is running.
144         * @param intent The Intent being received.
145         */
146        // TODO: Test whether this will be called twice for each user.
147        @Override
148        public void onReceive(Context context, Intent intent) {
149            if (DEBUG) {
150                Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
151                        + context.getUserId());
152            }
153            final String action = intent.getAction();
154            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
155                final int networkType =
156                        intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
157                                ConnectivityManager.TYPE_NONE);
158                // Connectivity manager for THIS context - important!
159                final ConnectivityManager connManager = (ConnectivityManager)
160                        context.getSystemService(Context.CONNECTIVITY_SERVICE);
161                final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
162                final int userid = context.getUserId();
163                // This broadcast gets sent a lot, only update if the active network has changed.
164                if (activeNetwork == null) {
165                    mNetworkUnmetered = false;
166                    mNetworkConnected = false;
167                    updateTrackedJobs(userid);
168                } else if (activeNetwork.getType() == networkType) {
169                    mNetworkUnmetered = false;
170                    mNetworkConnected = !intent.getBooleanExtra(
171                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
172                    if (mNetworkConnected) {  // No point making the call if we know there's no conn.
173                        mNetworkUnmetered = !connManager.isActiveNetworkMetered();
174                    }
175                    updateTrackedJobs(userid);
176                }
177            } else {
178                if (DEBUG) {
179                    Slog.d(TAG, "Unrecognised action in intent: " + action);
180                }
181            }
182        }
183    };
184
185    @Override
186    public void dumpControllerStateLocked(PrintWriter pw) {
187        pw.println("Conn.");
188        pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
189        for (JobStatus js: mTrackedJobs) {
190            pw.println(String.valueOf(js.getJobId() + "," + js.getUid())
191                    + ": C=" + js.hasConnectivityConstraint()
192                    + ", UM=" + js.hasUnmeteredConstraint());
193        }
194    }
195}
196