Home | History | Annotate | Download | only in controllers
      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 
     17 package com.android.server.job.controllers;
     18 
     19 
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.net.ConnectivityManager;
     25 import android.net.NetworkInfo;
     26 import android.os.ServiceManager;
     27 import android.os.UserHandle;
     28 import android.util.Slog;
     29 
     30 import com.android.server.ConnectivityService;
     31 import com.android.server.job.JobSchedulerService;
     32 import com.android.server.job.StateChangedListener;
     33 
     34 import java.io.PrintWriter;
     35 import java.util.LinkedList;
     36 import 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  */
     43 public 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 }
    201