ConnectivityController.java revision 7060b04f6d92351b67222e636ab378a0273bf3e7
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());
62            }
63            return mSingleton;
64        }
65    }
66
67    private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
68        super(stateChangedListener, context);
69        // Register connectivity changed BR.
70        IntentFilter intentFilter = new IntentFilter();
71        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
72        mContext.registerReceiverAsUser(
73                mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
74        ConnectivityService cs =
75                (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
76        if (cs != null) {
77            if (cs.getActiveNetworkInfo() != null) {
78                mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
79            }
80            mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
81        }
82    }
83
84    @Override
85    public void maybeStartTrackingJob(JobStatus jobStatus) {
86        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
87            synchronized (mTrackedJobs) {
88                jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected);
89                jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered);
90                mTrackedJobs.add(jobStatus);
91            }
92        }
93    }
94
95    @Override
96    public void maybeStopTrackingJob(JobStatus jobStatus) {
97        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
98            synchronized (mTrackedJobs) {
99                mTrackedJobs.remove(jobStatus);
100            }
101        }
102    }
103
104    /**
105     * @param userId Id of the user for whom we are updating the connectivity state.
106     */
107    private void updateTrackedJobs(int userId) {
108        synchronized (mTrackedJobs) {
109            boolean changed = false;
110            for (JobStatus js : mTrackedJobs) {
111                if (js.getUserId() != userId) {
112                    continue;
113                }
114                boolean prevIsConnected =
115                        js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected);
116                boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered);
117                if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) {
118                    changed = true;
119                }
120            }
121            if (changed) {
122                mStateChangedListener.onControllerStateChanged();
123            }
124        }
125    }
126
127    /**
128     * We know the network has just come up. We want to run any jobs that are ready.
129     */
130    public synchronized void onNetworkActive() {
131        synchronized (mTrackedJobs) {
132            for (JobStatus js : mTrackedJobs) {
133                if (js.isReady()) {
134                    if (DEBUG) {
135                        Slog.d(TAG, "Running " + js + " due to network activity.");
136                    }
137                    mStateChangedListener.onRunJobNow(js);
138                }
139            }
140        }
141    }
142
143    class ConnectivityChangedReceiver extends BroadcastReceiver {
144        /**
145         * We'll receive connectivity changes for each user here, which we process independently.
146         * We are only interested in the active network here. We're only interested in the active
147         * network, b/c the end result of this will be for apps to try to hit the network.
148         * @param context The Context in which the receiver is running.
149         * @param intent The Intent being received.
150         */
151        // TODO: Test whether this will be called twice for each user.
152        @Override
153        public void onReceive(Context context, Intent intent) {
154            if (DEBUG) {
155                Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
156                        + context.getUserId());
157            }
158            final String action = intent.getAction();
159            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
160                final int networkType =
161                        intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
162                                ConnectivityManager.TYPE_NONE);
163                // Connectivity manager for THIS context - important!
164                final ConnectivityManager connManager = (ConnectivityManager)
165                        context.getSystemService(Context.CONNECTIVITY_SERVICE);
166                final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
167                final int userid = context.getUserId();
168                // This broadcast gets sent a lot, only update if the active network has changed.
169                if (activeNetwork == null) {
170                    mNetworkUnmetered = false;
171                    mNetworkConnected = false;
172                    updateTrackedJobs(userid);
173                } else if (activeNetwork.getType() == networkType) {
174                    mNetworkUnmetered = false;
175                    mNetworkConnected = !intent.getBooleanExtra(
176                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
177                    if (mNetworkConnected) {  // No point making the call if we know there's no conn.
178                        mNetworkUnmetered = !connManager.isActiveNetworkMetered();
179                    }
180                    updateTrackedJobs(userid);
181                }
182            } else {
183                if (DEBUG) {
184                    Slog.d(TAG, "Unrecognised action in intent: " + action);
185                }
186            }
187        }
188    };
189
190    @Override
191    public void dumpControllerState(PrintWriter pw) {
192        pw.println("Conn.");
193        pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
194        for (JobStatus js: mTrackedJobs) {
195            pw.println(String.valueOf(js.hashCode()).substring(0, 3) + ".."
196                    + ": C=" + js.hasConnectivityConstraint()
197                    + ", UM=" + js.hasUnmeteredConstraint());
198        }
199    }
200}