github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/bind/keybase.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package keybase
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"runtime/debug"
    14  	"runtime/trace"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/keybase/client/go/chat/globals"
    19  	"github.com/keybase/client/go/status"
    20  	"golang.org/x/sync/errgroup"
    21  
    22  	"strings"
    23  
    24  	"github.com/keybase/client/go/externals"
    25  	"github.com/keybase/client/go/kbfs/env"
    26  	"github.com/keybase/client/go/kbfs/fsrpc"
    27  	"github.com/keybase/client/go/kbfs/libgit"
    28  	"github.com/keybase/client/go/kbfs/libkbfs"
    29  	"github.com/keybase/client/go/kbfs/simplefs"
    30  	"github.com/keybase/client/go/libkb"
    31  	"github.com/keybase/client/go/logger"
    32  	"github.com/keybase/client/go/protocol/chat1"
    33  	"github.com/keybase/client/go/protocol/keybase1"
    34  	"github.com/keybase/client/go/service"
    35  	"github.com/keybase/client/go/uidmap"
    36  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    37  	context "golang.org/x/net/context"
    38  )
    39  
    40  var kbCtx *libkb.GlobalContext
    41  var kbChatCtx *globals.ChatContext
    42  var kbSvc *service.Service
    43  var conn net.Conn
    44  var startOnce sync.Once
    45  var logSendContext status.LogSendContext
    46  
    47  var initMutex sync.Mutex
    48  var initComplete bool
    49  
    50  type PushNotifier interface {
    51  	LocalNotification(ident string, msg string, badgeCount int, soundName string, convID string, typ string)
    52  	DisplayChatNotification(notification *ChatNotification)
    53  }
    54  
    55  type NativeVideoHelper interface {
    56  	Thumbnail(filename string) []byte
    57  	Duration(filename string) int
    58  }
    59  
    60  // NativeInstallReferrerListener is implemented in Java on Android.
    61  type NativeInstallReferrerListener interface {
    62  	// StartInstallReferrerListener is used to get referrer information from the
    63  	// google play store on Android (to implement deferred deep links). This is
    64  	// asynchronous (due to the underlying play store api being so): pass it a
    65  	// callback function which will be called with the referrer string once it
    66  	// is available (or an empty string in case of errors).
    67  	StartInstallReferrerListener(callback StringReceiver)
    68  }
    69  
    70  type StringReceiver interface {
    71  	CallbackWithString(s string)
    72  }
    73  
    74  // InstallReferrerListener is a wrapper around NativeInstallReferrerListener to
    75  // work around gomobile/gobind limitations while preventing import cycles.
    76  type InstallReferrerListener struct {
    77  	n NativeInstallReferrerListener
    78  }
    79  
    80  func (i InstallReferrerListener) StartInstallReferrerListener(callback service.StringReceiver) {
    81  	i.n.StartInstallReferrerListener(callback)
    82  }
    83  
    84  var _ service.InstallReferrerListener = InstallReferrerListener{}
    85  
    86  func newInstallReferrerListener(n NativeInstallReferrerListener) service.InstallReferrerListener {
    87  	return InstallReferrerListener{n: n}
    88  }
    89  
    90  type videoHelper struct {
    91  	nvh NativeVideoHelper
    92  }
    93  
    94  func newVideoHelper(nvh NativeVideoHelper) videoHelper {
    95  	return videoHelper{
    96  		nvh: nvh,
    97  	}
    98  }
    99  
   100  func (v videoHelper) ThumbnailAndDuration(ctx context.Context, filename string) ([]byte, int, error) {
   101  	return v.nvh.Thumbnail(filename), v.nvh.Duration(filename), nil
   102  }
   103  
   104  type ExternalDNSNSFetcher interface {
   105  	GetServers() []byte
   106  }
   107  
   108  type dnsNSFetcher struct {
   109  	externalFetcher ExternalDNSNSFetcher
   110  }
   111  
   112  func newDNSNSFetcher(d ExternalDNSNSFetcher) dnsNSFetcher {
   113  	return dnsNSFetcher{
   114  		externalFetcher: d,
   115  	}
   116  }
   117  
   118  func (d dnsNSFetcher) processExternalResult(raw []byte) []string {
   119  	return strings.Split(string(raw), ",")
   120  }
   121  
   122  func (d dnsNSFetcher) GetServers() []string {
   123  	if d.externalFetcher != nil {
   124  		return d.processExternalResult(d.externalFetcher.GetServers())
   125  	}
   126  	return getDNSServers()
   127  }
   128  
   129  var _ libkb.DNSNameServerFetcher = dnsNSFetcher{}
   130  
   131  func flattenError(err error) error {
   132  	if err != nil {
   133  		return errors.New(err.Error())
   134  	}
   135  	return err
   136  }
   137  
   138  func isInited() bool {
   139  	initMutex.Lock()
   140  	defer initMutex.Unlock()
   141  	return initComplete
   142  }
   143  
   144  func setInited() {
   145  	initMutex.Lock()
   146  	defer initMutex.Unlock()
   147  	initComplete = true
   148  }
   149  
   150  // InitOnce runs the Keybase services (only runs one time)
   151  func InitOnce(homeDir, mobileSharedHome, logFile, runModeStr string,
   152  	accessGroupOverride bool, dnsNSFetcher ExternalDNSNSFetcher, nvh NativeVideoHelper,
   153  	mobileOsVersion string, isIPad bool, installReferrerListener NativeInstallReferrerListener, isIOS bool) {
   154  	startOnce.Do(func() {
   155  		if err := Init(homeDir, mobileSharedHome, logFile, runModeStr, accessGroupOverride, dnsNSFetcher, nvh, mobileOsVersion, isIPad, installReferrerListener, isIOS); err != nil {
   156  			kbCtx.Log.Errorf("Init error: %s", err)
   157  		}
   158  	})
   159  }
   160  
   161  // Init runs the Keybase services
   162  func Init(homeDir, mobileSharedHome, logFile, runModeStr string,
   163  	accessGroupOverride bool, externalDNSNSFetcher ExternalDNSNSFetcher, nvh NativeVideoHelper,
   164  	mobileOsVersion string, isIPad bool, installReferrerListener NativeInstallReferrerListener, isIOS bool) (err error) {
   165  	defer func() {
   166  		err = flattenError(err)
   167  		if err == nil {
   168  			setInited()
   169  		}
   170  	}()
   171  
   172  	fmt.Printf("Go: Initializing: home: %s mobileSharedHome: %s\n", homeDir, mobileSharedHome)
   173  	if isIOS {
   174  		// buffer of bytes
   175  		buffer = make([]byte, 300*1024)
   176  	} else {
   177  		const targetBufferSize = 300 * 1024
   178  		// bufferSize must be divisible by 3 to ensure that we don't split
   179  		// our b64 encode across a payload boundary if we go over our buffer
   180  		// size.
   181  		const bufferSize = targetBufferSize - (targetBufferSize % 3)
   182  		// buffer for the conn.Read
   183  		buffer = make([]byte, bufferSize)
   184  	}
   185  
   186  	var perfLogFile, ekLogFile, guiLogFile string
   187  	if logFile != "" {
   188  		fmt.Printf("Go: Using log: %s\n", logFile)
   189  		ekLogFile = logFile + ".ek"
   190  		fmt.Printf("Go: Using eklog: %s\n", ekLogFile)
   191  		perfLogFile = logFile + ".perf"
   192  		fmt.Printf("Go: Using perfLog: %s\n", perfLogFile)
   193  		guiLogFile = logFile + ".gui"
   194  		fmt.Printf("Go: Using guilog: %s\n", guiLogFile)
   195  	}
   196  	libkb.IsIPad = isIPad
   197  
   198  	// Reduce OS threads on mobile so we don't have too much contention with JS thread
   199  	oldProcs := runtime.GOMAXPROCS(0)
   200  	newProcs := oldProcs - 2
   201  	if newProcs <= 0 {
   202  		newProcs = 1
   203  	}
   204  	runtime.GOMAXPROCS(newProcs)
   205  	fmt.Printf("Go: setting GOMAXPROCS to: %d previous: %d\n", newProcs, oldProcs)
   206  
   207  	startTrace(logFile)
   208  
   209  	dnsNSFetcher := newDNSNSFetcher(externalDNSNSFetcher)
   210  	dnsServers := dnsNSFetcher.GetServers()
   211  	for _, srv := range dnsServers {
   212  		fmt.Printf("Go: DNS Server: %s\n", srv)
   213  	}
   214  
   215  	kbCtx = libkb.NewGlobalContext()
   216  	kbCtx.Init()
   217  	kbCtx.SetProofServices(externals.NewProofServices(kbCtx))
   218  
   219  	var suffix string
   220  	if isIPad {
   221  		suffix = " (iPad)"
   222  	}
   223  	fmt.Printf("Go (GOOS:%s): Mobile OS version is: %q%v\n", runtime.GOOS, mobileOsVersion, suffix)
   224  	kbCtx.MobileOsVersion = mobileOsVersion
   225  
   226  	// 10k uid -> FullName cache entries allowed
   227  	kbCtx.SetUIDMapper(uidmap.NewUIDMap(10000))
   228  	kbCtx.SetServiceSummaryMapper(uidmap.NewServiceSummaryMap(1000))
   229  	usage := libkb.Usage{
   230  		Config:    true,
   231  		API:       true,
   232  		KbKeyring: true,
   233  	}
   234  	runMode, err := libkb.StringToRunMode(runModeStr)
   235  	if err != nil {
   236  		return err
   237  	}
   238  	config := libkb.AppConfig{
   239  		HomeDir:                        homeDir,
   240  		MobileSharedHomeDir:            mobileSharedHome,
   241  		LogFile:                        logFile,
   242  		EKLogFile:                      ekLogFile,
   243  		PerfLogFile:                    perfLogFile,
   244  		GUILogFile:                     guiLogFile,
   245  		RunMode:                        runMode,
   246  		Debug:                          true,
   247  		LocalRPCDebug:                  "",
   248  		VDebugSetting:                  "mobile", // use empty string for same logging as desktop default
   249  		SecurityAccessGroupOverride:    accessGroupOverride,
   250  		ChatInboxSourceLocalizeThreads: 2,
   251  		LinkCacheSize:                  1000,
   252  	}
   253  	if err = kbCtx.Configure(config, usage); err != nil {
   254  		fmt.Printf("failed to configure: %s\n", err)
   255  		return err
   256  	}
   257  
   258  	kbSvc = service.NewService(kbCtx, false)
   259  	if err = kbSvc.StartLoopbackServer(libkb.LoginAttemptOffline); err != nil {
   260  		fmt.Printf("failed to start loopback: %s\n", err)
   261  		return err
   262  	}
   263  	kbCtx.SetService()
   264  	uir := service.NewUIRouter(kbCtx)
   265  	kbCtx.SetUIRouter(uir)
   266  	kbCtx.SetDNSNameServerFetcher(dnsNSFetcher)
   267  	if err = kbSvc.SetupCriticalSubServices(); err != nil {
   268  		fmt.Printf("failed subservices setup: %s\n", err)
   269  		return err
   270  	}
   271  	kbSvc.SetupChatModules(nil)
   272  	if installReferrerListener != nil {
   273  		kbSvc.SetInstallReferrerListener(newInstallReferrerListener(installReferrerListener))
   274  	}
   275  	kbSvc.RunBackgroundOperations(uir)
   276  	kbChatCtx = kbSvc.ChatContextified.ChatG()
   277  	kbChatCtx.NativeVideoHelper = newVideoHelper(nvh)
   278  
   279  	logs := status.Logs{
   280  		Service: config.GetLogFile(),
   281  		EK:      config.GetEKLogFile(),
   282  		Perf:    config.GetPerfLogFile(),
   283  	}
   284  
   285  	fmt.Printf("Go: Using config: %+v\n", kbCtx.Env.GetLogFileConfig(config.GetLogFile()))
   286  
   287  	logSendContext = status.LogSendContext{
   288  		Contextified: libkb.NewContextified(kbCtx),
   289  		Logs:         logs,
   290  	}
   291  
   292  	// open the connection
   293  	if err = Reset(); err != nil {
   294  		fmt.Printf("failed conn setup %s\n", err)
   295  		return err
   296  	}
   297  
   298  	go func() {
   299  		kbfsCtx := env.NewContextFromGlobalContext(kbCtx)
   300  		kbfsParams := libkbfs.DefaultInitParams(kbfsCtx)
   301  		// Setting this flag will enable KBFS debug logging to always
   302  		// be true in a mobile setting. Change these back to the
   303  		// commented-out values if we need to make a mobile release
   304  		// before KBFS-on-mobile is ready.
   305  		kbfsParams.Debug = true                         // false
   306  		kbfsParams.Mode = libkbfs.InitConstrainedString // libkbfs.InitMinimalString
   307  		if _, err = libkbfs.Init(
   308  			context.Background(), kbfsCtx, kbfsParams, serviceCn{}, nil,
   309  			kbCtx.Log); err != nil {
   310  			fmt.Printf("unable to init KBFS: %s", err)
   311  		}
   312  	}()
   313  
   314  	return nil
   315  }
   316  
   317  func LogToService(str string) {
   318  	kbCtx.Log.Info(str)
   319  }
   320  
   321  type serviceCn struct{}
   322  
   323  func (s serviceCn) NewKeybaseService(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.KeybaseService, error) {
   324  	// TODO: plumb the func somewhere it can be called on shutdown?
   325  	gitrpc, _ := libgit.NewRPCHandlerWithCtx(
   326  		ctx, config, nil)
   327  	sfsIface, _ := simplefs.NewSimpleFS(ctx, config)
   328  	additionalProtocols := []rpc.Protocol{
   329  		keybase1.SimpleFSProtocol(sfsIface),
   330  		keybase1.KBFSGitProtocol(gitrpc),
   331  		keybase1.FsProtocol(fsrpc.NewFS(config, log)),
   332  	}
   333  	keybaseService := libkbfs.NewKeybaseDaemonRPC(
   334  		config, ctx, log, true, additionalProtocols)
   335  	return keybaseService, nil
   336  }
   337  
   338  func (s serviceCn) NewCrypto(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.Crypto, error) {
   339  	return libkbfs.NewCryptoClientRPC(config, ctx), nil
   340  }
   341  
   342  func (s serviceCn) NewChat(config libkbfs.Config, params libkbfs.InitParams, ctx libkbfs.Context, log logger.Logger) (libkbfs.Chat, error) {
   343  	return libkbfs.NewChatRPC(config, ctx), nil
   344  }
   345  
   346  // LogSend sends a log to Keybase
   347  func LogSend(statusJSON string, feedback string, sendLogs, sendMaxBytes bool, traceDir, cpuProfileDir string) (res string, err error) {
   348  	defer func() { err = flattenError(err) }()
   349  	env := kbCtx.Env
   350  	logSendContext.UID = env.GetUID()
   351  	logSendContext.InstallID = env.GetInstallID()
   352  	logSendContext.StatusJSON = statusJSON
   353  	logSendContext.Feedback = feedback
   354  	logSendContext.Logs.GUI = env.GetGUILogFile()
   355  	logSendContext.Logs.Trace = traceDir
   356  	logSendContext.Logs.CPUProfile = cpuProfileDir
   357  	var numBytes int
   358  	switch kbCtx.MobileNetState.State() {
   359  	case keybase1.MobileNetworkState_WIFI:
   360  		numBytes = status.LogSendDefaultBytesMobileWifi
   361  	default:
   362  		numBytes = status.LogSendDefaultBytesMobileNoWifi
   363  	}
   364  	if sendMaxBytes {
   365  		numBytes = status.LogSendMaxBytes
   366  	}
   367  
   368  	logSendID, err := logSendContext.LogSend(sendLogs, numBytes, true /* mergeExtendedStatus */, true /* addNetworkStats */)
   369  	logSendContext.Clear()
   370  	return string(logSendID), err
   371  }
   372  
   373  // WriteArr sends raw bytes encoded msgpack rpc payload, ios only
   374  func WriteArr(b []byte) (err error) {
   375  	bytes := make([]byte, len(b))
   376  	copy(bytes, b)
   377  	defer func() { err = flattenError(err) }()
   378  	if conn == nil {
   379  		return errors.New("connection not initialized")
   380  	}
   381  	n, err := conn.Write(bytes)
   382  	if err != nil {
   383  		return fmt.Errorf("Write error: %s", err)
   384  	}
   385  	if n != len(bytes) {
   386  		return errors.New("Did not write all the data")
   387  	}
   388  	return nil
   389  }
   390  
   391  const bufferSize = 1024 * 1024
   392  
   393  // buffer for the conn.Read
   394  var buffer = make([]byte, bufferSize)
   395  
   396  // ReadArr is a blocking read for msgpack rpc data.
   397  // It is called serially by the mobile run loops.
   398  func ReadArr() (data []byte, err error) {
   399  	defer func() { err = flattenError(err) }()
   400  	if conn == nil {
   401  		return nil, errors.New("connection not initialized")
   402  	}
   403  	n, err := conn.Read(buffer)
   404  	if n > 0 && err == nil {
   405  		return buffer[0:n], nil
   406  	}
   407  
   408  	if err != nil {
   409  		// Attempt to fix the connection
   410  		if ierr := Reset(); ierr != nil {
   411  			fmt.Printf("failed to Reset: %v\n", ierr)
   412  		}
   413  		return nil, fmt.Errorf("Read error: %s", err)
   414  	}
   415  
   416  	return nil, nil
   417  }
   418  
   419  // Reset resets the socket connection
   420  func Reset() error {
   421  	if conn != nil {
   422  		conn.Close()
   423  	}
   424  	if kbCtx == nil || kbCtx.LoopbackListener == nil {
   425  		return nil
   426  	}
   427  
   428  	var err error
   429  	conn, err = kbCtx.LoopbackListener.Dial()
   430  	if err != nil {
   431  		return fmt.Errorf("Socket error: %s", err)
   432  	}
   433  	return nil
   434  }
   435  
   436  // ForceGC Forces a gc
   437  func ForceGC() {
   438  	fmt.Printf("Flushing global caches\n")
   439  	kbCtx.FlushCaches()
   440  	fmt.Printf("Done flushing global caches\n")
   441  
   442  	fmt.Printf("Starting force gc\n")
   443  	debug.FreeOSMemory()
   444  	fmt.Printf("Done force gc\n")
   445  }
   446  
   447  // Version returns semantic version string
   448  func Version() string {
   449  	return libkb.VersionString()
   450  }
   451  
   452  func IsAppStateForeground() bool {
   453  	if !isInited() {
   454  		return false
   455  	}
   456  	return kbCtx.MobileAppState.State() == keybase1.MobileAppState_FOREGROUND
   457  }
   458  
   459  func SetAppStateForeground() {
   460  	if !isInited() {
   461  		return
   462  	}
   463  	defer kbCtx.Trace("SetAppStateForeground", nil)()
   464  	kbCtx.MobileAppState.Update(keybase1.MobileAppState_FOREGROUND)
   465  }
   466  func SetAppStateBackground() {
   467  	if !isInited() {
   468  		return
   469  	}
   470  	defer kbCtx.Trace("SetAppStateBackground", nil)()
   471  	kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND)
   472  }
   473  func SetAppStateInactive() {
   474  	if !isInited() {
   475  		return
   476  	}
   477  	defer kbCtx.Trace("SetAppStateInactive", nil)()
   478  	kbCtx.MobileAppState.Update(keybase1.MobileAppState_INACTIVE)
   479  }
   480  func SetAppStateBackgroundActive() {
   481  	if !isInited() {
   482  		return
   483  	}
   484  	defer kbCtx.Trace("SetAppStateBackgroundActive", nil)()
   485  	kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUNDACTIVE)
   486  }
   487  
   488  func waitForInit(maxDur time.Duration) error {
   489  	if isInited() {
   490  		return nil
   491  	}
   492  	maxCh := time.After(maxDur)
   493  	for {
   494  		select {
   495  		case <-time.After(200 * time.Millisecond):
   496  			if isInited() {
   497  				return nil
   498  			}
   499  		case <-maxCh:
   500  			return errors.New("waitForInit timeout")
   501  		}
   502  	}
   503  }
   504  
   505  func BackgroundSync() {
   506  	// On Android there is a race where this function can be called before Init when starting up in the
   507  	// background. Let's wait a little bit here for Init to get run, and bail out if it never does.
   508  	if err := waitForInit(5 * time.Second); err != nil {
   509  		return
   510  	}
   511  	defer kbCtx.Trace("BackgroundSync", nil)()
   512  
   513  	// Skip the sync if we aren't in the background
   514  	if state := kbCtx.MobileAppState.State(); state != keybase1.MobileAppState_BACKGROUND {
   515  		kbCtx.Log.Debug("BackgroundSync: skipping, app not in background state: %v", state)
   516  		return
   517  	}
   518  
   519  	nextState := keybase1.MobileAppState_BACKGROUNDACTIVE
   520  	kbCtx.MobileAppState.Update(nextState)
   521  	doneCh := make(chan struct{})
   522  	go func() {
   523  		defer func() { close(doneCh) }()
   524  		select {
   525  		case state := <-kbCtx.MobileAppState.NextUpdate(&nextState):
   526  			// if literally anything happens, let's get out of here
   527  			kbCtx.Log.Debug("BackgroundSync: bailing out early, appstate change: %v", state)
   528  			return
   529  		case <-time.After(10 * time.Second):
   530  			kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND)
   531  			return
   532  		}
   533  	}()
   534  	<-doneCh
   535  }
   536  
   537  // pushPendingMessageFailure sends at most one notification that a message
   538  // failed to send. We only notify the user about visible messages that have
   539  // failed.
   540  func pushPendingMessageFailure(obrs []chat1.OutboxRecord, pusher PushNotifier) {
   541  	for _, obr := range obrs {
   542  		if topicType := obr.Msg.ClientHeader.Conv.TopicType; obr.Msg.IsBadgableType() && topicType == chat1.TopicType_CHAT {
   543  			kbCtx.Log.Debug("pushPendingMessageFailure: pushing convID: %s", obr.ConvID)
   544  			pusher.LocalNotification("failedpending",
   545  				"Heads up! Your message hasn't sent yet, tap here to retry.",
   546  				-1, "default", obr.ConvID.String(), "chat.failedpending")
   547  			return
   548  		}
   549  	}
   550  	kbCtx.Log.Debug("pushPendingMessageFailure: skipped notification for: %d items", len(obrs))
   551  }
   552  
   553  // AppWillExit is called reliably on iOS when the app is about to terminate
   554  // not as reliably on android
   555  func AppWillExit(pusher PushNotifier) {
   556  	if !isInited() {
   557  		return
   558  	}
   559  	defer kbCtx.Trace("AppWillExit", nil)()
   560  	ctx := context.Background()
   561  	obrs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx)
   562  	if err == nil {
   563  		// We are about to get killed with messages still to send, let the user
   564  		// know they will get stuck
   565  		pushPendingMessageFailure(obrs, pusher)
   566  	}
   567  	kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUND)
   568  }
   569  
   570  // AppDidEnterBackground notifies the service that the app is in the background
   571  // [iOS] returning true will request about ~3mins from iOS to continue execution
   572  func AppDidEnterBackground() bool {
   573  	if !isInited() {
   574  		return false
   575  	}
   576  	defer kbCtx.Trace("AppDidEnterBackground", nil)()
   577  	ctx := context.Background()
   578  	convs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx)
   579  	if err != nil {
   580  		kbCtx.Log.Debug("AppDidEnterBackground: failed to get active deliveries: %s", err)
   581  		convs = nil
   582  	}
   583  	stayRunning := false
   584  	switch {
   585  	case len(convs) > 0:
   586  		kbCtx.Log.Debug("AppDidEnterBackground: active deliveries in progress")
   587  		stayRunning = true
   588  	case kbChatCtx.LiveLocationTracker.ActivelyTracking(ctx):
   589  		kbCtx.Log.Debug("AppDidEnterBackground: active live location in progress")
   590  		stayRunning = true
   591  	case kbChatCtx.CoinFlipManager.HasActiveGames(ctx):
   592  		kbCtx.Log.Debug("AppDidEnterBackground: active coin flip games in progress")
   593  		stayRunning = true
   594  	}
   595  	if stayRunning {
   596  		kbCtx.Log.Debug("AppDidEnterBackground: setting background active")
   597  		kbCtx.MobileAppState.Update(keybase1.MobileAppState_BACKGROUNDACTIVE)
   598  		return true
   599  	}
   600  	SetAppStateBackground()
   601  	return false
   602  }
   603  
   604  func AppBeginBackgroundTaskNonblock(pusher PushNotifier) {
   605  	if !isInited() {
   606  		return
   607  	}
   608  	defer kbCtx.Trace("AppBeginBackgroundTaskNonblock", nil)()
   609  	go AppBeginBackgroundTask(pusher)
   610  }
   611  
   612  // AppBeginBackgroundTask notifies us that an app background task has been started on our behalf. This
   613  // function will return once we no longer need any time in the background.
   614  func AppBeginBackgroundTask(pusher PushNotifier) {
   615  	if !isInited() {
   616  		return
   617  	}
   618  	defer kbCtx.Trace("AppBeginBackgroundTask", nil)()
   619  	ctx := context.Background()
   620  	// Poll active deliveries in case we can shutdown early
   621  	beginTime := libkb.ForceWallClock(time.Now())
   622  	ticker := time.NewTicker(5 * time.Second)
   623  	appState := kbCtx.MobileAppState.State()
   624  	if appState != keybase1.MobileAppState_BACKGROUNDACTIVE {
   625  		kbCtx.Log.Debug("AppBeginBackgroundTask: not in background mode, early out")
   626  		return
   627  	}
   628  	var g *errgroup.Group
   629  	g, ctx = errgroup.WithContext(ctx)
   630  	g.Go(func() error {
   631  		select {
   632  		case appState = <-kbCtx.MobileAppState.NextUpdate(&appState):
   633  			kbCtx.Log.Debug(
   634  				"AppBeginBackgroundTask: app state change, aborting with no task shutdown: %v", appState)
   635  			return errors.New("app state change")
   636  		case <-ctx.Done():
   637  			return ctx.Err()
   638  		}
   639  	})
   640  	g.Go(func() error {
   641  		ch, cancel := kbChatCtx.MessageDeliverer.NextFailure()
   642  		defer cancel()
   643  		select {
   644  		case obrs := <-ch:
   645  			kbCtx.Log.Debug(
   646  				"AppBeginBackgroundTask: failure received, alerting the user: %d marked", len(obrs))
   647  			pushPendingMessageFailure(obrs, pusher)
   648  			return errors.New("failure received")
   649  		case <-ctx.Done():
   650  			return ctx.Err()
   651  		}
   652  	})
   653  	g.Go(func() error {
   654  		successCount := 0
   655  		for {
   656  			select {
   657  			case <-ticker.C:
   658  				obrs, err := kbChatCtx.MessageDeliverer.ActiveDeliveries(ctx)
   659  				if err != nil {
   660  					kbCtx.Log.Debug("AppBeginBackgroundTask: failed to query active deliveries: %s", err)
   661  					continue
   662  				}
   663  				if len(obrs) == 0 {
   664  					kbCtx.Log.Debug("AppBeginBackgroundTask: delivered everything: successCount: %d",
   665  						successCount)
   666  					// We can race the failure case here, so lets go a couple passes of no pending
   667  					// convs before we abort due to ths condition.
   668  					if successCount > 1 {
   669  						return errors.New("delivered everything")
   670  					}
   671  					successCount++
   672  				}
   673  				curTime := libkb.ForceWallClock(time.Now())
   674  				if curTime.Sub(beginTime) >= 10*time.Minute {
   675  					kbCtx.Log.Debug("AppBeginBackgroundTask: failed to deliver and time is up, aborting")
   676  					pushPendingMessageFailure(obrs, pusher)
   677  					return errors.New("time expired")
   678  				}
   679  			case <-ctx.Done():
   680  				return ctx.Err()
   681  			}
   682  		}
   683  	})
   684  	if err := g.Wait(); err != nil {
   685  		kbCtx.Log.Debug("AppBeginBackgroundTask: dropped out of wait because: %s", err)
   686  	}
   687  }
   688  
   689  func startTrace(logFile string) {
   690  	if os.Getenv("KEYBASE_TRACE_MOBILE") != "1" {
   691  		return
   692  	}
   693  
   694  	tname := filepath.Join(filepath.Dir(logFile), "svctrace.out")
   695  	f, err := os.Create(tname)
   696  	if err != nil {
   697  		fmt.Printf("error creating %s\n", tname)
   698  		return
   699  	}
   700  	fmt.Printf("Go: starting trace %s\n", tname)
   701  	_ = trace.Start(f)
   702  	go func() {
   703  		fmt.Printf("Go: sleeping 30s for trace\n")
   704  		time.Sleep(30 * time.Second)
   705  		fmt.Printf("Go: stopping trace %s\n", tname)
   706  		trace.Stop()
   707  		time.Sleep(5 * time.Second)
   708  		fmt.Printf("Go: trace stopped\n")
   709  	}()
   710  }